/*** * * ipv4/ip_output.c - prepare outgoing IP packets * * Copyright (C) 2002 Ulrich Marx * 2003-2005 Jan Kiszka * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include static DEFINE_RTDM_LOCK(rt_ip_id_lock); static u16 rt_ip_id_count = 0; /*** * Slow path for fragmented packets */ int rt_ip_build_xmit_slow(struct rtsocket *sk, int getfrag(const void *, char *, unsigned int, unsigned int), const void *frag, unsigned length, struct dest_route *rt, int msg_flags, unsigned int mtu, unsigned int prio) { int err, next_err; struct rtskb *skb; struct rtskb *next_skb; struct iphdr *iph; struct rtnet_device *rtdev = rt->rtdev; unsigned int fragdatalen; unsigned int offset = 0; u16 msg_rt_ip_id; rtdm_lockctx_t context; unsigned int rtskb_size; int hh_len = (rtdev->hard_header_len + 15) & ~15; #define FRAGHEADERLEN sizeof(struct iphdr) fragdatalen = ((mtu - FRAGHEADERLEN) & ~7); /* Store id in local variable */ rtdm_lock_get_irqsave(&rt_ip_id_lock, context); msg_rt_ip_id = rt_ip_id_count++; rtdm_lock_put_irqrestore(&rt_ip_id_lock, context); rtskb_size = mtu + hh_len + 15; /* TODO: delay previous skb until ALL errors are catched which may occure during next skb setup */ /* Preallocate first rtskb */ skb = alloc_rtskb(rtskb_size, &sk->skb_pool); if (skb == NULL) return -ENOBUFS; for (offset = 0; offset < length; offset += fragdatalen) { int fraglen; /* The length (IP, including ip-header) of this very fragment */ __u16 frag_off = offset >> 3; next_err = 0; if (offset >= length - fragdatalen) { /* last fragment */ fraglen = FRAGHEADERLEN + length - offset; next_skb = NULL; } else { fraglen = FRAGHEADERLEN + fragdatalen; frag_off |= IP_MF; next_skb = alloc_rtskb(rtskb_size, &sk->skb_pool); if (next_skb == NULL) { frag_off &= ~IP_MF; /* cut the chain */ next_err = -ENOBUFS; } } rtskb_reserve(skb, hh_len); skb->rtdev = rtdev; skb->nh.iph = iph = (struct iphdr *)rtskb_put(skb, fraglen); skb->priority = prio; iph->version = 4; iph->ihl = 5; /* 20 byte header - no options */ iph->tos = sk->prot.inet.tos; iph->tot_len = htons(fraglen); iph->id = htons(msg_rt_ip_id); iph->frag_off = htons(frag_off); iph->ttl = 255; iph->protocol = sk->protocol; iph->saddr = rtdev->local_ip; iph->daddr = rt->ip; iph->check = 0; /* required! */ iph->check = ip_fast_csum((unsigned char *)iph, 5 /*iph->ihl*/); if ((err = getfrag(frag, ((char *)iph) + 5 /*iph->ihl*/ * 4, offset, fraglen - FRAGHEADERLEN))) goto error; if (rtdev->hard_header) { err = rtdev->hard_header(skb, rtdev, ETH_P_IP, rt->dev_addr, rtdev->dev_addr, skb->len); if (err < 0) goto error; } err = rtdev_xmit(skb); skb = next_skb; if (err != 0) { err = -EAGAIN; goto error; } if (next_err != 0) return next_err; } return 0; error: if (skb != NULL) { kfree_rtskb(skb); if (next_skb != NULL) kfree_rtskb(next_skb); } return err; } /*** * Fast path for unfragmented packets. */ int rt_ip_build_xmit(struct rtsocket *sk, int getfrag(const void *, char *, unsigned int, unsigned int), const void *frag, unsigned length, struct dest_route *rt, int msg_flags) { int err = 0; struct rtskb *skb; struct iphdr *iph; int hh_len; u16 msg_rt_ip_id; rtdm_lockctx_t context; struct rtnet_device *rtdev = rt->rtdev; unsigned int prio; unsigned int mtu; /* sk->priority may encode both priority and output channel. Make sure we use a consitent value, also for the MTU which is derived from the channel. */ prio = (volatile unsigned int)sk->priority; mtu = rtdev->get_mtu(rtdev, prio); /* * Try the simple case first. This leaves fragmented frames, and by choice * RAW frames within 20 bytes of maximum size(rare) to the long path */ length += sizeof(struct iphdr); if (length > mtu) return rt_ip_build_xmit_slow(sk, getfrag, frag, length - sizeof(struct iphdr), rt, msg_flags, mtu, prio); /* Store id in local variable */ rtdm_lock_get_irqsave(&rt_ip_id_lock, context); msg_rt_ip_id = rt_ip_id_count++; rtdm_lock_put_irqrestore(&rt_ip_id_lock, context); hh_len = (rtdev->hard_header_len + 15) & ~15; skb = alloc_rtskb(length + hh_len + 15, &sk->skb_pool); if (skb == NULL) return -ENOBUFS; rtskb_reserve(skb, hh_len); skb->rtdev = rtdev; skb->nh.iph = iph = (struct iphdr *)rtskb_put(skb, length); skb->priority = prio; iph->version = 4; iph->ihl = 5; iph->tos = sk->prot.inet.tos; iph->tot_len = htons(length); iph->id = htons(msg_rt_ip_id); iph->frag_off = htons(IP_DF); iph->ttl = 255; iph->protocol = sk->protocol; iph->saddr = rtdev->local_ip; iph->daddr = rt->ip; iph->check = 0; /* required! */ iph->check = ip_fast_csum((unsigned char *)iph, 5 /*iph->ihl*/); if ((err = getfrag(frag, ((char *)iph) + 5 /*iph->ihl*/ * 4, 0, length - 5 /*iph->ihl*/ * 4))) goto error; if (rtdev->hard_header) { err = rtdev->hard_header(skb, rtdev, ETH_P_IP, rt->dev_addr, rtdev->dev_addr, skb->len); if (err < 0) goto error; } err = rtdev_xmit(skb); if (err) return -EAGAIN; else return 0; error: kfree_rtskb(skb); return err; } EXPORT_SYMBOL_GPL(rt_ip_build_xmit); /*** * IP protocol layer initialiser */ static struct rtpacket_type ip_packet_type = { .type = __constant_htons( ETH_P_IP), .handler = &rt_ip_rcv }; /*** * ip_init */ void __init rt_ip_init(void) { rtdev_add_pack(&ip_packet_type); rt_ip_fragment_init(); } /*** * ip_release */ void rt_ip_release(void) { rtdev_remove_pack(&ip_packet_type); rt_ip_fragment_cleanup(); }