📄 ip6_output.c
字号:
/* * IPv6 output functions * Linux INET6 implementation * * Authors: * Pedro Roque <roque@di.fc.ul.pt> * * $Id: ip6_output.c,v 1.17 1999/04/22 10:07:42 davem Exp $ * * Based on linux/net/ipv4/ip_output.c * * 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. * * Changes: * A.N.Kuznetsov : airthmetics in fragmentation. * extension headers are implemented. * route changes now work. * ip6_forward does not confuse sniffers. * etc. * */#include <linux/errno.h>#include <linux/types.h>#include <linux/socket.h>#include <linux/net.h>#include <linux/netdevice.h>#include <linux/if_arp.h>#include <linux/in6.h>#include <linux/route.h>#include <net/sock.h>#include <net/snmp.h>#include <net/ipv6.h>#include <net/ndisc.h>#include <net/protocol.h>#include <net/ip6_route.h>#include <net/addrconf.h>#include <net/rawv6.h>#include <net/icmp.h>static u32 ipv6_fragmentation_id = 1;int ip6_output(struct sk_buff *skb){ struct dst_entry *dst = skb->dst; struct device *dev = dst->dev; struct hh_cache *hh = dst->hh; skb->protocol = __constant_htons(ETH_P_IPV6); skb->dev = dev; if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr)) { if (!(dev->flags&IFF_LOOPBACK) && (skb->sk == NULL || skb->sk->net_pinfo.af_inet6.mc_loop) && ipv6_chk_mcast_addr(dev, &skb->nh.ipv6h->daddr)) { /* Do not check for IFF_ALLMULTI; multicast routing is not supported in any case. */ dev_loopback_xmit(skb); if (skb->nh.ipv6h->hop_limit == 0) { kfree_skb(skb); return 0; } } ipv6_statistics.Ip6OutMcastPkts++; } if (hh) {#ifdef __alpha__ /* Alpha has disguisting memcpy. Help it. */ u64 *aligned_hdr = (u64*)(skb->data - 16); u64 *aligned_hdr0 = hh->hh_data; read_lock_irq(&hh->hh_lock); aligned_hdr[0] = aligned_hdr0[0]; aligned_hdr[1] = aligned_hdr0[1];#else read_lock_irq(&hh->hh_lock); memcpy(skb->data - 16, hh->hh_data, 16);#endif read_unlock_irq(&hh->hh_lock); skb_push(skb, dev->hard_header_len); return hh->hh_output(skb); } else if (dst->neighbour) return dst->neighbour->output(skb); kfree_skb(skb); return -EINVAL;}/* * xmit an sk_buff (used by TCP) */int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl, struct ipv6_txoptions *opt){ struct ipv6_pinfo * np = sk ? &sk->net_pinfo.af_inet6 : NULL; struct in6_addr *first_hop = fl->nl_u.ip6_u.daddr; struct dst_entry *dst = skb->dst; struct ipv6hdr *hdr; u8 proto = fl->proto; int seg_len = skb->len; int hlimit; if (opt) { int head_room; /* First: exthdrs may take lots of space (~8K for now) MAX_HEADER is not enough. */ head_room = opt->opt_nflen + opt->opt_flen; seg_len += head_room; head_room += sizeof(struct ipv6hdr) + ((dst->dev->hard_header_len + 15)&~15); if (skb_headroom(skb) < head_room) { struct sk_buff *skb2 = skb_realloc_headroom(skb, head_room); kfree(skb); skb = skb2; if (skb == NULL) return -ENOBUFS; if (sk) skb_set_owner_w(skb, sk); } if (opt->opt_flen) ipv6_push_frag_opts(skb, opt, &proto); if (opt->opt_nflen) ipv6_push_nfrag_opts(skb, opt, &proto, &first_hop); } hdr = skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, sizeof(struct ipv6hdr)); /* * Fill in the IPv6 header */ *(u32*)hdr = __constant_htonl(0x60000000) | fl->fl6_flowlabel; hlimit = -1; if (np) hlimit = np->hop_limit; if (hlimit < 0) hlimit = ((struct rt6_info*)dst)->rt6i_hoplimit; hdr->payload_len = htons(seg_len); hdr->nexthdr = proto; hdr->hop_limit = hlimit; ipv6_addr_copy(&hdr->saddr, fl->nl_u.ip6_u.saddr); ipv6_addr_copy(&hdr->daddr, first_hop); if (skb->len <= dst->pmtu) { ipv6_statistics.Ip6OutRequests++; dst->output(skb); return 0; } printk(KERN_DEBUG "IPv6: sending pkt_too_big to self\n"); start_bh_atomic(); icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, dst->pmtu, skb->dev); end_bh_atomic(); kfree_skb(skb); return -EMSGSIZE;}/* * To avoid extra problems ND packets are send through this * routine. It's code duplication but I really want to avoid * extra checks since ipv6_build_header is used by TCP (which * is for us performace critical) */int ip6_nd_hdr(struct sock *sk, struct sk_buff *skb, struct device *dev, struct in6_addr *saddr, struct in6_addr *daddr, int proto, int len){ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; struct ipv6hdr *hdr; int totlen; skb->protocol = __constant_htons(ETH_P_IPV6); skb->dev = dev; totlen = len + sizeof(struct ipv6hdr); hdr = (struct ipv6hdr *) skb_put(skb, sizeof(struct ipv6hdr)); skb->nh.ipv6h = hdr; *(u32*)hdr = htonl(0x60000000); hdr->payload_len = htons(len); hdr->nexthdr = proto; hdr->hop_limit = np->hop_limit; ipv6_addr_copy(&hdr->saddr, saddr); ipv6_addr_copy(&hdr->daddr, daddr); return 0;}static struct ipv6hdr * ip6_bld_1(struct sock *sk, struct sk_buff *skb, struct flowi *fl, int hlimit, unsigned pktlength){ struct ipv6hdr *hdr; skb->nh.raw = skb_put(skb, sizeof(struct ipv6hdr)); hdr = skb->nh.ipv6h; *(u32*)hdr = fl->fl6_flowlabel | htonl(0x60000000); hdr->payload_len = htons(pktlength - sizeof(struct ipv6hdr)); hdr->hop_limit = hlimit; hdr->nexthdr = fl->proto; ipv6_addr_copy(&hdr->saddr, fl->nl_u.ip6_u.saddr); ipv6_addr_copy(&hdr->daddr, fl->nl_u.ip6_u.daddr); return hdr;}static __inline__ u8 * ipv6_build_fraghdr(struct sk_buff *skb, u8* prev_hdr, unsigned offset){ struct frag_hdr *fhdr; fhdr = (struct frag_hdr *) skb_put(skb, sizeof(struct frag_hdr)); fhdr->nexthdr = *prev_hdr; *prev_hdr = NEXTHDR_FRAGMENT; prev_hdr = &fhdr->nexthdr; fhdr->reserved = 0; fhdr->frag_off = htons(offset); fhdr->identification = ipv6_fragmentation_id++; return &fhdr->nexthdr;}static int ip6_frag_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data, struct dst_entry *dst, struct flowi *fl, struct ipv6_txoptions *opt, struct in6_addr *final_dst, int hlimit, int flags, unsigned length, int mtu){ struct ipv6hdr *hdr; struct sk_buff *last_skb; u8 *prev_hdr; int unfrag_len; int frag_len; int last_len; int nfrags; int fhdr_dist; int frag_off; int data_off; int err; /* * Fragmentation * * Extension header order: * Hop-by-hop -> Dest0 -> Routing -> Fragment -> Auth -> Dest1 -> rest (...) * * We must build the non-fragmented part that * will be in every packet... this also means * that other extension headers (Dest, Auth, etc) * must be considered in the data to be fragmented */ unfrag_len = sizeof(struct ipv6hdr) + sizeof(struct frag_hdr); last_len = length; if (opt) { unfrag_len += opt->opt_nflen; last_len += opt->opt_flen; } /* * Length of fragmented part on every packet but * the last must be an: * "integer multiple of 8 octects". */ frag_len = (mtu - unfrag_len) & ~0x7; /* Unfragmentable part exceeds mtu. */ if (frag_len <= 0) { ipv6_local_error(sk, EMSGSIZE, fl, mtu); return -EMSGSIZE; } nfrags = last_len / frag_len; /* * We must send from end to start because of * UDP/ICMP checksums. We do a funny trick: * fill the last skb first with the fixed * header (and its data) and then use it * to create the following segments and send it * in the end. If the peer is checking the M_flag * to trigger the reassembly code then this * might be a good idea. */ frag_off = nfrags * frag_len; last_len -= frag_off; if (last_len == 0) { last_len = frag_len; frag_off -= frag_len; nfrags--; } data_off = frag_off; /* And it is implementation problem: for now we assume, that all the exthdrs will fit to the first fragment. */ if (opt) { if (frag_len < opt->opt_flen) { ipv6_local_error(sk, EMSGSIZE, fl, mtu); return -EMSGSIZE; } data_off = frag_off - opt->opt_flen; } last_skb = sock_alloc_send_skb(sk, unfrag_len + frag_len + dst->dev->hard_header_len + 15, 0, flags & MSG_DONTWAIT, &err); if (last_skb == NULL) return err; last_skb->dst = dst_clone(dst); skb_reserve(last_skb, (dst->dev->hard_header_len + 15) & ~15); hdr = ip6_bld_1(sk, last_skb, fl, hlimit, frag_len+unfrag_len); prev_hdr = &hdr->nexthdr; if (opt && opt->opt_nflen) prev_hdr = ipv6_build_nfrag_opts(last_skb, prev_hdr, opt, final_dst, 0); prev_hdr = ipv6_build_fraghdr(last_skb, prev_hdr, frag_off); fhdr_dist = prev_hdr - last_skb->data; err = getfrag(data, &hdr->saddr, last_skb->tail, data_off, last_len); if (!err) { while (nfrags--) { struct sk_buff *skb; struct frag_hdr *fhdr2; skb = skb_copy(last_skb, sk->allocation); if (skb == NULL) { ipv6_statistics.Ip6FragFails++; kfree_skb(last_skb); return -ENOMEM; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -