diff options
Diffstat (limited to 'net/ipv6/datagram.c')
| -rw-r--r-- | net/ipv6/datagram.c | 108 | 
1 files changed, 107 insertions, 1 deletions
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c index 622dc7939a1..5959230bc6c 100644 --- a/net/ipv6/datagram.c +++ b/net/ipv6/datagram.c @@ -278,6 +278,45 @@ void ipv6_local_error(struct sock *sk, int err, struct flowi *fl, u32 info)  		kfree_skb(skb);  } +void ipv6_local_rxpmtu(struct sock *sk, struct flowi *fl, u32 mtu) +{ +	struct ipv6_pinfo *np = inet6_sk(sk); +	struct ipv6hdr *iph; +	struct sk_buff *skb; +	struct ip6_mtuinfo *mtu_info; + +	if (!np->rxopt.bits.rxpmtu) +		return; + +	skb = alloc_skb(sizeof(struct ipv6hdr), GFP_ATOMIC); +	if (!skb) +		return; + +	skb_put(skb, sizeof(struct ipv6hdr)); +	skb_reset_network_header(skb); +	iph = ipv6_hdr(skb); +	ipv6_addr_copy(&iph->daddr, &fl->fl6_dst); + +	mtu_info = IP6CBMTU(skb); +	if (!mtu_info) { +		kfree_skb(skb); +		return; +	} + +	mtu_info->ip6m_mtu = mtu; +	mtu_info->ip6m_addr.sin6_family = AF_INET6; +	mtu_info->ip6m_addr.sin6_port = 0; +	mtu_info->ip6m_addr.sin6_flowinfo = 0; +	mtu_info->ip6m_addr.sin6_scope_id = fl->oif; +	ipv6_addr_copy(&mtu_info->ip6m_addr.sin6_addr, &ipv6_hdr(skb)->daddr); + +	__skb_pull(skb, skb_tail_pointer(skb) - skb->data); +	skb_reset_transport_header(skb); + +	skb = xchg(&np->rxpmtu, skb); +	kfree_skb(skb); +} +  /*   *	Handle MSG_ERRQUEUE   */ @@ -381,6 +420,54 @@ out:  	return err;  } +/* + *	Handle IPV6_RECVPATHMTU + */ +int ipv6_recv_rxpmtu(struct sock *sk, struct msghdr *msg, int len) +{ +	struct ipv6_pinfo *np = inet6_sk(sk); +	struct sk_buff *skb; +	struct sockaddr_in6 *sin; +	struct ip6_mtuinfo mtu_info; +	int err; +	int copied; + +	err = -EAGAIN; +	skb = xchg(&np->rxpmtu, NULL); +	if (skb == NULL) +		goto out; + +	copied = skb->len; +	if (copied > len) { +		msg->msg_flags |= MSG_TRUNC; +		copied = len; +	} +	err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); +	if (err) +		goto out_free_skb; + +	sock_recv_timestamp(msg, sk, skb); + +	memcpy(&mtu_info, IP6CBMTU(skb), sizeof(mtu_info)); + +	sin = (struct sockaddr_in6 *)msg->msg_name; +	if (sin) { +		sin->sin6_family = AF_INET6; +		sin->sin6_flowinfo = 0; +		sin->sin6_port = 0; +		sin->sin6_scope_id = mtu_info.ip6m_addr.sin6_scope_id; +		ipv6_addr_copy(&sin->sin6_addr, &mtu_info.ip6m_addr.sin6_addr); +	} + +	put_cmsg(msg, SOL_IPV6, IPV6_PATHMTU, sizeof(mtu_info), &mtu_info); + +	err = copied; + +out_free_skb: +	kfree_skb(skb); +out: +	return err; +}  int datagram_recv_ctl(struct sock *sk, struct msghdr *msg, struct sk_buff *skb) @@ -497,7 +584,7 @@ int datagram_recv_ctl(struct sock *sk, struct msghdr *msg, struct sk_buff *skb)  int datagram_send_ctl(struct net *net,  		      struct msghdr *msg, struct flowi *fl,  		      struct ipv6_txoptions *opt, -		      int *hlimit, int *tclass) +		      int *hlimit, int *tclass, int *dontfrag)  {  	struct in6_pktinfo *src_info;  	struct cmsghdr *cmsg; @@ -737,6 +824,25 @@ int datagram_send_ctl(struct net *net,  			break;  		    } + +		case IPV6_DONTFRAG: +		    { +			int df; + +			err = -EINVAL; +			if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) { +				goto exit_f; +			} + +			df = *(int *)CMSG_DATA(cmsg); +			if (df < 0 || df > 1) +				goto exit_f; + +			err = 0; +			*dontfrag = df; + +			break; +		    }  		default:  			LIMIT_NETDEBUG(KERN_DEBUG "invalid cmsg type: %d\n",  				       cmsg->cmsg_type);  |