Skip to content
Snippets Groups Projects
ip6_output.c 28.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    					get_page(page);
    					skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);
    					frag = &skb_shinfo(skb)->frags[i];
    				}
    			} else if(i < MAX_SKB_FRAGS) {
    				if (copy > PAGE_SIZE)
    					copy = PAGE_SIZE;
    				page = alloc_pages(sk->sk_allocation, 0);
    				if (page == NULL) {
    					err = -ENOMEM;
    					goto error;
    				}
    				sk->sk_sndmsg_page = page;
    				sk->sk_sndmsg_off = 0;
    
    				skb_fill_page_desc(skb, i, page, 0, 0);
    				frag = &skb_shinfo(skb)->frags[i];
    				skb->truesize += PAGE_SIZE;
    				atomic_add(PAGE_SIZE, &sk->sk_wmem_alloc);
    			} else {
    				err = -EMSGSIZE;
    				goto error;
    			}
    			if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) {
    				err = -EFAULT;
    				goto error;
    			}
    			sk->sk_sndmsg_off += copy;
    			frag->size += copy;
    			skb->len += copy;
    			skb->data_len += copy;
    		}
    		offset += copy;
    		length -= copy;
    	}
    	return 0;
    error:
    	inet->cork.length -= length;
    	IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS);
    	return err;
    }
    
    int ip6_push_pending_frames(struct sock *sk)
    {
    	struct sk_buff *skb, *tmp_skb;
    	struct sk_buff **tail_skb;
    	struct in6_addr final_dst_buf, *final_dst = &final_dst_buf;
    	struct inet_sock *inet = inet_sk(sk);
    	struct ipv6_pinfo *np = inet6_sk(sk);
    	struct ipv6hdr *hdr;
    	struct ipv6_txoptions *opt = np->cork.opt;
    	struct rt6_info *rt = np->cork.rt;
    	struct flowi *fl = &inet->cork.fl;
    	unsigned char proto = fl->proto;
    	int err = 0;
    
    	if ((skb = __skb_dequeue(&sk->sk_write_queue)) == NULL)
    		goto out;
    	tail_skb = &(skb_shinfo(skb)->frag_list);
    
    	/* move skb->data to ip header from ext header */
    	if (skb->data < skb->nh.raw)
    		__skb_pull(skb, skb->nh.raw - skb->data);
    	while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
    		__skb_pull(tmp_skb, skb->h.raw - skb->nh.raw);
    		*tail_skb = tmp_skb;
    		tail_skb = &(tmp_skb->next);
    		skb->len += tmp_skb->len;
    		skb->data_len += tmp_skb->len;
    		skb->truesize += tmp_skb->truesize;
    		__sock_put(tmp_skb->sk);
    		tmp_skb->destructor = NULL;
    		tmp_skb->sk = NULL;
    	}
    
    	ipv6_addr_copy(final_dst, &fl->fl6_dst);
    	__skb_pull(skb, skb->h.raw - skb->nh.raw);
    	if (opt && opt->opt_flen)
    		ipv6_push_frag_opts(skb, opt, &proto);
    	if (opt && opt->opt_nflen)
    		ipv6_push_nfrag_opts(skb, opt, &proto, &final_dst);
    
    	skb->nh.ipv6h = hdr = (struct ipv6hdr*) skb_push(skb, sizeof(struct ipv6hdr));
    	
    	*(u32*)hdr = fl->fl6_flowlabel | htonl(0x60000000);
    
    	if (skb->len <= sizeof(struct ipv6hdr) + IPV6_MAXPLEN)
    		hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
    	else
    		hdr->payload_len = 0;
    	hdr->hop_limit = np->cork.hop_limit;
    	hdr->nexthdr = proto;
    	ipv6_addr_copy(&hdr->saddr, &fl->fl6_src);
    	ipv6_addr_copy(&hdr->daddr, final_dst);
    
    	skb->dst = dst_clone(&rt->u.dst);
    	IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS);	
    	err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dst->dev, dst_output);
    	if (err) {
    		if (err > 0)
    
    			err = np->recverr ? net_xmit_errno(err) : 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (err)
    			goto error;
    	}
    
    out:
    	inet->cork.flags &= ~IPCORK_OPT;
    	if (np->cork.opt) {
    		kfree(np->cork.opt);
    		np->cork.opt = NULL;
    	}
    	if (np->cork.rt) {
    		dst_release(&np->cork.rt->u.dst);
    		np->cork.rt = NULL;
    		inet->cork.flags &= ~IPCORK_ALLFRAG;
    	}
    	memset(&inet->cork.fl, 0, sizeof(inet->cork.fl));
    	return err;
    error:
    	goto out;
    }
    
    void ip6_flush_pending_frames(struct sock *sk)
    {
    	struct inet_sock *inet = inet_sk(sk);
    	struct ipv6_pinfo *np = inet6_sk(sk);
    	struct sk_buff *skb;
    
    	while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL) {
    		IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS);
    		kfree_skb(skb);
    	}
    
    	inet->cork.flags &= ~IPCORK_OPT;
    
    	if (np->cork.opt) {
    		kfree(np->cork.opt);
    		np->cork.opt = NULL;
    	}
    	if (np->cork.rt) {
    		dst_release(&np->cork.rt->u.dst);
    		np->cork.rt = NULL;
    		inet->cork.flags &= ~IPCORK_ALLFRAG;
    	}
    	memset(&inet->cork.fl, 0, sizeof(inet->cork.fl));
    }