Skip to content
Snippets Groups Projects
tcp_ipv6.c 40.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	TCP over IPv6
     *	Linux INET6 implementation 
     *
     *	Authors:
     *	Pedro Roque		<roque@di.fc.ul.pt>	
     *
     *	$Id: tcp_ipv6.c,v 1.144 2002/02/01 22:01:04 davem Exp $
     *
     *	Based on: 
     *	linux/net/ipv4/tcp.c
     *	linux/net/ipv4/tcp_input.c
     *	linux/net/ipv4/tcp_output.c
     *
     *	Fixes:
     *	Hideaki YOSHIFUJI	:	sin6_scope_id support
     *	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.
     *	YOSHIFUJI Hideaki @USAGI:	convert /proc/net/tcp6 to seq_file.
     *
     *	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/module.h>
    #include <linux/config.h>
    #include <linux/errno.h>
    #include <linux/types.h>
    #include <linux/socket.h>
    #include <linux/sockios.h>
    #include <linux/net.h>
    #include <linux/jiffies.h>
    #include <linux/in.h>
    #include <linux/in6.h>
    #include <linux/netdevice.h>
    #include <linux/init.h>
    #include <linux/jhash.h>
    #include <linux/ipsec.h>
    #include <linux/times.h>
    
    #include <linux/ipv6.h>
    #include <linux/icmpv6.h>
    #include <linux/random.h>
    
    #include <net/tcp.h>
    #include <net/ndisc.h>
    
    #include <net/inet6_connection_sock.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <net/ipv6.h>
    #include <net/transp_v6.h>
    #include <net/addrconf.h>
    #include <net/ip6_route.h>
    #include <net/ip6_checksum.h>
    #include <net/inet_ecn.h>
    #include <net/protocol.h>
    #include <net/xfrm.h>
    #include <net/addrconf.h>
    #include <net/snmp.h>
    #include <net/dsfield.h>
    
    #include <net/timewait_sock.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #include <asm/uaccess.h>
    
    #include <linux/proc_fs.h>
    #include <linux/seq_file.h>
    
    
    /* Socket used for sending RSTs and ACKs */
    static struct socket *tcp6_socket;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static void	tcp_v6_send_reset(struct sk_buff *skb);
    
    static void	tcp_v6_reqsk_send_ack(struct sk_buff *skb, struct request_sock *req);
    
    static void	tcp_v6_send_check(struct sock *sk, int len, 
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				  struct sk_buff *skb);
    
    static int	tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb);
    
    
    static struct inet_connection_sock_af_ops ipv6_mapped;
    static struct inet_connection_sock_af_ops ipv6_specific;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    static int tcp_v6_get_port(struct sock *sk, unsigned short snum)
    {
    
    	return inet_csk_get_port(&tcp_hashinfo, sk, snum,
    				 inet6_csk_bind_conflict);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static void tcp_v6_hash(struct sock *sk)
    {
    	if (sk->sk_state != TCP_CLOSE) {
    
    		if (inet_csk(sk)->icsk_af_ops == &ipv6_mapped) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			tcp_prot.hash(sk);
    			return;
    		}
    		local_bh_disable();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		local_bh_enable();
    	}
    }
    
    static __inline__ u16 tcp_v6_check(struct tcphdr *th, int len,
    				   struct in6_addr *saddr, 
    				   struct in6_addr *daddr, 
    				   unsigned long base)
    {
    	return csum_ipv6_magic(saddr, daddr, len, IPPROTO_TCP, base);
    }
    
    static __u32 tcp_v6_init_sequence(struct sock *sk, struct sk_buff *skb)
    {
    	if (skb->protocol == htons(ETH_P_IPV6)) {
    		return secure_tcpv6_sequence_number(skb->nh.ipv6h->daddr.s6_addr32,
    						    skb->nh.ipv6h->saddr.s6_addr32,
    						    skb->h.th->dest,
    						    skb->h.th->source);
    	} else {
    		return secure_tcp_sequence_number(skb->nh.iph->daddr,
    						  skb->nh.iph->saddr,
    						  skb->h.th->dest,
    						  skb->h.th->source);
    	}
    }
    
    static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, 
    			  int addr_len)
    {
    	struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr;
    
     	struct inet_sock *inet = inet_sk(sk);
    	struct inet_connection_sock *icsk = inet_csk(sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ipv6_pinfo *np = inet6_sk(sk);
    	struct tcp_sock *tp = tcp_sk(sk);
    	struct in6_addr *saddr = NULL, *final_p = NULL, final;
    	struct flowi fl;
    	struct dst_entry *dst;
    	int addr_type;
    	int err;
    
    	if (addr_len < SIN6_LEN_RFC2133) 
    		return -EINVAL;
    
    	if (usin->sin6_family != AF_INET6) 
    		return(-EAFNOSUPPORT);
    
    	memset(&fl, 0, sizeof(fl));
    
    	if (np->sndflow) {
    		fl.fl6_flowlabel = usin->sin6_flowinfo&IPV6_FLOWINFO_MASK;
    		IP6_ECN_flow_init(fl.fl6_flowlabel);
    		if (fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) {
    			struct ip6_flowlabel *flowlabel;
    			flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
    			if (flowlabel == NULL)
    				return -EINVAL;
    			ipv6_addr_copy(&usin->sin6_addr, &flowlabel->dst);
    			fl6_sock_release(flowlabel);
    		}
    	}
    
    	/*
      	 *	connect() to INADDR_ANY means loopback (BSD'ism).
      	 */
      	
      	if(ipv6_addr_any(&usin->sin6_addr))
    		usin->sin6_addr.s6_addr[15] = 0x1; 
    
    	addr_type = ipv6_addr_type(&usin->sin6_addr);
    
    	if(addr_type & IPV6_ADDR_MULTICAST)
    		return -ENETUNREACH;
    
    	if (addr_type&IPV6_ADDR_LINKLOCAL) {
    		if (addr_len >= sizeof(struct sockaddr_in6) &&
    		    usin->sin6_scope_id) {
    			/* If interface is set while binding, indices
    			 * must coincide.
    			 */
    			if (sk->sk_bound_dev_if &&
    			    sk->sk_bound_dev_if != usin->sin6_scope_id)
    				return -EINVAL;
    
    			sk->sk_bound_dev_if = usin->sin6_scope_id;
    		}
    
    		/* Connect to link-local address requires an interface */
    		if (!sk->sk_bound_dev_if)
    			return -EINVAL;
    	}
    
    	if (tp->rx_opt.ts_recent_stamp &&
    	    !ipv6_addr_equal(&np->daddr, &usin->sin6_addr)) {
    		tp->rx_opt.ts_recent = 0;
    		tp->rx_opt.ts_recent_stamp = 0;
    		tp->write_seq = 0;
    	}
    
    	ipv6_addr_copy(&np->daddr, &usin->sin6_addr);
    	np->flow_label = fl.fl6_flowlabel;
    
    	/*
    	 *	TCP over IPv4
    	 */
    
    	if (addr_type == IPV6_ADDR_MAPPED) {
    
    		u32 exthdrlen = icsk->icsk_ext_hdr_len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		struct sockaddr_in sin;
    
    		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];
    
    
    		icsk->icsk_af_ops = &ipv6_mapped;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		sk->sk_backlog_rcv = tcp_v4_do_rcv;
    
    		err = tcp_v4_connect(sk, (struct sockaddr *)&sin, sizeof(sin));
    
    		if (err) {
    
    			icsk->icsk_ext_hdr_len = exthdrlen;
    			icsk->icsk_af_ops = &ipv6_specific;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			sk->sk_backlog_rcv = tcp_v6_do_rcv;
    			goto failure;
    		} else {
    			ipv6_addr_set(&np->saddr, 0, 0, htonl(0x0000FFFF),
    				      inet->saddr);
    			ipv6_addr_set(&np->rcv_saddr, 0, 0, htonl(0x0000FFFF),
    				      inet->rcv_saddr);
    		}
    
    		return err;
    	}
    
    	if (!ipv6_addr_any(&np->rcv_saddr))
    		saddr = &np->rcv_saddr;
    
    	fl.proto = IPPROTO_TCP;
    	ipv6_addr_copy(&fl.fl6_dst, &np->daddr);
    	ipv6_addr_copy(&fl.fl6_src,
    		       (saddr ? saddr : &np->saddr));
    	fl.oif = sk->sk_bound_dev_if;
    	fl.fl_ip_dport = usin->sin6_port;
    	fl.fl_ip_sport = inet->sport;
    
    	if (np->opt && np->opt->srcrt) {
    		struct rt0_hdr *rt0 = (struct rt0_hdr *)np->opt->srcrt;
    		ipv6_addr_copy(&final, &fl.fl6_dst);
    		ipv6_addr_copy(&fl.fl6_dst, rt0->addr);
    		final_p = &final;
    	}
    
    	err = ip6_dst_lookup(sk, &dst, &fl);
    	if (err)
    		goto failure;
    	if (final_p)
    		ipv6_addr_copy(&fl.fl6_dst, final_p);
    
    
    	if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto failure;
    
    	if (saddr == NULL) {
    		saddr = &fl.fl6_src;
    		ipv6_addr_copy(&np->rcv_saddr, saddr);
    	}
    
    	/* set the source address */
    	ipv6_addr_copy(&np->saddr, saddr);
    	inet->rcv_saddr = LOOPBACK4_IPV6;
    
    	ip6_dst_store(sk, dst, NULL);
    	sk->sk_route_caps = dst->dev->features &
    		~(NETIF_F_IP_CSUM | NETIF_F_TSO);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (np->opt)
    
    		icsk->icsk_ext_hdr_len = (np->opt->opt_flen +
    					  np->opt->opt_nflen);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	tp->rx_opt.mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr);
    
    	inet->dport = usin->sin6_port;
    
    	tcp_set_state(sk, TCP_SYN_SENT);
    
    	err = inet6_hash_connect(&tcp_death_row, sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (err)
    		goto late_failure;
    
    	if (!tp->write_seq)
    		tp->write_seq = secure_tcpv6_sequence_number(np->saddr.s6_addr32,
    							     np->daddr.s6_addr32,
    							     inet->sport,
    							     inet->dport);
    
    	err = tcp_connect(sk);
    	if (err)
    		goto late_failure;
    
    	return 0;
    
    late_failure:
    	tcp_set_state(sk, TCP_CLOSE);
    	__sk_dst_reset(sk);
    failure:
    	inet->dport = 0;
    	sk->sk_route_caps = 0;
    	return err;
    }
    
    static void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
    		int type, int code, int offset, __u32 info)
    {
    	struct ipv6hdr *hdr = (struct ipv6hdr*)skb->data;
    
    	const struct tcphdr *th = (struct tcphdr *)(skb->data+offset);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ipv6_pinfo *np;
    	struct sock *sk;
    	int err;
    	struct tcp_sock *tp; 
    	__u32 seq;
    
    
    	sk = inet6_lookup(&tcp_hashinfo, &hdr->daddr, th->dest, &hdr->saddr,
    			  th->source, skb->dev->ifindex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (sk == NULL) {
    		ICMP6_INC_STATS_BH(__in6_dev_get(skb->dev), ICMP6_MIB_INERRORS);
    		return;
    	}
    
    	if (sk->sk_state == TCP_TIME_WAIT) {
    
    		inet_twsk_put((struct inet_timewait_sock *)sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return;
    	}
    
    	bh_lock_sock(sk);
    	if (sock_owned_by_user(sk))
    		NET_INC_STATS_BH(LINUX_MIB_LOCKDROPPEDICMPS);
    
    	if (sk->sk_state == TCP_CLOSE)
    		goto out;
    
    	tp = tcp_sk(sk);
    	seq = ntohl(th->seq); 
    	if (sk->sk_state != TCP_LISTEN &&
    	    !between(seq, tp->snd_una, tp->snd_nxt)) {
    		NET_INC_STATS_BH(LINUX_MIB_OUTOFWINDOWICMPS);
    		goto out;
    	}
    
    	np = inet6_sk(sk);
    
    	if (type == ICMPV6_PKT_TOOBIG) {
    		struct dst_entry *dst = NULL;
    
    		if (sock_owned_by_user(sk))
    			goto out;
    		if ((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE))
    			goto out;
    
    		/* icmp should have updated the destination cache entry */
    		dst = __sk_dst_check(sk, np->dst_cookie);
    
    		if (dst == NULL) {
    			struct inet_sock *inet = inet_sk(sk);
    			struct flowi fl;
    
    			/* BUGGG_FUTURE: Again, it is not clear how
    			   to handle rthdr case. Ignore this complexity
    			   for now.
    			 */
    			memset(&fl, 0, sizeof(fl));
    			fl.proto = IPPROTO_TCP;
    			ipv6_addr_copy(&fl.fl6_dst, &np->daddr);
    			ipv6_addr_copy(&fl.fl6_src, &np->saddr);
    			fl.oif = sk->sk_bound_dev_if;
    			fl.fl_ip_dport = inet->dport;
    			fl.fl_ip_sport = inet->sport;
    
    			if ((err = ip6_dst_lookup(sk, &dst, &fl))) {
    				sk->sk_err_soft = -err;
    				goto out;
    			}
    
    			if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0) {
    				sk->sk_err_soft = -err;
    				goto out;
    			}
    
    		} else
    			dst_hold(dst);
    
    
    		if (inet_csk(sk)->icsk_pmtu_cookie > dst_mtu(dst)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			tcp_sync_mss(sk, dst_mtu(dst));
    			tcp_simple_retransmit(sk);
    		} /* else let the usual retransmit timer handle it */
    		dst_release(dst);
    		goto out;
    	}
    
    	icmpv6_err_convert(type, code, &err);
    
    
    	/* Might be for an request_sock */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	switch (sk->sk_state) {
    
    		struct request_sock *req, **prev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	case TCP_LISTEN:
    		if (sock_owned_by_user(sk))
    			goto out;
    
    
    		req = inet6_csk_search_req(sk, &prev, th->dest, &hdr->daddr,
    					   &hdr->saddr, inet6_iif(skb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (!req)
    			goto out;
    
    		/* ICMPs are not backlogged, hence we cannot get
    		 * an established socket here.
    		 */
    		BUG_TRAP(req->sk == NULL);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			NET_INC_STATS_BH(LINUX_MIB_OUTOFWINDOWICMPS);
    			goto out;
    		}
    
    
    		inet_csk_reqsk_queue_drop(sk, req, prev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto out;
    
    	case TCP_SYN_SENT:
    	case TCP_SYN_RECV:  /* Cannot happen.
    			       It can, it SYNs are crossed. --ANK */ 
    		if (!sock_owned_by_user(sk)) {
    			TCP_INC_STATS_BH(TCP_MIB_ATTEMPTFAILS);
    			sk->sk_err = err;
    			sk->sk_error_report(sk);		/* Wake people up to see the error (see connect in sock.c) */
    
    			tcp_done(sk);
    		} else
    			sk->sk_err_soft = err;
    		goto out;
    	}
    
    	if (!sock_owned_by_user(sk) && np->recverr) {
    		sk->sk_err = err;
    		sk->sk_error_report(sk);
    	} else
    		sk->sk_err_soft = err;
    
    out:
    	bh_unlock_sock(sk);
    	sock_put(sk);
    }
    
    
    
    static int tcp_v6_send_synack(struct sock *sk, struct request_sock *req,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			      struct dst_entry *dst)
    {
    
    	struct inet6_request_sock *treq = inet6_rsk(req);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ipv6_pinfo *np = inet6_sk(sk);
    	struct sk_buff * skb;
    	struct ipv6_txoptions *opt = NULL;
    	struct in6_addr * final_p = NULL, final;
    	struct flowi fl;
    	int err = -1;
    
    	memset(&fl, 0, sizeof(fl));
    	fl.proto = IPPROTO_TCP;
    
    	ipv6_addr_copy(&fl.fl6_dst, &treq->rmt_addr);
    	ipv6_addr_copy(&fl.fl6_src, &treq->loc_addr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fl.fl6_flowlabel = 0;
    
    	fl.oif = treq->iif;
    	fl.fl_ip_dport = inet_rsk(req)->rmt_port;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fl.fl_ip_sport = inet_sk(sk)->sport;
    
    	if (dst == NULL) {
    		opt = np->opt;
    		if (opt == NULL &&
    
    		    treq->pktopts) {
    			struct sk_buff *pktopts = treq->pktopts;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			struct inet6_skb_parm *rxopt = IP6CB(pktopts);
    			if (rxopt->srcrt)
    				opt = ipv6_invert_rthdr(sk, (struct ipv6_rt_hdr*)(pktopts->nh.raw + rxopt->srcrt));
    		}
    
    		if (opt && opt->srcrt) {
    			struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
    			ipv6_addr_copy(&final, &fl.fl6_dst);
    			ipv6_addr_copy(&fl.fl6_dst, rt0->addr);
    			final_p = &final;
    		}
    
    		err = ip6_dst_lookup(sk, &dst, &fl);
    		if (err)
    			goto done;
    		if (final_p)
    			ipv6_addr_copy(&fl.fl6_dst, final_p);
    		if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
    			goto done;
    	}
    
    	skb = tcp_make_synack(sk, dst, req);
    	if (skb) {
    		struct tcphdr *th = skb->h.th;
    
    		th->check = tcp_v6_check(th, skb->len,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					 csum_partial((char *)th, skb->len, skb->csum));
    
    
    		ipv6_addr_copy(&fl.fl6_dst, &treq->rmt_addr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		err = ip6_xmit(sk, skb, &fl, opt, 0);
    		if (err == NET_XMIT_CN)
    			err = 0;
    	}
    
    done:
            if (opt && opt != np->opt)
    		sock_kfree_s(sk, opt, opt->tot_len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    
    static void tcp_v6_reqsk_destructor(struct request_sock *req)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	if (inet6_rsk(req)->pktopts)
    		kfree_skb(inet6_rsk(req)->pktopts);
    
    static struct request_sock_ops tcp6_request_sock_ops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.family		=	AF_INET6,
    
    	.obj_size	=	sizeof(struct tcp6_request_sock),
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.rtx_syn_ack	=	tcp_v6_send_synack,
    
    	.send_ack	=	tcp_v6_reqsk_send_ack,
    	.destructor	=	tcp_v6_reqsk_destructor,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.send_reset	=	tcp_v6_send_reset
    };
    
    
    static struct timewait_sock_ops tcp6_timewait_sock_ops = {
    	.twsk_obj_size	= sizeof(struct tcp6_timewait_sock),
    	.twsk_unique	= tcp_twsk_unique,
    };
    
    
    static void tcp_v6_send_check(struct sock *sk, int len, struct sk_buff *skb)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct ipv6_pinfo *np = inet6_sk(sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (skb->ip_summed == CHECKSUM_HW) {
    		th->check = ~csum_ipv6_magic(&np->saddr, &np->daddr, len, IPPROTO_TCP,  0);
    		skb->csum = offsetof(struct tcphdr, check);
    	} else {
    		th->check = csum_ipv6_magic(&np->saddr, &np->daddr, len, IPPROTO_TCP, 
    					    csum_partial((char *)th, th->doff<<2, 
    							 skb->csum));
    	}
    }
    
    
    static void tcp_v6_send_reset(struct sk_buff *skb)
    {
    	struct tcphdr *th = skb->h.th, *t1; 
    	struct sk_buff *buff;
    	struct flowi fl;
    
    	if (th->rst)
    		return;
    
    	if (!ipv6_unicast_destination(skb))
    		return; 
    
    	/*
    	 * We need to grab some memory, and put together an RST,
    	 * and then put it into the queue to be sent.
    	 */
    
    	buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + sizeof(struct tcphdr),
    			 GFP_ATOMIC);
    	if (buff == NULL) 
    	  	return;
    
    	skb_reserve(buff, MAX_HEADER + sizeof(struct ipv6hdr) + sizeof(struct tcphdr));
    
    	t1 = (struct tcphdr *) skb_push(buff,sizeof(struct tcphdr));
    
    	/* Swap the send and the receive. */
    	memset(t1, 0, sizeof(*t1));
    	t1->dest = th->source;
    	t1->source = th->dest;
    	t1->doff = sizeof(*t1)/4;
    	t1->rst = 1;
      
    	if(th->ack) {
    	  	t1->seq = th->ack_seq;
    	} else {
    		t1->ack = 1;
    		t1->ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin
    				    + skb->len - (th->doff<<2));
    	}
    
    	buff->csum = csum_partial((char *)t1, sizeof(*t1), 0);
    
    	memset(&fl, 0, sizeof(fl));
    	ipv6_addr_copy(&fl.fl6_dst, &skb->nh.ipv6h->saddr);
    	ipv6_addr_copy(&fl.fl6_src, &skb->nh.ipv6h->daddr);
    
    	t1->check = csum_ipv6_magic(&fl.fl6_src, &fl.fl6_dst,
    				    sizeof(*t1), IPPROTO_TCP,
    				    buff->csum);
    
    	fl.proto = IPPROTO_TCP;
    
    	fl.oif = inet6_iif(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fl.fl_ip_dport = t1->dest;
    	fl.fl_ip_sport = t1->source;
    
    	/* sk = NULL, but it is safe for now. RST socket required. */
    	if (!ip6_dst_lookup(NULL, &buff->dst, &fl)) {
    
    
    		if (xfrm_lookup(&buff->dst, &fl, NULL, 0) >= 0) {
    
    			ip6_xmit(tcp6_socket->sk, buff, &fl, NULL, 0);
    
    			TCP_INC_STATS_BH(TCP_MIB_OUTSEGS);
    			TCP_INC_STATS_BH(TCP_MIB_OUTRSTS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	kfree_skb(buff);
    }
    
    static void tcp_v6_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32 ts)
    {
    	struct tcphdr *th = skb->h.th, *t1;
    	struct sk_buff *buff;
    	struct flowi fl;
    	int tot_len = sizeof(struct tcphdr);
    
    	if (ts)
    		tot_len += 3*4;
    
    	buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + tot_len,
    			 GFP_ATOMIC);
    	if (buff == NULL)
    		return;
    
    	skb_reserve(buff, MAX_HEADER + sizeof(struct ipv6hdr) + tot_len);
    
    	t1 = (struct tcphdr *) skb_push(buff,tot_len);
    
    	/* Swap the send and the receive. */
    	memset(t1, 0, sizeof(*t1));
    	t1->dest = th->source;
    	t1->source = th->dest;
    	t1->doff = tot_len/4;
    	t1->seq = htonl(seq);
    	t1->ack_seq = htonl(ack);
    	t1->ack = 1;
    	t1->window = htons(win);
    	
    	if (ts) {
    		u32 *ptr = (u32*)(t1 + 1);
    		*ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
    			       (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP);
    		*ptr++ = htonl(tcp_time_stamp);
    		*ptr = htonl(ts);
    	}
    
    	buff->csum = csum_partial((char *)t1, tot_len, 0);
    
    	memset(&fl, 0, sizeof(fl));
    	ipv6_addr_copy(&fl.fl6_dst, &skb->nh.ipv6h->saddr);
    	ipv6_addr_copy(&fl.fl6_src, &skb->nh.ipv6h->daddr);
    
    	t1->check = csum_ipv6_magic(&fl.fl6_src, &fl.fl6_dst,
    				    tot_len, IPPROTO_TCP,
    				    buff->csum);
    
    	fl.proto = IPPROTO_TCP;
    
    	fl.oif = inet6_iif(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fl.fl_ip_dport = t1->dest;
    	fl.fl_ip_sport = t1->source;
    
    	if (!ip6_dst_lookup(NULL, &buff->dst, &fl)) {
    
    		if (xfrm_lookup(&buff->dst, &fl, NULL, 0) >= 0) {
    
    			ip6_xmit(tcp6_socket->sk, buff, &fl, NULL, 0);
    
    			TCP_INC_STATS_BH(TCP_MIB_OUTSEGS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	kfree_skb(buff);
    }
    
    static void tcp_v6_timewait_ack(struct sock *sk, struct sk_buff *skb)
    {
    
    	struct inet_timewait_sock *tw = inet_twsk(sk);
    	const struct tcp_timewait_sock *tcptw = tcp_twsk(sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	tcp_v6_send_ack(skb, tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt,
    			tcptw->tw_rcv_wnd >> tw->tw_rcv_wscale,
    			tcptw->tw_ts_recent);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void tcp_v6_reqsk_send_ack(struct sk_buff *skb, struct request_sock *req)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	tcp_v6_send_ack(skb, tcp_rsk(req)->snt_isn + 1, tcp_rsk(req)->rcv_isn + 1, req->rcv_wnd, req->ts_recent);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    
    static struct sock *tcp_v6_hnd_req(struct sock *sk,struct sk_buff *skb)
    {
    
    	struct request_sock *req, **prev;
    
    	const struct tcphdr *th = skb->h.th;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct sock *nsk;
    
    	/* Find possible connection requests. */
    
    	req = inet6_csk_search_req(sk, &prev, th->source,
    				   &skb->nh.ipv6h->saddr,
    				   &skb->nh.ipv6h->daddr, inet6_iif(skb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (req)
    		return tcp_check_req(sk, skb, req, prev);
    
    
    	nsk = __inet6_lookup_established(&tcp_hashinfo, &skb->nh.ipv6h->saddr,
    					 th->source, &skb->nh.ipv6h->daddr,
    					 ntohs(th->dest), inet6_iif(skb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (nsk) {
    		if (nsk->sk_state != TCP_TIME_WAIT) {
    			bh_lock_sock(nsk);
    			return nsk;
    		}
    
    		inet_twsk_put((struct inet_timewait_sock *)nsk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return NULL;
    	}
    
    #if 0 /*def CONFIG_SYN_COOKIES*/
    	if (!th->rst && !th->syn && th->ack)
    		sk = cookie_v6_check(sk, skb, &(IPCB(skb)->opt));
    #endif
    	return sk;
    }
    
    /* FIXME: this is substantially similar to the ipv4 code.
     * Can some kind of merge be done? -- erics
     */
    static int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb)
    {
    
    	struct inet6_request_sock *treq;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ipv6_pinfo *np = inet6_sk(sk);
    	struct tcp_options_received tmp_opt;
    	struct tcp_sock *tp = tcp_sk(sk);
    
    	struct request_sock *req = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	__u32 isn = TCP_SKB_CB(skb)->when;
    
    	if (skb->protocol == htons(ETH_P_IP))
    		return tcp_v4_conn_request(sk, skb);
    
    	if (!ipv6_unicast_destination(skb))
    		goto drop; 
    
    	/*
    	 *	There are no SYN attacks on IPv6, yet...	
    	 */
    
    	if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (net_ratelimit())
    			printk(KERN_INFO "TCPv6: dropping request, synflood is possible\n");
    		goto drop;		
    	}
    
    
    	if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto drop;
    
    
    	req = inet6_reqsk_alloc(&tcp6_request_sock_ops);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (req == NULL)
    		goto drop;
    
    	tcp_clear_options(&tmp_opt);
    	tmp_opt.mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr);
    	tmp_opt.user_mss = tp->rx_opt.user_mss;
    
    	tcp_parse_options(skb, &tmp_opt, 0);
    
    	tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;
    	tcp_openreq_init(req, &tmp_opt, skb);
    
    
    	treq = inet6_rsk(req);
    
    	ipv6_addr_copy(&treq->rmt_addr, &skb->nh.ipv6h->saddr);
    	ipv6_addr_copy(&treq->loc_addr, &skb->nh.ipv6h->daddr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	TCP_ECN_create_request(req, skb->h.th);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (ipv6_opt_accepted(sk, skb) ||
    
    	    np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo ||
    	    np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		atomic_inc(&skb->users);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* So that link locals have meaning */
    	if (!sk->sk_bound_dev_if &&
    
    	    ipv6_addr_type(&treq->rmt_addr) & IPV6_ADDR_LINKLOCAL)
    
    		treq->iif = inet6_iif(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (isn == 0) 
    		isn = tcp_v6_init_sequence(sk,skb);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (tcp_v6_send_synack(sk, req, NULL))
    		goto drop;
    
    
    	inet6_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    
    drop:
    	if (req)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	TCP_INC_STATS_BH(TCP_MIB_ATTEMPTFAILS);
    	return 0; /* don't send reset */
    }
    
    static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
    
    					  struct request_sock *req,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					  struct dst_entry *dst)
    {
    
    	struct inet6_request_sock *treq = inet6_rsk(req);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
    	struct tcp6_sock *newtcp6sk;
    	struct inet_sock *newinet;
    	struct tcp_sock *newtp;
    	struct sock *newsk;
    	struct ipv6_txoptions *opt;
    
    	if (skb->protocol == htons(ETH_P_IP)) {
    		/*
    		 *	v6 mapped
    		 */
    
    		newsk = tcp_v4_syn_recv_sock(sk, skb, req, dst);
    
    		if (newsk == NULL) 
    			return NULL;
    
    		newtcp6sk = (struct tcp6_sock *)newsk;
    		inet_sk(newsk)->pinet6 = &newtcp6sk->inet6;
    
    		newinet = inet_sk(newsk);
    		newnp = inet6_sk(newsk);
    		newtp = tcp_sk(newsk);
    
    		memcpy(newnp, np, sizeof(struct ipv6_pinfo));
    
    		ipv6_addr_set(&newnp->daddr, 0, 0, htonl(0x0000FFFF),
    			      newinet->daddr);
    
    		ipv6_addr_set(&newnp->saddr, 0, 0, htonl(0x0000FFFF),
    			      newinet->saddr);
    
    		ipv6_addr_copy(&newnp->rcv_saddr, &newnp->saddr);
    
    
    		inet_csk(newsk)->icsk_af_ops = &ipv6_mapped;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		newsk->sk_backlog_rcv = tcp_v4_do_rcv;
    		newnp->pktoptions  = NULL;
    		newnp->opt	   = NULL;
    
    		newnp->mcast_oif   = inet6_iif(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		newnp->mcast_hops  = skb->nh.ipv6h->hop_limit;
    
    
    		/*
    		 * No need to charge this sock to the relevant IPv6 refcnt debug socks count
    		 * here, tcp_create_openreq_child now does this for us, see the comment in
    		 * that function for the gory details. -acme
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 */
    
    		/* It is tricky place. Until this moment IPv4 tcp
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		   Sync it now.
    		 */
    
    		tcp_sync_mss(newsk, inet_csk(newsk)->icsk_pmtu_cookie);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		return newsk;
    	}
    
    	opt = np->opt;
    
    	if (sk_acceptq_is_full(sk))
    		goto out_overflow;
    
    
    	    opt == NULL && treq->pktopts) {
    		struct inet6_skb_parm *rxopt = IP6CB(treq->pktopts);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (rxopt->srcrt)
    
    			opt = ipv6_invert_rthdr(sk, (struct ipv6_rt_hdr *)(treq->pktopts->nh.raw + rxopt->srcrt));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	if (dst == NULL) {
    		struct in6_addr *final_p = NULL, final;
    		struct flowi fl;
    
    		memset(&fl, 0, sizeof(fl));
    		fl.proto = IPPROTO_TCP;
    
    		ipv6_addr_copy(&fl.fl6_dst, &treq->rmt_addr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (opt && opt->srcrt) {
    			struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
    			ipv6_addr_copy(&final, &fl.fl6_dst);
    			ipv6_addr_copy(&fl.fl6_dst, rt0->addr);
    			final_p = &final;
    		}
    
    		ipv6_addr_copy(&fl.fl6_src, &treq->loc_addr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		fl.oif = sk->sk_bound_dev_if;
    
    		fl.fl_ip_dport = inet_rsk(req)->rmt_port;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		fl.fl_ip_sport = inet_sk(sk)->sport;
    
    		if (ip6_dst_lookup(sk, &dst, &fl))
    			goto out;
    
    		if (final_p)
    			ipv6_addr_copy(&fl.fl6_dst, final_p);
    
    		if ((xfrm_lookup(&dst, &fl, sk, 0)) < 0)
    			goto out;
    	} 
    
    	newsk = tcp_create_openreq_child(sk, req, skb);
    	if (newsk == NULL)
    		goto out;
    
    
    	/*
    	 * No need to charge this sock to the relevant IPv6 refcnt debug socks
    	 * count here, tcp_create_openreq_child now does this for us, see the
    	 * comment in that function for the gory details. -acme
    	 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	ip6_dst_store(newsk, dst, NULL);
    	newsk->sk_route_caps = dst->dev->features &
    		~(NETIF_F_IP_CSUM | NETIF_F_TSO);
    
    	newtcp6sk = (struct tcp6_sock *)newsk;
    	inet_sk(newsk)->pinet6 = &newtcp6sk->inet6;
    
    	newtp = tcp_sk(newsk);
    	newinet = inet_sk(newsk);
    	newnp = inet6_sk(newsk);
    
    	memcpy(newnp, np, sizeof(struct ipv6_pinfo));
    
    
    	ipv6_addr_copy(&newnp->daddr, &treq->rmt_addr);
    	ipv6_addr_copy(&newnp->saddr, &treq->loc_addr);
    	ipv6_addr_copy(&newnp->rcv_saddr, &treq->loc_addr);
    	newsk->sk_bound_dev_if = treq->iif;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Now IPv6 options... 
    
    	   First: no IPv4 options.
    	 */
    	newinet->opt = NULL;
    
    	/* Clone RX bits */
    	newnp->rxopt.all = np->rxopt.all;
    
    	/* Clone pktoptions received with SYN */
    	newnp->pktoptions = NULL;
    
    	if (treq->pktopts != NULL) {
    		newnp->pktoptions = skb_clone(treq->pktopts, GFP_ATOMIC);
    		kfree_skb(treq->pktopts);
    		treq->pktopts = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (newnp->pktoptions)
    			skb_set_owner_r(newnp->pktoptions, newsk);
    	}
    	newnp->opt	  = NULL;
    
    	newnp->mcast_oif  = inet6_iif(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	newnp->mcast_hops = skb->nh.ipv6h->hop_limit;
    
    	/* Clone native IPv6 options from listening socket (if any)
    
    	   Yes, keeping reference count would be much more clever,
    	   but we make one more one thing there: reattach optmem
    	   to newsk.
    	 */
    	if (opt) {
    		newnp->opt = ipv6_dup_options(newsk, opt);
    		if (opt != np->opt)
    			sock_kfree_s(sk, opt, opt->tot_len);
    	}
    
    
    	inet_csk(newsk)->icsk_ext_hdr_len = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (newnp->opt)
    
    		inet_csk(newsk)->icsk_ext_hdr_len = (newnp->opt->opt_nflen +
    						     newnp->opt->opt_flen);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    John Heffner's avatar
    John Heffner committed
    	tcp_mtup_init(newsk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	tcp_sync_mss(newsk, dst_mtu(dst));
    	newtp->advmss = dst_metric(dst, RTAX_ADVMSS);
    	tcp_initialize_rcv_mss(newsk);
    
    	newinet->daddr = newinet->saddr = newinet->rcv_saddr = LOOPBACK4_IPV6;
    
    
    	__inet6_hash(&tcp_hashinfo, newsk);
    
    	inet_inherit_port(&tcp_hashinfo, sk, newsk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return newsk;