diff options
Diffstat (limited to 'pfinet/linux-src/net/ipv6/raw_ipv6.c')
-rw-r--r-- | pfinet/linux-src/net/ipv6/raw_ipv6.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/pfinet/linux-src/net/ipv6/raw_ipv6.c b/pfinet/linux-src/net/ipv6/raw_ipv6.c new file mode 100644 index 00000000..95856ea7 --- /dev/null +++ b/pfinet/linux-src/net/ipv6/raw_ipv6.c @@ -0,0 +1,644 @@ +/* + * RAW sockets for IPv6 + * Linux INET6 implementation + * + * Authors: + * Pedro Roque <roque@di.fc.ul.pt> + * + * Adapted from linux/net/ipv4/raw.c + * + * $Id: raw_ipv6.c,v 1.1 2007/10/08 21:12:31 stesie Exp $ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/sockios.h> +#include <linux/sched.h> +#include <linux/net.h> +#include <linux/in6.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/icmpv6.h> +#include <asm/uaccess.h> + +#include <net/sock.h> +#include <net/snmp.h> + +#include <net/ipv6.h> +#include <net/ndisc.h> +#include <net/protocol.h> +#include <net/ip6_route.h> +#include <net/addrconf.h> +#include <net/transp_v6.h> + +#include <net/rawv6.h> + +#include <asm/uaccess.h> + +struct sock *raw_v6_htable[RAWV6_HTABLE_SIZE]; + +static void raw_v6_hash(struct sock *sk) +{ + struct sock **skp = &raw_v6_htable[sk->num & (RAWV6_HTABLE_SIZE - 1)]; + + SOCKHASH_LOCK(); + if ((sk->next = *skp) != NULL) + (*skp)->pprev = &sk->next; + *skp = sk; + sk->pprev = skp; + SOCKHASH_UNLOCK(); +} + +static void raw_v6_unhash(struct sock *sk) +{ + SOCKHASH_LOCK(); + if (sk->pprev) { + if (sk->next) + sk->next->pprev = sk->pprev; + *sk->pprev = sk->next; + sk->pprev = NULL; + } + SOCKHASH_UNLOCK(); +} + +static __inline__ int inet6_mc_check(struct sock *sk, struct in6_addr *addr) +{ + struct ipv6_mc_socklist *mc; + + for (mc = sk->net_pinfo.af_inet6.ipv6_mc_list; mc; mc=mc->next) { + if (ipv6_addr_cmp(&mc->addr, addr) == 0) + return 1; + } + + return 0; +} + +/* Grumble... icmp and ip_input want to get at this... */ +struct sock *raw_v6_lookup(struct sock *sk, unsigned short num, + struct in6_addr *loc_addr, struct in6_addr *rmt_addr) +{ + struct sock *s = sk; + int addr_type = ipv6_addr_type(loc_addr); + + for(s = sk; s; s = s->next) { + if((s->num == num) && + !(s->dead && (s->state == TCP_CLOSE))) { + struct ipv6_pinfo *np = &s->net_pinfo.af_inet6; + + if (!ipv6_addr_any(&np->daddr) && + ipv6_addr_cmp(&np->daddr, rmt_addr)) + continue; + + if (!ipv6_addr_any(&np->rcv_saddr)) { + if (ipv6_addr_cmp(&np->rcv_saddr, loc_addr) == 0) + return(s); + if ((addr_type & IPV6_ADDR_MULTICAST) && + inet6_mc_check(s, loc_addr)) + return (s); + continue; + } + return(s); + } + } + return NULL; +} + +/* This cleans up af_inet6 a bit. -DaveM */ +static int rawv6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len) +{ + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) uaddr; + __u32 v4addr = 0; + int addr_type; + + /* Check these errors. */ + if (sk->state != TCP_CLOSE || (addr_len < sizeof(struct sockaddr_in6))) + return -EINVAL; + + addr_type = ipv6_addr_type(&addr->sin6_addr); + + /* Check if the address belongs to the host. */ + if (addr_type == IPV6_ADDR_MAPPED) { + /* Raw sockets are IPv6 only */ + return(-EADDRNOTAVAIL); + } else { + if (addr_type != IPV6_ADDR_ANY) { + /* ipv4 addr of the socket is invalid. Only the + * unpecified and mapped address have a v4 equivalent. + */ + v4addr = LOOPBACK4_IPV6; + if (!(addr_type & IPV6_ADDR_MULTICAST)) { + if (ipv6_chk_addr(&addr->sin6_addr, NULL, 0) == NULL) + return(-EADDRNOTAVAIL); + } + } + } + + sk->rcv_saddr = v4addr; + sk->saddr = v4addr; + memcpy(&sk->net_pinfo.af_inet6.rcv_saddr, &addr->sin6_addr, + sizeof(struct in6_addr)); + if (!(addr_type & IPV6_ADDR_MULTICAST)) + memcpy(&sk->net_pinfo.af_inet6.saddr, &addr->sin6_addr, + sizeof(struct in6_addr)); + return 0; +} + +void rawv6_err(struct sock *sk, struct sk_buff *skb, struct ipv6hdr *hdr, + struct inet6_skb_parm *opt, + int type, int code, unsigned char *buff, u32 info) +{ + int err; + int harderr; + + if (buff > skb->tail) + return; + + /* Report error on raw socket, if: + 1. User requested recverr. + 2. Socket is connected (otherwise the error indication + is useless without recverr and error is hard. + */ + if (!sk->net_pinfo.af_inet6.recverr && sk->state != TCP_ESTABLISHED) + return; + + harderr = icmpv6_err_convert(type, code, &err); + if (type == ICMPV6_PKT_TOOBIG) + harderr = (sk->net_pinfo.af_inet6.pmtudisc == IPV6_PMTUDISC_DO); + + if (sk->net_pinfo.af_inet6.recverr) + ipv6_icmp_error(sk, skb, err, 0, ntohl(info), buff); + + if (sk->net_pinfo.af_inet6.recverr || harderr) { + sk->err = err; + sk->error_report(sk); + } +} + +static inline int rawv6_rcv_skb(struct sock * sk, struct sk_buff * skb) +{ + /* Charge it to the socket. */ + if (sock_queue_rcv_skb(sk,skb)<0) { + ipv6_statistics.Ip6InDiscards++; + kfree_skb(skb); + return 0; + } + + ipv6_statistics.Ip6InDelivers++; + return 0; +} + +/* + * This is next to useless... + * if we demultiplex in network layer we don't need the extra call + * just to queue the skb... + * maybe we could have the network decide uppon a hint if it + * should call raw_rcv for demultiplexing + */ +int rawv6_rcv(struct sock *sk, struct sk_buff *skb, unsigned long len) +{ + if (sk->ip_hdrincl) + skb->h.raw = skb->nh.raw; + + rawv6_rcv_skb(sk, skb); + return 0; +} + + +/* + * This should be easy, if there is something there + * we return it, otherwise we block. + */ + +int rawv6_recvmsg(struct sock *sk, struct msghdr *msg, int len, + int noblock, int flags, int *addr_len) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)msg->msg_name; + struct sk_buff *skb; + int copied, err; + + if (flags & MSG_OOB) + return -EOPNOTSUPP; + + if (addr_len) + *addr_len=sizeof(*sin6); + + if (flags & MSG_ERRQUEUE) + return ipv6_recv_error(sk, msg, len); + + skb = skb_recv_datagram(sk, flags, noblock, &err); + if (!skb) + goto out; + + copied = skb->tail - skb->h.raw; + if (copied > len) { + copied = len; + msg->msg_flags |= MSG_TRUNC; + } + + err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); + sk->stamp=skb->stamp; + if (err) + goto out_free; + + /* Copy the address. */ + if (sin6) { + sin6->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, &skb->nh.ipv6h->saddr, + sizeof(struct in6_addr)); + sin6->sin6_flowinfo = 0; + } + + if (sk->net_pinfo.af_inet6.rxopt.all) + datagram_recv_ctl(sk, msg, skb); + err = copied; + +out_free: + skb_free_datagram(sk, skb); +out: + return err; +} + +/* + * Sending... + */ + +struct rawv6_fakehdr { + struct iovec *iov; + struct sock *sk; + __u32 len; + __u32 cksum; + __u32 proto; + struct in6_addr *daddr; +}; + +static int rawv6_getfrag(const void *data, struct in6_addr *saddr, + char *buff, unsigned int offset, unsigned int len) +{ + struct iovec *iov = (struct iovec *) data; + + return memcpy_fromiovecend(buff, iov, offset, len); +} + +static int rawv6_frag_cksum(const void *data, struct in6_addr *addr, + char *buff, unsigned int offset, + unsigned int len) +{ + struct rawv6_fakehdr *hdr = (struct rawv6_fakehdr *) data; + + if (csum_partial_copy_fromiovecend(buff, hdr->iov, offset, + len, &hdr->cksum)) + return -EFAULT; + + if (offset == 0) { + struct sock *sk; + struct raw6_opt *opt; + struct in6_addr *daddr; + + sk = hdr->sk; + opt = &sk->tp_pinfo.tp_raw; + + if (hdr->daddr) + daddr = hdr->daddr; + else + daddr = addr + 1; + + hdr->cksum = csum_ipv6_magic(addr, daddr, hdr->len, + hdr->proto, hdr->cksum); + + if (opt->offset < len) { + __u16 *csum; + + csum = (__u16 *) (buff + opt->offset); + *csum = hdr->cksum; + } else { + if (net_ratelimit()) + printk(KERN_DEBUG "icmp: cksum offset too big\n"); + return -EINVAL; + } + } + return 0; +} + + +static int rawv6_sendmsg(struct sock *sk, struct msghdr *msg, int len) +{ + struct ipv6_txoptions opt_space; + struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *) msg->msg_name; + struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; + struct ipv6_txoptions *opt = NULL; + struct ip6_flowlabel *flowlabel = NULL; + struct flowi fl; + int addr_len = msg->msg_namelen; + struct in6_addr *daddr; + struct raw6_opt *raw_opt; + int hlimit = -1; + u16 proto; + int err; + + /* Rough check on arithmetic overflow, + better check is made in ip6_build_xmit + */ + if (len < 0) + return -EMSGSIZE; + + /* Mirror BSD error message compatibility */ + if (msg->msg_flags & MSG_OOB) + return -EOPNOTSUPP; + + if (msg->msg_flags & ~(MSG_DONTROUTE|MSG_DONTWAIT)) + return(-EINVAL); + /* + * Get and verify the address. + */ + + fl.fl6_flowlabel = 0; + + if (sin6) { + if (addr_len < sizeof(struct sockaddr_in6)) + return(-EINVAL); + + if (sin6->sin6_family && sin6->sin6_family != AF_INET6) + return(-EINVAL); + + /* port is the proto value [0..255] carried in nexthdr */ + proto = ntohs(sin6->sin6_port); + + if (!proto) + proto = sk->num; + + if (proto > 255) + return(-EINVAL); + + daddr = &sin6->sin6_addr; + if (np->sndflow) { + fl.fl6_flowlabel = sin6->sin6_flowinfo&IPV6_FLOWINFO_MASK; + if (fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) { + flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel); + if (flowlabel == NULL) + return -EINVAL; + daddr = &flowlabel->dst; + } + } + + + /* Otherwise it will be difficult to maintain sk->dst_cache. */ + if (sk->state == TCP_ESTABLISHED && + !ipv6_addr_cmp(daddr, &sk->net_pinfo.af_inet6.daddr)) + daddr = &sk->net_pinfo.af_inet6.daddr; + } else { + if (sk->state != TCP_ESTABLISHED) + return(-EINVAL); + + proto = sk->num; + daddr = &(sk->net_pinfo.af_inet6.daddr); + fl.fl6_flowlabel = np->flow_label; + } + + if (ipv6_addr_any(daddr)) { + /* + * unspecfied destination address + * treated as error... is this correct ? + */ + return(-EINVAL); + } + + fl.oif = sk->bound_dev_if; + fl.fl6_src = NULL; + + if (msg->msg_controllen) { + opt = &opt_space; + memset(opt, 0, sizeof(struct ipv6_txoptions)); + + err = datagram_send_ctl(msg, &fl, opt, &hlimit); + if (err < 0) { + fl6_sock_release(flowlabel); + return err; + } + if ((fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) && !flowlabel) { + flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel); + if (flowlabel == NULL) + return -EINVAL; + } + if (!(opt->opt_nflen|opt->opt_flen)) + opt = NULL; + } + if (opt == NULL) + opt = np->opt; + if (flowlabel) + opt = fl6_merge_options(&opt_space, flowlabel, opt); + + raw_opt = &sk->tp_pinfo.tp_raw; + + fl.proto = proto; + fl.fl6_dst = daddr; + fl.uli_u.icmpt.type = 0; + fl.uli_u.icmpt.code = 0; + + if (raw_opt->checksum) { + struct rawv6_fakehdr hdr; + + hdr.iov = msg->msg_iov; + hdr.sk = sk; + hdr.len = len; + hdr.cksum = 0; + hdr.proto = proto; + + if (opt && opt->srcrt) + hdr.daddr = daddr; + else + hdr.daddr = NULL; + + err = ip6_build_xmit(sk, rawv6_frag_cksum, &hdr, &fl, len, + opt, hlimit, msg->msg_flags); + } else { + err = ip6_build_xmit(sk, rawv6_getfrag, msg->msg_iov, &fl, len, + opt, hlimit, msg->msg_flags); + } + + fl6_sock_release(flowlabel); + + return err<0?err:len; +} + +static int rawv6_seticmpfilter(struct sock *sk, int level, int optname, + char *optval, int optlen) +{ + switch (optname) { + case ICMPV6_FILTER: + if (optlen > sizeof(struct icmp6_filter)) + optlen = sizeof(struct icmp6_filter); + if (copy_from_user(&sk->tp_pinfo.tp_raw.filter, optval, optlen)) + return -EFAULT; + return 0; + default: + return -ENOPROTOOPT; + }; + + return 0; +} + +static int rawv6_geticmpfilter(struct sock *sk, int level, int optname, + char *optval, int *optlen) +{ + int len; + + switch (optname) { + case ICMPV6_FILTER: + if (get_user(len, optlen)) + return -EFAULT; + if (len > sizeof(struct icmp6_filter)) + len = sizeof(struct icmp6_filter); + if (put_user(len, optlen)) + return -EFAULT; + if (copy_to_user(optval, &sk->tp_pinfo.tp_raw.filter, len)) + return -EFAULT; + return 0; + default: + return -ENOPROTOOPT; + }; + + return 0; +} + + +static int rawv6_setsockopt(struct sock *sk, int level, int optname, + char *optval, int optlen) +{ + struct raw6_opt *opt = &sk->tp_pinfo.tp_raw; + int val; + + switch(level) { + case SOL_RAW: + break; + + case SOL_ICMPV6: + if (sk->num != IPPROTO_ICMPV6) + return -EOPNOTSUPP; + return rawv6_seticmpfilter(sk, level, optname, optval, + optlen); + case SOL_IPV6: + if (optname == IPV6_CHECKSUM) + break; + default: + return ipv6_setsockopt(sk, level, optname, optval, + optlen); + }; + + if (get_user(val, (int *)optval)) + return -EFAULT; + + switch (optname) { + case IPV6_CHECKSUM: + if (val < 0) { + opt->checksum = 0; + } else { + opt->checksum = 1; + opt->offset = val; + } + + return 0; + break; + + default: + return(-ENOPROTOOPT); + } +} + +static int rawv6_getsockopt(struct sock *sk, int level, int optname, + char *optval, int *optlen) +{ + struct raw6_opt *opt = &sk->tp_pinfo.tp_raw; + int val, len; + + switch(level) { + case SOL_RAW: + break; + + case SOL_ICMPV6: + if (sk->num != IPPROTO_ICMPV6) + return -EOPNOTSUPP; + return rawv6_geticmpfilter(sk, level, optname, optval, + optlen); + case SOL_IPV6: + if (optname == IPV6_CHECKSUM) + break; + default: + return ipv6_getsockopt(sk, level, optname, optval, + optlen); + }; + + if (get_user(len,optlen)) + return -EFAULT; + + switch (optname) { + case IPV6_CHECKSUM: + if (opt->checksum == 0) + val = -1; + else + val = opt->offset; + + default: + return -ENOPROTOOPT; + } + + len=min(sizeof(int),len); + + if (put_user(len, optlen)) + return -EFAULT; + if (copy_to_user(optval,&val,len)) + return -EFAULT; + return 0; +} + + +static void rawv6_close(struct sock *sk, long timeout) +{ + /* See for explanation: raw_close in ipv4/raw.c */ + sk->state = TCP_CLOSE; + raw_v6_unhash(sk); + if (sk->num == IPPROTO_RAW) + ip6_ra_control(sk, -1, NULL); + sk->dead = 1; + destroy_sock(sk); +} + +static int rawv6_init_sk(struct sock *sk) +{ + return(0); +} + +struct proto rawv6_prot = { + (struct sock *)&rawv6_prot, /* sklist_next */ + (struct sock *)&rawv6_prot, /* sklist_prev */ + rawv6_close, /* close */ + udpv6_connect, /* connect */ + NULL, /* accept */ + NULL, /* retransmit */ + NULL, /* write_wakeup */ + NULL, /* read_wakeup */ + datagram_poll, /* poll */ + NULL, /* ioctl */ + rawv6_init_sk, /* init */ + inet6_destroy_sock, /* destroy */ + NULL, /* shutdown */ + rawv6_setsockopt, /* setsockopt */ + rawv6_getsockopt, /* getsockopt */ + rawv6_sendmsg, /* sendmsg */ + rawv6_recvmsg, /* recvmsg */ + rawv6_bind, /* bind */ + rawv6_rcv_skb, /* backlog_rcv */ + raw_v6_hash, /* hash */ + raw_v6_unhash, /* unhash */ + NULL, /* get_port */ + 128, /* max_header */ + 0, /* retransmits */ + "RAW", /* name */ + 0, /* inuse */ + 0 /* highestinuse */ +}; |