From 2b2d7fdc42475019e5ce3eabc9c9673e3c13d89f Mon Sep 17 00:00:00 2001 From: Samuel Thibault Date: Sun, 13 Jan 2013 16:23:35 +0100 Subject: [IPV6]: Add IPV6_V6ONLY socket option support. Cherry-picked from Linux 524354b4d086a4f013343d727eaccb7b4c39eb25 * pfinet/glue-include/linux/ipv6.h: Include linux/config.h> (__ipv6_only_sock, ipv6_only_sock): New macros * pfinet/linux-src/include/linux/ipv6.h: Likewise. * pfinet/linux-src/include/linux/in6.h (IPV6_V6ONLY): New macro. * pfinet/linux-src/include/linux/sysctl.h (NET_IPV6_BINDV6ONLY): New macro. * pfinet/linux-src/include/net/ipv6.h (sysctl_ipv6_bindv6only): Declare variable. * pfinet/linux-src/include/net/sock.h (ipv6_pinfo): Add ipv6only field. * pfinet/linux-src/net/ipv4/tcp_ipv4.c: Include linux/ipv6.h. (tcp_v4_get_port, tcp_v4_lookup_listener): Test for ipv6_only_sock. * pfinet/linux-src/net/ipv4/udp.c: Include linux/ipv6.h. (udp_v4_get_port, udp_v4_lookup_longway, udp_v4_mcast_next): Test for ipv6_only_sock. * pfinet/linux-src/net/ipv6/af_inet6.c (sysctl_ipv6_bindv6only): New variable. (inet6_create): Initialize ipv6only field to sysctl_ipv6_bindv6only. * pfinet/linux-src/net/ipv6/ipv6_sockglue.c (ipv6_setsockopt): Test for ipv6_only_sock. (ipv6_setsockopt, ipv6_getsockopt): Support IPV6_V6ONLY case. * pfinet/linux-src/net/ipv6/tcp_ipv6.c (ipv6_rcv_saddr_equal): New inline function. (tcp_v6_get_port): Replace old tests with ipv6_rcv_saddr_equal. (tcp_v6_connect): Test for __ipv6_only_sock. * pfinet/linux-src/net/ipv6/udp_ipv6.c (udv6_rcv_saddr_equal): New inline function. (udp_v6_get_port): Replace old tests with udv6_rcv_saddr_equal. (udpv6_connect, udpv6_sendmsg): Test for __ipv6_only_sock. --- pfinet/glue-include/linux/ipv6.h | 9 +++++ pfinet/linux-src/include/linux/in6.h | 1 + pfinet/linux-src/include/linux/ipv6.h | 9 +++++ pfinet/linux-src/include/linux/sysctl.h | 3 +- pfinet/linux-src/include/net/ipv6.h | 3 ++ pfinet/linux-src/include/net/sock.h | 3 +- pfinet/linux-src/net/ipv4/tcp_ipv4.c | 20 +++++++---- pfinet/linux-src/net/ipv4/udp.c | 21 +++++++---- pfinet/linux-src/net/ipv6/af_inet6.c | 4 +++ pfinet/linux-src/net/ipv6/ipv6_sockglue.c | 15 +++++++- pfinet/linux-src/net/ipv6/tcp_ipv6.c | 58 ++++++++++++++++++++++++------ pfinet/linux-src/net/ipv6/udp_ipv6.c | 59 +++++++++++++++++++++++++++---- 12 files changed, 171 insertions(+), 34 deletions(-) (limited to 'pfinet') diff --git a/pfinet/glue-include/linux/ipv6.h b/pfinet/glue-include/linux/ipv6.h index 5a1604a3..3b119b90 100644 --- a/pfinet/glue-include/linux/ipv6.h +++ b/pfinet/glue-include/linux/ipv6.h @@ -1,6 +1,7 @@ #ifndef _IPV6_H #define _IPV6_H +#include #include #include @@ -112,6 +113,14 @@ struct inet6_skb_parm __u16 dst1; }; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +#define __ipv6_only_sock(sk) (sk->net_pinfo.af_inet6.ipv6only) +#define ipv6_only_sock(sk) ((sk)->family == PF_INET6 && __ipv6_only_sock(sk)) +#else +#define __ipv6_only_sock(sk) 0 +#define ipv6_only_sock(sk) 0 +#endif + #endif #endif diff --git a/pfinet/linux-src/include/linux/in6.h b/pfinet/linux-src/include/linux/in6.h index ca5e768b..35e018c7 100644 --- a/pfinet/linux-src/include/linux/in6.h +++ b/pfinet/linux-src/include/linux/in6.h @@ -179,6 +179,7 @@ struct in6_flowlabel_req #define IPV6_MTU_DISCOVER 23 #define IPV6_MTU 24 #define IPV6_RECVERR 25 +#define IPV6_V6ONLY 26 /* IPV6_MTU_DISCOVER values */ #define IPV6_PMTUDISC_DONT 0 diff --git a/pfinet/linux-src/include/linux/ipv6.h b/pfinet/linux-src/include/linux/ipv6.h index 84564bae..299db1a5 100644 --- a/pfinet/linux-src/include/linux/ipv6.h +++ b/pfinet/linux-src/include/linux/ipv6.h @@ -1,6 +1,7 @@ #ifndef _IPV6_H #define _IPV6_H +#include #include #include @@ -118,6 +119,14 @@ struct inet6_skb_parm __u16 dst1; }; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +#define __ipv6_only_sock(sk) (sk->net_pinfo.af_inet6.ipv6only) +#define ipv6_only_sock(sk) ((sk)->family == PF_INET6 && __ipv6_only_sock(sk)) +#else +#define __ipv6_only_sock(sk) 0 +#define ipv6_only_sock(sk) 0 +#endif + #endif #endif diff --git a/pfinet/linux-src/include/linux/sysctl.h b/pfinet/linux-src/include/linux/sysctl.h index 00bebabe..1067bfbb 100644 --- a/pfinet/linux-src/include/linux/sysctl.h +++ b/pfinet/linux-src/include/linux/sysctl.h @@ -276,7 +276,8 @@ enum enum { NET_IPV6_CONF=16, NET_IPV6_NEIGH=17, - NET_IPV6_ROUTE=18 + NET_IPV6_ROUTE=18, + NET_IPV6_BINDV6ONLY=20, }; enum { diff --git a/pfinet/linux-src/include/net/ipv6.h b/pfinet/linux-src/include/net/ipv6.h index 50e2299f..77520eeb 100644 --- a/pfinet/linux-src/include/net/ipv6.h +++ b/pfinet/linux-src/include/net/ipv6.h @@ -86,6 +86,9 @@ struct frag_hdr { #include +/* sysctls */ +extern int sysctl_ipv6_bindv6only; + extern struct ipv6_mib ipv6_statistics; extern struct icmpv6_mib icmpv6_statistics; extern struct udp_mib udp_stats_in6; diff --git a/pfinet/linux-src/include/net/sock.h b/pfinet/linux-src/include/net/sock.h index 7789ec40..96fdfe8b 100644 --- a/pfinet/linux-src/include/net/sock.h +++ b/pfinet/linux-src/include/net/sock.h @@ -170,7 +170,8 @@ struct ipv6_pinfo { __u8 mc_loop:1, recverr:1, sndflow:1, - pmtudisc:2; + pmtudisc:2, + ipv6only:1; struct ipv6_mc_socklist *ipv6_mc_list; struct ipv6_fl_socklist *ipv6_fl_list; diff --git a/pfinet/linux-src/net/ipv4/tcp_ipv4.c b/pfinet/linux-src/net/ipv4/tcp_ipv4.c index 60d2bdb4..df2c8b7c 100644 --- a/pfinet/linux-src/net/ipv4/tcp_ipv4.c +++ b/pfinet/linux-src/net/ipv4/tcp_ipv4.c @@ -45,9 +45,13 @@ * Vitaly E. Lavrov : Transparent proxy revived after year coma. * Andi Kleen : Fix new listen. * Andi Kleen : Fix accept error reporting. + * YOSHIFUJI Hideaki @USAGI and: Support IPV6_V6ONLY socket option, which + * Alexey Kuznetsov allow both IPv4 and IPv6 sockets to bind + * a single port at the same time. */ #include + #include #include #include @@ -61,6 +65,7 @@ #include #include +#include #include extern int sysctl_tcp_timestamps; @@ -258,7 +263,8 @@ static int tcp_v4_get_port(struct sock *sk, unsigned short snum) int sk_reuse = sk->reuse; for( ; sk2 != NULL; sk2 = sk2->bind_next) { - if (sk->bound_dev_if == sk2->bound_dev_if) { + if (!ipv6_only_sock(sk2) && + sk->bound_dev_if == sk2->bound_dev_if) { if (!sk_reuse || !sk2->reuse || sk2->state == TCP_LISTEN) { @@ -378,23 +384,23 @@ static struct sock *tcp_v4_lookup_listener(u32 daddr, unsigned short hnum, int d struct sock *result = NULL; int score, hiscore; - hiscore=0; + hiscore=-1; for(sk = tcp_listening_hash[tcp_lhashfn(hnum)]; sk; sk = sk->next) { - if(sk->num == hnum) { + if(sk->num == hnum && !ipv6_only_sock(sk)) { __u32 rcv_saddr = sk->rcv_saddr; - score = 1; + score = (sk->family == PF_INET ? 1 : 0); if(rcv_saddr) { if (rcv_saddr != daddr) continue; - score++; + score+=2; } if (sk->bound_dev_if) { if (sk->bound_dev_if != dif) continue; - score++; + score+=2; } - if (score == 3) + if (score == 5) return sk; if (score > hiscore) { hiscore = score; diff --git a/pfinet/linux-src/net/ipv4/udp.c b/pfinet/linux-src/net/ipv4/udp.c index 1ceb43e4..f9be2e04 100644 --- a/pfinet/linux-src/net/ipv4/udp.c +++ b/pfinet/linux-src/net/ipv4/udp.c @@ -61,6 +61,9 @@ * return ENOTCONN for unconnected sockets (POSIX) * Janos Farkas : don't deliver multi/broadcasts to a different * bound-to-device socket + * YOSHIFUJI Hideaki @USAGI and: Support IPV6_V6ONLY socket option, which + * Alexey Kuznetsov: allow both IPv4 and IPv6 sockets to bind + * a single port at the same time. * * * This program is free software; you can redistribute it and/or @@ -104,6 +107,7 @@ #include #include #include +#include #include #include #include @@ -178,6 +182,7 @@ gotit: sk2 = sk2->next) { if (sk2->num == snum && sk2 != sk && + !ipv6_only_sock(sk2) && sk2->bound_dev_if == sk->bound_dev_if && (!sk2->rcv_saddr || !sk->rcv_saddr || @@ -236,29 +241,30 @@ struct sock *udp_v4_lookup_longway(u32 saddr, u16 sport, u32 daddr, u16 dport, i int badness = -1; for(sk = udp_hash[hnum & (UDP_HTABLE_SIZE - 1)]; sk != NULL; sk = sk->next) { - if((sk->num == hnum) && !(sk->dead && (sk->state == TCP_CLOSE))) { - int score = 0; + if((sk->num == hnum) && !ipv6_only_sock(sk) + && !(sk->dead && (sk->state == TCP_CLOSE))) { + int score = (sk->family == PF_INET ? 1 : 0); if(sk->rcv_saddr) { if(sk->rcv_saddr != daddr) continue; - score++; + score+=2; } if(sk->daddr) { if(sk->daddr != saddr) continue; - score++; + score+=2; } if(sk->dport) { if(sk->dport != sport) continue; - score++; + score+=2; } if(sk->bound_dev_if) { if(sk->bound_dev_if != dif) continue; - score++; + score+=2; } - if(score == 4) { + if(score == 9) { result = sk; break; } else if(score > badness) { @@ -389,6 +395,7 @@ static inline struct sock *udp_v4_mcast_next(struct sock *sk, (s->daddr && s->daddr!=raddr) || (s->dport != rnum && s->dport != 0) || (s->rcv_saddr && s->rcv_saddr != laddr) || + ipv6_only_sock(s) || (s->bound_dev_if && s->bound_dev_if != dif)) continue; break; diff --git a/pfinet/linux-src/net/ipv6/af_inet6.c b/pfinet/linux-src/net/ipv6/af_inet6.c index ca428188..f10ff8d7 100644 --- a/pfinet/linux-src/net/ipv6/af_inet6.c +++ b/pfinet/linux-src/net/ipv6/af_inet6.c @@ -84,6 +84,8 @@ extern void ipv6_sysctl_register(void); extern void ipv6_sysctl_unregister(void); #endif +int sysctl_ipv6_bindv6only; + static int inet6_create(struct socket *sock, int protocol) { struct sock *sk; @@ -137,6 +139,8 @@ static int inet6_create(struct socket *sock, int protocol) sk->net_pinfo.af_inet6.mc_loop = 1; sk->net_pinfo.af_inet6.pmtudisc = IPV6_PMTUDISC_WANT; + sk->net_pinfo.af_inet6.ipv6only = sysctl_ipv6_bindv6only; + /* Init the ipv4 part of the socket since we can have sockets * using v6 API for ipv4. */ diff --git a/pfinet/linux-src/net/ipv6/ipv6_sockglue.c b/pfinet/linux-src/net/ipv6/ipv6_sockglue.c index 6a48d1be..f1f67811 100644 --- a/pfinet/linux-src/net/ipv6/ipv6_sockglue.c +++ b/pfinet/linux-src/net/ipv6/ipv6_sockglue.c @@ -146,7 +146,8 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname, char *optval, goto addrform_done; } - if (!(ipv6_addr_type(&np->daddr) & IPV6_ADDR_MAPPED)) { + if (ipv6_only_sock(sk) || + !(ipv6_addr_type(&np->daddr) & IPV6_ADDR_MAPPED)) { retv = -EADDRNOTAVAIL; goto addrform_done; } @@ -181,6 +182,15 @@ addrform_done: } break; + case IPV6_V6ONLY: + if (sk->num) { + retv = -EINVAL; + goto out; + } + np->ipv6only = valbool; + retv = 0; + break; + case IPV6_PKTINFO: np->rxopt.bits.rxinfo = valbool; retv = 0; @@ -395,6 +405,9 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname, char *optval, if (!val) return -ENOTCONN; break; + case IPV6_V6ONLY: + val = np->ipv6only; + break; default: return -EINVAL; } diff --git a/pfinet/linux-src/net/ipv6/tcp_ipv6.c b/pfinet/linux-src/net/ipv6/tcp_ipv6.c index c761e76c..3fba9af6 100644 --- a/pfinet/linux-src/net/ipv6/tcp_ipv6.c +++ b/pfinet/linux-src/net/ipv6/tcp_ipv6.c @@ -4,6 +4,9 @@ * * Authors: * Pedro Roque + * YOSHIFUJI Hideaki @USAGI and: Support IPV6_V6ONLY socket option, which + * Alexey Kuznetsov allow both IPv4 and IPv6 sockets to bind + * a single port at the same time. * * $Id: tcp_ipv6.c,v 1.3 2007/10/13 01:43:00 stesie Exp $ * @@ -79,6 +82,43 @@ static __inline__ int tcp_v6_sk_hashfn(struct sock *sk) return tcp_v6_hashfn(laddr, lport, faddr, fport); } +static inline int ipv6_rcv_saddr_equal(struct sock *sk, struct sock *sk2) +{ + struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; + int addr_type = ipv6_addr_type(&np->rcv_saddr); + + if (!sk2->rcv_saddr && !ipv6_only_sock(sk)) + return 1; + + if (sk2->family == AF_INET6 && + ipv6_addr_any(&sk2->rcv_saddr) && + !(ipv6_only_sock(sk2) && addr_type == IPV6_ADDR_MAPPED)) + return 1; + + if (addr_type == IPV6_ADDR_ANY && + (!ipv6_only_sock(sk) || + !(sk2->family == AF_INET6 ? + ipv6_addr_type(&sk2->rcv_saddr) == IPV6_ADDR_MAPPED : 1))) + return 1; + + if (sk2->family == AF_INET6 && + !ipv6_addr_cmp(&np->rcv_saddr, + (sk2->state != TCP_TIME_WAIT ? + &sk2->rcv_saddr : + &((struct tcp_tw_bucket *)sk)->v6_rcv_saddr))) + return 1; + + if (addr_type == IPV6_ADDR_MAPPED && + !ipv6_only_sock(sk2) && + (!sk2->rcv_saddr || + !sk->rcv_saddr || + sk->rcv_saddr == sk2->rcv_saddr)) + return 1; + + return 0; +} + + /* Grrr, addr_type already calculated by caller, but I don't want * to add some silly "cookie" argument to this method just for that. * But it doesn't matter, the recalculation is in the rarest path @@ -134,18 +174,11 @@ static int tcp_v6_get_port(struct sock *sk, unsigned short snum) for( ; sk2 != NULL; sk2 = sk2->bind_next) { if (sk->bound_dev_if == sk2->bound_dev_if) { - if (!sk_reuse || + if ((!sk_reuse || !sk2->reuse || - sk2->state == TCP_LISTEN) { - /* NOTE: IPv6 tw bucket have different format */ - if (!sk2->rcv_saddr || - addr_type == IPV6_ADDR_ANY || - !ipv6_addr_cmp(&sk->net_pinfo.af_inet6.rcv_saddr, - sk2->state != TCP_TIME_WAIT ? - &sk2->net_pinfo.af_inet6.rcv_saddr : - &((struct tcp_tw_bucket*)sk)->v6_rcv_saddr)) - break; - } + sk2->state == TCP_LISTEN) && + ipv6_rcv_saddr_equal(sk, sk2)) + break; } } /* If we found a conflict, fail. */ @@ -460,6 +493,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, SOCK_DEBUG(sk, "connect: ipv4 mapped\n"); + if (__ipv6_only_sock(sk)) + return -ENETUNREACH; + sin.sin_family = AF_INET; sin.sin_port = usin->sin6_port; sin.sin_addr.s_addr = usin->sin6_addr.s6_addr32[3]; diff --git a/pfinet/linux-src/net/ipv6/udp_ipv6.c b/pfinet/linux-src/net/ipv6/udp_ipv6.c index f6968ae4..bbc4f027 100644 --- a/pfinet/linux-src/net/ipv6/udp_ipv6.c +++ b/pfinet/linux-src/net/ipv6/udp_ipv6.c @@ -5,6 +5,10 @@ * Authors: * Pedro Roque * + * YOSHIFUJI Hideaki @USAGI and: Support IPV6_V6ONLY socket option, which + * Alexey Kuznetsov allow both IPv4 and IPv6 sockets to bind + * a single port at the same time. + * * Based on linux/ipv4/udp.c * * $Id: udp_ipv6.c,v 1.3 2007/10/13 01:43:00 stesie Exp $ @@ -46,6 +50,41 @@ struct udp_mib udp_stats_in6; +static __inline__ int udv6_rcv_saddr_equal(struct sock *sk, struct sock *sk2) +{ + struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; + int addr_type = ipv6_addr_type(&np->rcv_saddr); + + if (!sk2->rcv_saddr && !ipv6_only_sock(sk)) + return 1; + + if (sk2->family == AF_INET6 && + ipv6_addr_any(&sk2->rcv_saddr) && + !(ipv6_only_sock(sk2) && addr_type == IPV6_ADDR_MAPPED)) + return 1; + + if (addr_type == IPV6_ADDR_ANY && + (!ipv6_only_sock(sk) || + !(sk2->family == AF_INET6 ? + ipv6_addr_type(&sk2->rcv_saddr) == IPV6_ADDR_MAPPED : 1))) + return 1; + + if (sk2->family == AF_INET6 && + !ipv6_addr_cmp(&sk->rcv_saddr, + &sk2->rcv_saddr)) + return 1; + + if (addr_type == IPV6_ADDR_MAPPED && + !ipv6_only_sock(sk2) && + (!sk2->rcv_saddr || + !sk->rcv_saddr || + sk->rcv_saddr == sk2->rcv_saddr)) + return 1; + + return 0; +} + + /* Grrr, addr_type already calculated by caller, but I don't want * to add some silly "cookie" argument to this method just for that. */ @@ -104,11 +143,8 @@ gotit: if (sk2->num == snum && sk2 != sk && sk2->bound_dev_if == sk->bound_dev_if && - (!sk2->rcv_saddr || - addr_type == IPV6_ADDR_ANY || - !ipv6_addr_cmp(&sk->net_pinfo.af_inet6.rcv_saddr, - &sk2->net_pinfo.af_inet6.rcv_saddr)) && - (!sk2->reuse || !sk->reuse)) + (!sk2->reuse || !sk->reuse) && + udv6_rcv_saddr_equal(sk, sk2)) goto fail; } } @@ -208,6 +244,8 @@ int udpv6_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) int err; if (usin->sin6_family == AF_INET) { + if (__ipv6_only_sock(sk)) + return -EAFNOSUPPORT; err = udp_connect(sk, uaddr, addr_len); goto ipv4_connected; } @@ -251,6 +289,9 @@ int udpv6_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) if (addr_type == IPV6_ADDR_MAPPED) { struct sockaddr_in sin; + if (__ipv6_only_sock(sk)) + return -ENETUNREACH; + sin.sin_family = AF_INET; sin.sin_addr.s_addr = daddr->s6_addr32[3]; sin.sin_port = usin->sin6_port; @@ -806,8 +847,11 @@ static int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, int ulen) fl.fl6_flowlabel = 0; if (sin6) { - if (sin6->sin6_family == AF_INET) + if (sin6->sin6_family == AF_INET) { + if (__ipv6_only_sock(sk)) + return -ENETUNREACH; return udp_sendmsg(sk, msg, ulen); + } if (addr_len < sizeof(*sin6)) return(-EINVAL); @@ -854,6 +898,9 @@ static int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, int ulen) if (addr_type == IPV6_ADDR_MAPPED) { struct sockaddr_in sin; + if (__ipv6_only_sock(sk)) + return -ENETUNREACH; + sin.sin_family = AF_INET; sin.sin_addr.s_addr = daddr->s6_addr32[3]; sin.sin_port = udh.uh.dest; -- cgit v1.2.3