[IPV6]: Support several new sockopt / ancillary data in Advanced API (RFC3542).

Support several new socket options / ancillary data:
  IPV6_RECVPKTINFO, IPV6_PKTINFO,
  IPV6_RECVHOPOPTS, IPV6_HOPOPTS,
  IPV6_RECVDSTOPTS, IPV6_DSTOPTS, IPV6_RTHDRDSTOPTS,
  IPV6_RECVRTHDR, IPV6_RTHDR,
  IPV6_RECVHOPOPTS, IPV6_HOPOPTS

Old semantics are preserved as IPV6_2292xxxx so that
we can maintain backward compatibility.

Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index 76466af..dc1d991 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -210,39 +210,127 @@
 		retv = 0;
 		break;
 
-	case IPV6_PKTINFO:
+	case IPV6_RECVPKTINFO:
 		np->rxopt.bits.rxinfo = valbool;
 		retv = 0;
 		break;
+		
+	case IPV6_2292PKTINFO:
+		np->rxopt.bits.rxoinfo = valbool;
+		retv = 0;
+		break;
 
-	case IPV6_HOPLIMIT:
+	case IPV6_RECVHOPLIMIT:
 		np->rxopt.bits.rxhlim = valbool;
 		retv = 0;
 		break;
 
-	case IPV6_RTHDR:
+	case IPV6_2292HOPLIMIT:
+		np->rxopt.bits.rxohlim = valbool;
+		retv = 0;
+		break;
+
+	case IPV6_RECVRTHDR:
 		if (val < 0 || val > 2)
 			goto e_inval;
 		np->rxopt.bits.srcrt = val;
 		retv = 0;
 		break;
 
-	case IPV6_HOPOPTS:
+	case IPV6_2292RTHDR:
+		if (val < 0 || val > 2)
+			goto e_inval;
+		np->rxopt.bits.osrcrt = val;
+		retv = 0;
+		break;
+
+	case IPV6_RECVHOPOPTS:
 		np->rxopt.bits.hopopts = valbool;
 		retv = 0;
 		break;
 
-	case IPV6_DSTOPTS:
+	case IPV6_2292HOPOPTS:
+		np->rxopt.bits.ohopopts = valbool;
+		retv = 0;
+		break;
+
+	case IPV6_RECVDSTOPTS:
 		np->rxopt.bits.dstopts = valbool;
 		retv = 0;
 		break;
 
+	case IPV6_2292DSTOPTS:
+		np->rxopt.bits.odstopts = valbool;
+		retv = 0;
+		break;
+
 	case IPV6_FLOWINFO:
 		np->rxopt.bits.rxflow = valbool;
 		retv = 0;
 		break;
 
-	case IPV6_PKTOPTIONS:
+	case IPV6_HOPOPTS:
+	case IPV6_RTHDRDSTOPTS:
+	case IPV6_RTHDR:
+	case IPV6_DSTOPTS:
+	{
+		struct ipv6_txoptions *opt;
+		if (optlen == 0)
+			optval = 0;
+
+		/* hop-by-hop / destination options are privileged option */
+		retv = -EPERM;
+		if (optname != IPV6_RTHDR && !capable(CAP_NET_RAW))
+			break;
+
+		retv = -EINVAL;
+		if (optlen & 0x7 || optlen > 8 * 255)
+			break;
+
+		opt = ipv6_renew_options(sk, np->opt, optname,
+					 (struct ipv6_opt_hdr __user *)optval,
+					 optlen);
+		if (IS_ERR(opt)) {
+			retv = PTR_ERR(opt);
+			break;
+		}
+
+		/* routing header option needs extra check */
+		if (optname == IPV6_RTHDR && opt->srcrt) {
+			struct ipv6_rt_hdr *rthdr = opt->srcrt;
+			if (rthdr->type)
+				goto sticky_done;
+			if ((rthdr->hdrlen & 1) ||
+			    (rthdr->hdrlen >> 1) != rthdr->segments_left)
+				goto sticky_done;
+		}
+
+		retv = 0;
+		if (sk->sk_type == SOCK_STREAM) {
+			if (opt) {
+				struct tcp_sock *tp = tcp_sk(sk);
+				if (!((1 << sk->sk_state) &
+				      (TCPF_LISTEN | TCPF_CLOSE))
+				    && inet_sk(sk)->daddr != LOOPBACK4_IPV6) {
+					tp->ext_header_len = opt->opt_flen + opt->opt_nflen;
+					tcp_sync_mss(sk, tp->pmtu_cookie);
+				}
+			}
+			opt = xchg(&np->opt, opt);
+			sk_dst_reset(sk);
+		} else {
+			write_lock(&sk->sk_dst_lock);
+			opt = xchg(&np->opt, opt);
+			write_unlock(&sk->sk_dst_lock);
+			sk_dst_reset(sk);
+		}
+sticky_done:
+		if (opt)
+			sock_kfree_s(sk, opt, opt->tot_len);
+		break;
+	}
+
+	case IPV6_2292PKTOPTIONS:
 	{
 		struct ipv6_txoptions *opt = NULL;
 		struct msghdr msg;
@@ -529,6 +617,17 @@
 	return -EINVAL;
 }
 
+int ipv6_getsockopt_sticky(struct sock *sk, struct ipv6_opt_hdr *hdr,
+			   char __user *optval, int len)
+{
+	if (!hdr)
+		return 0;
+	len = min_t(int, len, ipv6_optlen(hdr));
+	if (copy_to_user(optval, hdr, ipv6_optlen(hdr)))
+		return -EFAULT;
+	return len;
+}
+
 int ipv6_getsockopt(struct sock *sk, int level, int optname,
 		    char __user *optval, int __user *optlen)
 {
@@ -567,7 +666,7 @@
 		return err;
 	}
 
-	case IPV6_PKTOPTIONS:
+	case IPV6_2292PKTOPTIONS:
 	{
 		struct msghdr msg;
 		struct sk_buff *skb;
@@ -601,6 +700,16 @@
 				int hlim = np->mcast_hops;
 				put_cmsg(&msg, SOL_IPV6, IPV6_HOPLIMIT, sizeof(hlim), &hlim);
 			}
+			if (np->rxopt.bits.rxoinfo) {
+				struct in6_pktinfo src_info;
+				src_info.ipi6_ifindex = np->mcast_oif;
+				ipv6_addr_copy(&src_info.ipi6_addr, &np->daddr);
+				put_cmsg(&msg, SOL_IPV6, IPV6_2292PKTINFO, sizeof(src_info), &src_info);
+			}
+			if (np->rxopt.bits.rxohlim) {
+				int hlim = np->mcast_hops;
+				put_cmsg(&msg, SOL_IPV6, IPV6_2292HOPLIMIT, sizeof(hlim), &hlim);
+			}
 		}
 		len -= msg.msg_controllen;
 		return put_user(len, optlen);
@@ -625,26 +734,59 @@
 		val = np->ipv6only;
 		break;
 
-	case IPV6_PKTINFO:
+	case IPV6_RECVPKTINFO:
 		val = np->rxopt.bits.rxinfo;
 		break;
 
-	case IPV6_HOPLIMIT:
+	case IPV6_2292PKTINFO:
+		val = np->rxopt.bits.rxoinfo;
+		break;
+
+	case IPV6_RECVHOPLIMIT:
 		val = np->rxopt.bits.rxhlim;
 		break;
 
-	case IPV6_RTHDR:
+	case IPV6_2292HOPLIMIT:
+		val = np->rxopt.bits.rxohlim;
+		break;
+
+	case IPV6_RECVRTHDR:
 		val = np->rxopt.bits.srcrt;
 		break;
 
+	case IPV6_2292RTHDR:
+		val = np->rxopt.bits.osrcrt;
+		break;
+
 	case IPV6_HOPOPTS:
+	case IPV6_RTHDRDSTOPTS:
+	case IPV6_RTHDR:
+	case IPV6_DSTOPTS:
+	{
+
+		lock_sock(sk);
+		len = ipv6_getsockopt_sticky(sk, np->opt->hopopt,
+					     optval, len);
+		release_sock(sk);
+		return put_user(len, optlen);
+	}
+
+	case IPV6_RECVHOPOPTS:
 		val = np->rxopt.bits.hopopts;
 		break;
 
-	case IPV6_DSTOPTS:
+	case IPV6_2292HOPOPTS:
+		val = np->rxopt.bits.ohopopts;
+		break;
+
+	case IPV6_RECVDSTOPTS:
 		val = np->rxopt.bits.dstopts;
 		break;
 
+	case IPV6_2292DSTOPTS:
+		val = np->rxopt.bits.odstopts;
+		break;
+
 	case IPV6_FLOWINFO:
 		val = np->rxopt.bits.rxflow;
 		break;