mirror of
https://github.com/vincentmli/bpfire.git
synced 2026-05-11 09:48:24 +02:00
3305 lines
90 KiB
Diff
3305 lines
90 KiB
Diff
diff -Naur linux-3.10.30.org/drivers/net/imq.c linux-3.10.30/drivers/net/imq.c
|
|
--- linux-3.10.30.org/drivers/net/imq.c 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-3.10.30/drivers/net/imq.c 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -0,0 +1,1001 @@
|
|
+/*
|
|
+ * Pseudo-driver for the intermediate queue device.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Authors: Patrick McHardy, <kaber@trash.net>
|
|
+ *
|
|
+ * The first version was written by Martin Devera, <devik@cdi.cz>
|
|
+ *
|
|
+ * Credits: Jan Rafaj <imq2t@cedric.vabo.cz>
|
|
+ * - Update patch to 2.4.21
|
|
+ * Sebastian Strollo <sstrollo@nortelnetworks.com>
|
|
+ * - Fix "Dead-loop on netdevice imq"-issue
|
|
+ * Marcel Sebek <sebek64@post.cz>
|
|
+ * - Update to 2.6.2-rc1
|
|
+ *
|
|
+ * After some time of inactivity there is a group taking care
|
|
+ * of IMQ again: http://www.linuximq.net
|
|
+ *
|
|
+ *
|
|
+ * 2004/06/30 - New version of IMQ patch to kernels <=2.6.7
|
|
+ * including the following changes:
|
|
+ *
|
|
+ * - Correction of ipv6 support "+"s issue (Hasso Tepper)
|
|
+ * - Correction of imq_init_devs() issue that resulted in
|
|
+ * kernel OOPS unloading IMQ as module (Norbert Buchmuller)
|
|
+ * - Addition of functionality to choose number of IMQ devices
|
|
+ * during kernel config (Andre Correa)
|
|
+ * - Addition of functionality to choose how IMQ hooks on
|
|
+ * PRE and POSTROUTING (after or before NAT) (Andre Correa)
|
|
+ * - Cosmetic corrections (Norbert Buchmuller) (Andre Correa)
|
|
+ *
|
|
+ *
|
|
+ * 2005/12/16 - IMQ versions between 2.6.7 and 2.6.13 were
|
|
+ * released with almost no problems. 2.6.14-x was released
|
|
+ * with some important changes: nfcache was removed; After
|
|
+ * some weeks of trouble we figured out that some IMQ fields
|
|
+ * in skb were missing in skbuff.c - skb_clone and copy_skb_header.
|
|
+ * These functions are correctly patched by this new patch version.
|
|
+ *
|
|
+ * Thanks for all who helped to figure out all the problems with
|
|
+ * 2.6.14.x: Patrick McHardy, Rune Kock, VeNoMouS, Max CtRiX,
|
|
+ * Kevin Shanahan, Richard Lucassen, Valery Dachev (hopefully
|
|
+ * I didn't forget anybody). I apologize again for my lack of time.
|
|
+ *
|
|
+ *
|
|
+ * 2008/06/17 - 2.6.25 - Changed imq.c to use qdisc_run() instead
|
|
+ * of qdisc_restart() and moved qdisc_run() to tasklet to avoid
|
|
+ * recursive locking. New initialization routines to fix 'rmmod' not
|
|
+ * working anymore. Used code from ifb.c. (Jussi Kivilinna)
|
|
+ *
|
|
+ * 2008/08/06 - 2.6.26 - (JK)
|
|
+ * - Replaced tasklet with 'netif_schedule()'.
|
|
+ * - Cleaned up and added comments for imq_nf_queue().
|
|
+ *
|
|
+ * 2009/04/12
|
|
+ * - Add skb_save_cb/skb_restore_cb helper functions for backuping
|
|
+ * control buffer. This is needed because qdisc-layer on kernels
|
|
+ * 2.6.27 and newer overwrite control buffer. (Jussi Kivilinna)
|
|
+ * - Add better locking for IMQ device. Hopefully this will solve
|
|
+ * SMP issues. (Jussi Kivilinna)
|
|
+ * - Port to 2.6.27
|
|
+ * - Port to 2.6.28
|
|
+ * - Port to 2.6.29 + fix rmmod not working
|
|
+ *
|
|
+ * 2009/04/20 - (Jussi Kivilinna)
|
|
+ * - Use netdevice feature flags to avoid extra packet handling
|
|
+ * by core networking layer and possibly increase performance.
|
|
+ *
|
|
+ * 2009/09/26 - (Jussi Kivilinna)
|
|
+ * - Add imq_nf_reinject_lockless to fix deadlock with
|
|
+ * imq_nf_queue/imq_nf_reinject.
|
|
+ *
|
|
+ * 2009/12/08 - (Jussi Kivilinna)
|
|
+ * - Port to 2.6.32
|
|
+ * - Add check for skb->nf_queue_entry==NULL in imq_dev_xmit()
|
|
+ * - Also add better error checking for skb->nf_queue_entry usage
|
|
+ *
|
|
+ * 2010/02/25 - (Jussi Kivilinna)
|
|
+ * - Port to 2.6.33
|
|
+ *
|
|
+ * 2010/08/15 - (Jussi Kivilinna)
|
|
+ * - Port to 2.6.35
|
|
+ * - Simplify hook registration by using nf_register_hooks.
|
|
+ * - nf_reinject doesn't need spinlock around it, therefore remove
|
|
+ * imq_nf_reinject function. Other nf_reinject users protect
|
|
+ * their own data with spinlock. With IMQ however all data is
|
|
+ * needed is stored per skbuff, so no locking is needed.
|
|
+ * - Changed IMQ to use 'separate' NF_IMQ_QUEUE instead of
|
|
+ * NF_QUEUE, this allows working coexistance of IMQ and other
|
|
+ * NF_QUEUE users.
|
|
+ * - Make IMQ multi-queue. Number of IMQ device queues can be
|
|
+ * increased with 'numqueues' module parameters. Default number
|
|
+ * of queues is 1, in other words by default IMQ works as
|
|
+ * single-queue device. Multi-queue selection is based on
|
|
+ * IFB multi-queue patch by Changli Gao <xiaosuo@gmail.com>.
|
|
+ *
|
|
+ * 2011/03/18 - (Jussi Kivilinna)
|
|
+ * - Port to 2.6.38
|
|
+ *
|
|
+ * 2011/07/12 - (syoder89@gmail.com)
|
|
+ * - Crash fix that happens when the receiving interface has more
|
|
+ * than one queue (add missing skb_set_queue_mapping in
|
|
+ * imq_select_queue).
|
|
+ *
|
|
+ * 2011/07/26 - (Jussi Kivilinna)
|
|
+ * - Add queue mapping checks for packets exiting IMQ.
|
|
+ * - Port to 3.0
|
|
+ *
|
|
+ * 2011/08/16 - (Jussi Kivilinna)
|
|
+ * - Clear IFF_TX_SKB_SHARING flag that was added for linux 3.0.2
|
|
+ *
|
|
+ * 2011/11/03 - Germano Michel <germanomichel@gmail.com>
|
|
+ * - Fix IMQ for net namespaces
|
|
+ *
|
|
+ * 2011/11/04 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
|
|
+ * - Port to 3.1
|
|
+ * - Clean-up, move 'get imq device pointer by imqX name' to
|
|
+ * separate function from imq_nf_queue().
|
|
+ *
|
|
+ * 2012/01/05 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
|
|
+ * - Port to 3.2
|
|
+ *
|
|
+ * 2012/03/19 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
|
|
+ * - Port to 3.3
|
|
+ *
|
|
+ * 2012/12/12 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
|
|
+ * - Port to 3.7
|
|
+ * - Fix checkpatch.pl warnings
|
|
+ *
|
|
+ * 2013/09/10 - Jussi Kivilinna <jussi.kivilinna@iki.fi>
|
|
+ * - Fixed GSO handling for 3.10, see imq_nf_queue() for comments.
|
|
+ * - Don't copy skb->cb_next when copying or cloning skbuffs.
|
|
+ *
|
|
+ * Also, many thanks to pablo Sebastian Greco for making the initial
|
|
+ * patch and to those who helped the testing.
|
|
+ *
|
|
+ * More info at: http://www.linuximq.net/ (Andre Correa)
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/moduleparam.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/rtnetlink.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/netfilter.h>
|
|
+#include <linux/netfilter_ipv4.h>
|
|
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
+ #include <linux/netfilter_ipv6.h>
|
|
+#endif
|
|
+#include <linux/imq.h>
|
|
+#include <net/pkt_sched.h>
|
|
+#include <net/netfilter/nf_queue.h>
|
|
+#include <net/sock.h>
|
|
+#include <linux/ip.h>
|
|
+#include <linux/ipv6.h>
|
|
+#include <linux/if_vlan.h>
|
|
+#include <linux/if_pppox.h>
|
|
+#include <net/ip.h>
|
|
+#include <net/ipv6.h>
|
|
+
|
|
+static int imq_nf_queue(struct nf_queue_entry *entry, unsigned queue_num);
|
|
+
|
|
+static nf_hookfn imq_nf_hook;
|
|
+
|
|
+static struct nf_hook_ops imq_ops[] = {
|
|
+ {
|
|
+ /* imq_ingress_ipv4 */
|
|
+ .hook = imq_nf_hook,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pf = PF_INET,
|
|
+ .hooknum = NF_INET_PRE_ROUTING,
|
|
+#if defined(CONFIG_IMQ_BEHAVIOR_BA) || defined(CONFIG_IMQ_BEHAVIOR_BB)
|
|
+ .priority = NF_IP_PRI_MANGLE + 1,
|
|
+#else
|
|
+ .priority = NF_IP_PRI_NAT_DST + 1,
|
|
+#endif
|
|
+ },
|
|
+ {
|
|
+ /* imq_egress_ipv4 */
|
|
+ .hook = imq_nf_hook,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pf = PF_INET,
|
|
+ .hooknum = NF_INET_POST_ROUTING,
|
|
+#if defined(CONFIG_IMQ_BEHAVIOR_AA) || defined(CONFIG_IMQ_BEHAVIOR_BA)
|
|
+ .priority = NF_IP_PRI_LAST,
|
|
+#else
|
|
+ .priority = NF_IP_PRI_NAT_SRC - 1,
|
|
+#endif
|
|
+ },
|
|
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
+ {
|
|
+ /* imq_ingress_ipv6 */
|
|
+ .hook = imq_nf_hook,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pf = PF_INET6,
|
|
+ .hooknum = NF_INET_PRE_ROUTING,
|
|
+#if defined(CONFIG_IMQ_BEHAVIOR_BA) || defined(CONFIG_IMQ_BEHAVIOR_BB)
|
|
+ .priority = NF_IP6_PRI_MANGLE + 1,
|
|
+#else
|
|
+ .priority = NF_IP6_PRI_NAT_DST + 1,
|
|
+#endif
|
|
+ },
|
|
+ {
|
|
+ /* imq_egress_ipv6 */
|
|
+ .hook = imq_nf_hook,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pf = PF_INET6,
|
|
+ .hooknum = NF_INET_POST_ROUTING,
|
|
+#if defined(CONFIG_IMQ_BEHAVIOR_AA) || defined(CONFIG_IMQ_BEHAVIOR_BA)
|
|
+ .priority = NF_IP6_PRI_LAST,
|
|
+#else
|
|
+ .priority = NF_IP6_PRI_NAT_SRC - 1,
|
|
+#endif
|
|
+ },
|
|
+#endif
|
|
+};
|
|
+
|
|
+#if defined(CONFIG_IMQ_NUM_DEVS)
|
|
+static int numdevs = CONFIG_IMQ_NUM_DEVS;
|
|
+#else
|
|
+static int numdevs = IMQ_MAX_DEVS;
|
|
+#endif
|
|
+
|
|
+static struct net_device *imq_devs_cache[IMQ_MAX_DEVS];
|
|
+
|
|
+#define IMQ_MAX_QUEUES 32
|
|
+static int numqueues = 1;
|
|
+static u32 imq_hashrnd;
|
|
+
|
|
+static inline __be16 pppoe_proto(const struct sk_buff *skb)
|
|
+{
|
|
+ return *((__be16 *)(skb_mac_header(skb) + ETH_HLEN +
|
|
+ sizeof(struct pppoe_hdr)));
|
|
+}
|
|
+
|
|
+static u16 imq_hash(struct net_device *dev, struct sk_buff *skb)
|
|
+{
|
|
+ unsigned int pull_len;
|
|
+ u16 protocol = skb->protocol;
|
|
+ u32 addr1, addr2;
|
|
+ u32 hash, ihl = 0;
|
|
+ union {
|
|
+ u16 in16[2];
|
|
+ u32 in32;
|
|
+ } ports;
|
|
+ u8 ip_proto;
|
|
+
|
|
+ pull_len = 0;
|
|
+
|
|
+recheck:
|
|
+ switch (protocol) {
|
|
+ case htons(ETH_P_8021Q): {
|
|
+ if (unlikely(skb_pull(skb, VLAN_HLEN) == NULL))
|
|
+ goto other;
|
|
+
|
|
+ pull_len += VLAN_HLEN;
|
|
+ skb->network_header += VLAN_HLEN;
|
|
+
|
|
+ protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
|
|
+ goto recheck;
|
|
+ }
|
|
+
|
|
+ case htons(ETH_P_PPP_SES): {
|
|
+ if (unlikely(skb_pull(skb, PPPOE_SES_HLEN) == NULL))
|
|
+ goto other;
|
|
+
|
|
+ pull_len += PPPOE_SES_HLEN;
|
|
+ skb->network_header += PPPOE_SES_HLEN;
|
|
+
|
|
+ protocol = pppoe_proto(skb);
|
|
+ goto recheck;
|
|
+ }
|
|
+
|
|
+ case htons(ETH_P_IP): {
|
|
+ const struct iphdr *iph = ip_hdr(skb);
|
|
+
|
|
+ if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))
|
|
+ goto other;
|
|
+
|
|
+ addr1 = iph->daddr;
|
|
+ addr2 = iph->saddr;
|
|
+
|
|
+ ip_proto = !(ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) ?
|
|
+ iph->protocol : 0;
|
|
+ ihl = ip_hdrlen(skb);
|
|
+
|
|
+ break;
|
|
+ }
|
|
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
+ case htons(ETH_P_IPV6): {
|
|
+ const struct ipv6hdr *iph = ipv6_hdr(skb);
|
|
+ __be16 fo = 0;
|
|
+
|
|
+ if (unlikely(!pskb_may_pull(skb, sizeof(struct ipv6hdr))))
|
|
+ goto other;
|
|
+
|
|
+ addr1 = iph->daddr.s6_addr32[3];
|
|
+ addr2 = iph->saddr.s6_addr32[3];
|
|
+ ihl = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &ip_proto,
|
|
+ &fo);
|
|
+ if (unlikely(ihl < 0))
|
|
+ goto other;
|
|
+
|
|
+ break;
|
|
+ }
|
|
+#endif
|
|
+ default:
|
|
+other:
|
|
+ if (pull_len != 0) {
|
|
+ skb_push(skb, pull_len);
|
|
+ skb->network_header -= pull_len;
|
|
+ }
|
|
+
|
|
+ return (u16)(ntohs(protocol) % dev->real_num_tx_queues);
|
|
+ }
|
|
+
|
|
+ if (addr1 > addr2)
|
|
+ swap(addr1, addr2);
|
|
+
|
|
+ switch (ip_proto) {
|
|
+ case IPPROTO_TCP:
|
|
+ case IPPROTO_UDP:
|
|
+ case IPPROTO_DCCP:
|
|
+ case IPPROTO_ESP:
|
|
+ case IPPROTO_AH:
|
|
+ case IPPROTO_SCTP:
|
|
+ case IPPROTO_UDPLITE: {
|
|
+ if (likely(skb_copy_bits(skb, ihl, &ports.in32, 4) >= 0)) {
|
|
+ if (ports.in16[0] > ports.in16[1])
|
|
+ swap(ports.in16[0], ports.in16[1]);
|
|
+ break;
|
|
+ }
|
|
+ /* fall-through */
|
|
+ }
|
|
+ default:
|
|
+ ports.in32 = 0;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (pull_len != 0) {
|
|
+ skb_push(skb, pull_len);
|
|
+ skb->network_header -= pull_len;
|
|
+ }
|
|
+
|
|
+ hash = jhash_3words(addr1, addr2, ports.in32, imq_hashrnd ^ ip_proto);
|
|
+
|
|
+ return (u16)(((u64)hash * dev->real_num_tx_queues) >> 32);
|
|
+}
|
|
+
|
|
+static inline bool sk_tx_queue_recorded(struct sock *sk)
|
|
+{
|
|
+ return (sk_tx_queue_get(sk) >= 0);
|
|
+}
|
|
+
|
|
+static struct netdev_queue *imq_select_queue(struct net_device *dev,
|
|
+ struct sk_buff *skb)
|
|
+{
|
|
+ u16 queue_index = 0;
|
|
+ u32 hash;
|
|
+
|
|
+ if (likely(dev->real_num_tx_queues == 1))
|
|
+ goto out;
|
|
+
|
|
+ /* IMQ can be receiving ingress or engress packets. */
|
|
+
|
|
+ /* Check first for if rx_queue is set */
|
|
+ if (skb_rx_queue_recorded(skb)) {
|
|
+ queue_index = skb_get_rx_queue(skb);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Check if socket has tx_queue set */
|
|
+ if (sk_tx_queue_recorded(skb->sk)) {
|
|
+ queue_index = sk_tx_queue_get(skb->sk);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Try use socket hash */
|
|
+ if (skb->sk && skb->sk->sk_hash) {
|
|
+ hash = skb->sk->sk_hash;
|
|
+ queue_index =
|
|
+ (u16)(((u64)hash * dev->real_num_tx_queues) >> 32);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Generate hash from packet data */
|
|
+ queue_index = imq_hash(dev, skb);
|
|
+
|
|
+out:
|
|
+ if (unlikely(queue_index >= dev->real_num_tx_queues))
|
|
+ queue_index = (u16)((u32)queue_index % dev->real_num_tx_queues);
|
|
+
|
|
+ skb_set_queue_mapping(skb, queue_index);
|
|
+ return netdev_get_tx_queue(dev, queue_index);
|
|
+}
|
|
+
|
|
+static struct net_device_stats *imq_get_stats(struct net_device *dev)
|
|
+{
|
|
+ return &dev->stats;
|
|
+}
|
|
+
|
|
+/* called for packets kfree'd in qdiscs at places other than enqueue */
|
|
+static void imq_skb_destructor(struct sk_buff *skb)
|
|
+{
|
|
+ struct nf_queue_entry *entry = skb->nf_queue_entry;
|
|
+
|
|
+ skb->nf_queue_entry = NULL;
|
|
+
|
|
+ if (entry) {
|
|
+ nf_queue_entry_release_refs(entry);
|
|
+ kfree(entry);
|
|
+ }
|
|
+
|
|
+ skb_restore_cb(skb); /* kfree backup */
|
|
+}
|
|
+
|
|
+static void imq_done_check_queue_mapping(struct sk_buff *skb,
|
|
+ struct net_device *dev)
|
|
+{
|
|
+ unsigned int queue_index;
|
|
+
|
|
+ /* Don't let queue_mapping be left too large after exiting IMQ */
|
|
+ if (likely(skb->dev != dev && skb->dev != NULL)) {
|
|
+ queue_index = skb_get_queue_mapping(skb);
|
|
+ if (unlikely(queue_index >= skb->dev->real_num_tx_queues)) {
|
|
+ queue_index = (u16)((u32)queue_index %
|
|
+ skb->dev->real_num_tx_queues);
|
|
+ skb_set_queue_mapping(skb, queue_index);
|
|
+ }
|
|
+ } else {
|
|
+ /* skb->dev was IMQ device itself or NULL, be on safe side and
|
|
+ * just clear queue mapping.
|
|
+ */
|
|
+ skb_set_queue_mapping(skb, 0);
|
|
+ }
|
|
+}
|
|
+
|
|
+static netdev_tx_t imq_dev_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
+{
|
|
+ struct nf_queue_entry *entry = skb->nf_queue_entry;
|
|
+
|
|
+ skb->nf_queue_entry = NULL;
|
|
+ dev->trans_start = jiffies;
|
|
+
|
|
+ dev->stats.tx_bytes += skb->len;
|
|
+ dev->stats.tx_packets++;
|
|
+
|
|
+ if (unlikely(entry == NULL)) {
|
|
+ /* We don't know what is going on here.. packet is queued for
|
|
+ * imq device, but (probably) not by us.
|
|
+ *
|
|
+ * If this packet was not send here by imq_nf_queue(), then
|
|
+ * skb_save_cb() was not used and skb_free() should not show:
|
|
+ * WARNING: IMQ: kfree_skb: skb->cb_next:..
|
|
+ * and/or
|
|
+ * WARNING: IMQ: kfree_skb: skb->nf_queue_entry...
|
|
+ *
|
|
+ * However if this message is shown, then IMQ is somehow broken
|
|
+ * and you should report this to linuximq.net.
|
|
+ */
|
|
+
|
|
+ /* imq_dev_xmit is black hole that eats all packets, report that
|
|
+ * we eat this packet happily and increase dropped counters.
|
|
+ */
|
|
+
|
|
+ dev->stats.tx_dropped++;
|
|
+ dev_kfree_skb(skb);
|
|
+
|
|
+ return NETDEV_TX_OK;
|
|
+ }
|
|
+
|
|
+ skb_restore_cb(skb); /* restore skb->cb */
|
|
+
|
|
+ skb->imq_flags = 0;
|
|
+ skb->destructor = NULL;
|
|
+
|
|
+ imq_done_check_queue_mapping(skb, dev);
|
|
+
|
|
+ nf_reinject(entry, NF_ACCEPT);
|
|
+
|
|
+ return NETDEV_TX_OK;
|
|
+}
|
|
+
|
|
+static struct net_device *get_imq_device_by_index(int index)
|
|
+{
|
|
+ struct net_device *dev = NULL;
|
|
+ struct net *net;
|
|
+ char buf[8];
|
|
+
|
|
+ /* get device by name and cache result */
|
|
+ snprintf(buf, sizeof(buf), "imq%d", index);
|
|
+
|
|
+ /* Search device from all namespaces. */
|
|
+ for_each_net(net) {
|
|
+ dev = dev_get_by_name(net, buf);
|
|
+ if (dev)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (WARN_ON_ONCE(dev == NULL)) {
|
|
+ /* IMQ device not found. Exotic config? */
|
|
+ return ERR_PTR(-ENODEV);
|
|
+ }
|
|
+
|
|
+ imq_devs_cache[index] = dev;
|
|
+ dev_put(dev);
|
|
+
|
|
+ return dev;
|
|
+}
|
|
+
|
|
+static struct nf_queue_entry *nf_queue_entry_dup(struct nf_queue_entry *e)
|
|
+{
|
|
+ struct nf_queue_entry *entry = kmemdup(e, e->size, GFP_ATOMIC);
|
|
+ if (entry) {
|
|
+ if (nf_queue_entry_get_refs(entry))
|
|
+ return entry;
|
|
+ kfree(entry);
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_BRIDGE_NETFILTER
|
|
+/* When called from bridge netfilter, skb->data must point to MAC header
|
|
+ * before calling skb_gso_segment(). Else, original MAC header is lost
|
|
+ * and segmented skbs will be sent to wrong destination.
|
|
+ */
|
|
+static void nf_bridge_adjust_skb_data(struct sk_buff *skb)
|
|
+{
|
|
+ if (skb->nf_bridge)
|
|
+ __skb_push(skb, skb->network_header - skb->mac_header);
|
|
+}
|
|
+
|
|
+static void nf_bridge_adjust_segmented_data(struct sk_buff *skb)
|
|
+{
|
|
+ if (skb->nf_bridge)
|
|
+ __skb_pull(skb, skb->network_header - skb->mac_header);
|
|
+}
|
|
+#else
|
|
+#define nf_bridge_adjust_skb_data(s) do {} while (0)
|
|
+#define nf_bridge_adjust_segmented_data(s) do {} while (0)
|
|
+#endif
|
|
+
|
|
+static void free_entry(struct nf_queue_entry *entry)
|
|
+{
|
|
+ nf_queue_entry_release_refs(entry);
|
|
+ kfree(entry);
|
|
+}
|
|
+
|
|
+static int __imq_nf_queue(struct nf_queue_entry *entry, struct net_device *dev);
|
|
+
|
|
+static int __imq_nf_queue_gso(struct nf_queue_entry *entry,
|
|
+ struct net_device *dev, struct sk_buff *skb)
|
|
+{
|
|
+ int ret = -ENOMEM;
|
|
+ struct nf_queue_entry *entry_seg;
|
|
+
|
|
+ nf_bridge_adjust_segmented_data(skb);
|
|
+
|
|
+ if (skb->next == NULL) { /* last packet, no need to copy entry */
|
|
+ struct sk_buff *gso_skb = entry->skb;
|
|
+ entry->skb = skb;
|
|
+ ret = __imq_nf_queue(entry, dev);
|
|
+ if (ret)
|
|
+ entry->skb = gso_skb;
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ skb->next = NULL;
|
|
+
|
|
+ entry_seg = nf_queue_entry_dup(entry);
|
|
+ if (entry_seg) {
|
|
+ entry_seg->skb = skb;
|
|
+ ret = __imq_nf_queue(entry_seg, dev);
|
|
+ if (ret)
|
|
+ free_entry(entry_seg);
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int imq_nf_queue(struct nf_queue_entry *entry, unsigned queue_num)
|
|
+{
|
|
+ struct sk_buff *skb, *segs;
|
|
+ struct net_device *dev;
|
|
+ unsigned int queued;
|
|
+ int index, retval, err;
|
|
+
|
|
+ index = entry->skb->imq_flags & IMQ_F_IFMASK;
|
|
+ if (unlikely(index > numdevs - 1)) {
|
|
+ if (net_ratelimit())
|
|
+ pr_warn("IMQ: invalid device specified, highest is %u\n",
|
|
+ numdevs - 1);
|
|
+ retval = -EINVAL;
|
|
+ goto out_no_dev;
|
|
+ }
|
|
+
|
|
+ /* check for imq device by index from cache */
|
|
+ dev = imq_devs_cache[index];
|
|
+ if (unlikely(!dev)) {
|
|
+ dev = get_imq_device_by_index(index);
|
|
+ if (IS_ERR(dev)) {
|
|
+ retval = PTR_ERR(dev);
|
|
+ goto out_no_dev;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (unlikely(!(dev->flags & IFF_UP))) {
|
|
+ entry->skb->imq_flags = 0;
|
|
+ retval = -ECANCELED;
|
|
+ goto out_no_dev;
|
|
+ }
|
|
+
|
|
+ if (!skb_is_gso(entry->skb))
|
|
+ return __imq_nf_queue(entry, dev);
|
|
+
|
|
+ /* Since 3.10.x, GSO handling moved here as result of upstream commit
|
|
+ * a5fedd43d5f6c94c71053a66e4c3d2e35f1731a2 (netfilter: move
|
|
+ * skb_gso_segment into nfnetlink_queue module).
|
|
+ *
|
|
+ * Following code replicates the gso handling from
|
|
+ * 'net/netfilter/nfnetlink_queue_core.c':nfqnl_enqueue_packet().
|
|
+ */
|
|
+
|
|
+ skb = entry->skb;
|
|
+
|
|
+ switch (entry->pf) {
|
|
+ case NFPROTO_IPV4:
|
|
+ skb->protocol = htons(ETH_P_IP);
|
|
+ break;
|
|
+ case NFPROTO_IPV6:
|
|
+ skb->protocol = htons(ETH_P_IPV6);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ nf_bridge_adjust_skb_data(skb);
|
|
+ segs = skb_gso_segment(skb, 0);
|
|
+ /* Does not use PTR_ERR to limit the number of error codes that can be
|
|
+ * returned by nf_queue. For instance, callers rely on -ECANCELED to
|
|
+ * mean 'ignore this hook'.
|
|
+ */
|
|
+ err = -ENOBUFS;
|
|
+ if (IS_ERR(segs))
|
|
+ goto out_err;
|
|
+ queued = 0;
|
|
+ err = 0;
|
|
+ do {
|
|
+ struct sk_buff *nskb = segs->next;
|
|
+ if (nskb && nskb->next)
|
|
+ nskb->cb_next = NULL;
|
|
+ if (err == 0)
|
|
+ err = __imq_nf_queue_gso(entry, dev, segs);
|
|
+ if (err == 0)
|
|
+ queued++;
|
|
+ else
|
|
+ kfree_skb(segs);
|
|
+ segs = nskb;
|
|
+ } while (segs);
|
|
+
|
|
+ if (queued) {
|
|
+ if (err) /* some segments are already queued */
|
|
+ free_entry(entry);
|
|
+ kfree_skb(skb);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+out_err:
|
|
+ nf_bridge_adjust_segmented_data(skb);
|
|
+ retval = err;
|
|
+out_no_dev:
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static int __imq_nf_queue(struct nf_queue_entry *entry, struct net_device *dev)
|
|
+{
|
|
+ struct sk_buff *skb_orig, *skb, *skb_shared;
|
|
+ struct Qdisc *q;
|
|
+ struct netdev_queue *txq;
|
|
+ spinlock_t *root_lock;
|
|
+ int users;
|
|
+ int retval = -EINVAL;
|
|
+ unsigned int orig_queue_index;
|
|
+
|
|
+ dev->last_rx = jiffies;
|
|
+
|
|
+ skb = entry->skb;
|
|
+ skb_orig = NULL;
|
|
+
|
|
+ /* skb has owner? => make clone */
|
|
+ if (unlikely(skb->destructor)) {
|
|
+ skb_orig = skb;
|
|
+ skb = skb_clone(skb, GFP_ATOMIC);
|
|
+ if (unlikely(!skb)) {
|
|
+ retval = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+ skb->cb_next = NULL;
|
|
+ entry->skb = skb;
|
|
+ }
|
|
+
|
|
+ skb->nf_queue_entry = entry;
|
|
+
|
|
+ dev->stats.rx_bytes += skb->len;
|
|
+ dev->stats.rx_packets++;
|
|
+
|
|
+ if (!skb->dev) {
|
|
+ /* skb->dev == NULL causes problems, try the find cause. */
|
|
+ if (net_ratelimit()) {
|
|
+ dev_warn(&dev->dev,
|
|
+ "received packet with skb->dev == NULL\n");
|
|
+ dump_stack();
|
|
+ }
|
|
+
|
|
+ skb->dev = dev;
|
|
+ }
|
|
+
|
|
+ /* Disables softirqs for lock below */
|
|
+ rcu_read_lock_bh();
|
|
+
|
|
+ /* Multi-queue selection */
|
|
+ orig_queue_index = skb_get_queue_mapping(skb);
|
|
+ txq = imq_select_queue(dev, skb);
|
|
+
|
|
+ q = rcu_dereference(txq->qdisc);
|
|
+ if (unlikely(!q->enqueue))
|
|
+ goto packet_not_eaten_by_imq_dev;
|
|
+
|
|
+ root_lock = qdisc_lock(q);
|
|
+ spin_lock(root_lock);
|
|
+
|
|
+ users = atomic_read(&skb->users);
|
|
+
|
|
+ skb_shared = skb_get(skb); /* increase reference count by one */
|
|
+
|
|
+ /* backup skb->cb, as qdisc layer will overwrite it */
|
|
+ skb_save_cb(skb_shared);
|
|
+ qdisc_enqueue_root(skb_shared, q); /* might kfree_skb */
|
|
+
|
|
+ if (likely(atomic_read(&skb_shared->users) == users + 1)) {
|
|
+ kfree_skb(skb_shared); /* decrease reference count by one */
|
|
+
|
|
+ skb->destructor = &imq_skb_destructor;
|
|
+
|
|
+ /* cloned? */
|
|
+ if (unlikely(skb_orig))
|
|
+ kfree_skb(skb_orig); /* free original */
|
|
+
|
|
+ spin_unlock(root_lock);
|
|
+ rcu_read_unlock_bh();
|
|
+
|
|
+ /* schedule qdisc dequeue */
|
|
+ __netif_schedule(q);
|
|
+
|
|
+ retval = 0;
|
|
+ goto out;
|
|
+ } else {
|
|
+ skb_restore_cb(skb_shared); /* restore skb->cb */
|
|
+ skb->nf_queue_entry = NULL;
|
|
+ /*
|
|
+ * qdisc dropped packet and decreased skb reference count of
|
|
+ * skb, so we don't really want to and try refree as that would
|
|
+ * actually destroy the skb.
|
|
+ */
|
|
+ spin_unlock(root_lock);
|
|
+ goto packet_not_eaten_by_imq_dev;
|
|
+ }
|
|
+
|
|
+packet_not_eaten_by_imq_dev:
|
|
+ skb_set_queue_mapping(skb, orig_queue_index);
|
|
+ rcu_read_unlock_bh();
|
|
+
|
|
+ /* cloned? restore original */
|
|
+ if (unlikely(skb_orig)) {
|
|
+ kfree_skb(skb);
|
|
+ entry->skb = skb_orig;
|
|
+ }
|
|
+ retval = -1;
|
|
+out:
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static unsigned int imq_nf_hook(unsigned int hook, struct sk_buff *pskb,
|
|
+ const struct net_device *indev,
|
|
+ const struct net_device *outdev,
|
|
+ int (*okfn)(struct sk_buff *))
|
|
+{
|
|
+ return (pskb->imq_flags & IMQ_F_ENQUEUE) ? NF_IMQ_QUEUE : NF_ACCEPT;
|
|
+}
|
|
+
|
|
+static int imq_close(struct net_device *dev)
|
|
+{
|
|
+ netif_stop_queue(dev);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int imq_open(struct net_device *dev)
|
|
+{
|
|
+ netif_start_queue(dev);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct net_device_ops imq_netdev_ops = {
|
|
+ .ndo_open = imq_open,
|
|
+ .ndo_stop = imq_close,
|
|
+ .ndo_start_xmit = imq_dev_xmit,
|
|
+ .ndo_get_stats = imq_get_stats,
|
|
+};
|
|
+
|
|
+static void imq_setup(struct net_device *dev)
|
|
+{
|
|
+ dev->netdev_ops = &imq_netdev_ops;
|
|
+ dev->type = ARPHRD_VOID;
|
|
+ dev->mtu = 16000; /* too small? */
|
|
+ dev->tx_queue_len = 11000; /* too big? */
|
|
+ dev->flags = IFF_NOARP;
|
|
+ dev->features = NETIF_F_SG | NETIF_F_FRAGLIST |
|
|
+ NETIF_F_GSO | NETIF_F_HW_CSUM |
|
|
+ NETIF_F_HIGHDMA;
|
|
+ dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE |
|
|
+ IFF_TX_SKB_SHARING);
|
|
+}
|
|
+
|
|
+static int imq_validate(struct nlattr *tb[], struct nlattr *data[])
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (tb[IFLA_ADDRESS]) {
|
|
+ if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) {
|
|
+ ret = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+ if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) {
|
|
+ ret = -EADDRNOTAVAIL;
|
|
+ goto end;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+end:
|
|
+ pr_warn("IMQ: imq_validate failed (%d)\n", ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static struct rtnl_link_ops imq_link_ops __read_mostly = {
|
|
+ .kind = "imq",
|
|
+ .priv_size = 0,
|
|
+ .setup = imq_setup,
|
|
+ .validate = imq_validate,
|
|
+};
|
|
+
|
|
+static const struct nf_queue_handler imq_nfqh = {
|
|
+ .outfn = imq_nf_queue,
|
|
+};
|
|
+
|
|
+static int __init imq_init_hooks(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ nf_register_queue_imq_handler(&imq_nfqh);
|
|
+
|
|
+ ret = nf_register_hooks(imq_ops, ARRAY_SIZE(imq_ops));
|
|
+ if (ret < 0)
|
|
+ nf_unregister_queue_imq_handler();
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int __init imq_init_one(int index)
|
|
+{
|
|
+ struct net_device *dev;
|
|
+ int ret;
|
|
+
|
|
+ dev = alloc_netdev_mq(0, "imq%d", imq_setup, numqueues);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ret = dev_alloc_name(dev, dev->name);
|
|
+ if (ret < 0)
|
|
+ goto fail;
|
|
+
|
|
+ dev->rtnl_link_ops = &imq_link_ops;
|
|
+ ret = register_netdevice(dev);
|
|
+ if (ret < 0)
|
|
+ goto fail;
|
|
+
|
|
+ return 0;
|
|
+fail:
|
|
+ free_netdev(dev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int __init imq_init_devs(void)
|
|
+{
|
|
+ int err, i;
|
|
+
|
|
+ if (numdevs < 1 || numdevs > IMQ_MAX_DEVS) {
|
|
+ pr_err("IMQ: numdevs has to be betweed 1 and %u\n",
|
|
+ IMQ_MAX_DEVS);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (numqueues < 1 || numqueues > IMQ_MAX_QUEUES) {
|
|
+ pr_err("IMQ: numqueues has to be betweed 1 and %u\n",
|
|
+ IMQ_MAX_QUEUES);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ get_random_bytes(&imq_hashrnd, sizeof(imq_hashrnd));
|
|
+
|
|
+ rtnl_lock();
|
|
+ err = __rtnl_link_register(&imq_link_ops);
|
|
+
|
|
+ for (i = 0; i < numdevs && !err; i++)
|
|
+ err = imq_init_one(i);
|
|
+
|
|
+ if (err) {
|
|
+ __rtnl_link_unregister(&imq_link_ops);
|
|
+ memset(imq_devs_cache, 0, sizeof(imq_devs_cache));
|
|
+ }
|
|
+ rtnl_unlock();
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int __init imq_init_module(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+#if defined(CONFIG_IMQ_NUM_DEVS)
|
|
+ BUILD_BUG_ON(CONFIG_IMQ_NUM_DEVS > 16);
|
|
+ BUILD_BUG_ON(CONFIG_IMQ_NUM_DEVS < 2);
|
|
+ BUILD_BUG_ON(CONFIG_IMQ_NUM_DEVS - 1 > IMQ_F_IFMASK);
|
|
+#endif
|
|
+
|
|
+ err = imq_init_devs();
|
|
+ if (err) {
|
|
+ pr_err("IMQ: Error trying imq_init_devs(net)\n");
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = imq_init_hooks();
|
|
+ if (err) {
|
|
+ pr_err(KERN_ERR "IMQ: Error trying imq_init_hooks()\n");
|
|
+ rtnl_link_unregister(&imq_link_ops);
|
|
+ memset(imq_devs_cache, 0, sizeof(imq_devs_cache));
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ pr_info("IMQ driver loaded successfully. (numdevs = %d, numqueues = %d)\n",
|
|
+ numdevs, numqueues);
|
|
+
|
|
+#if defined(CONFIG_IMQ_BEHAVIOR_BA) || defined(CONFIG_IMQ_BEHAVIOR_BB)
|
|
+ pr_info("\tHooking IMQ before NAT on PREROUTING.\n");
|
|
+#else
|
|
+ pr_info("\tHooking IMQ after NAT on PREROUTING.\n");
|
|
+#endif
|
|
+#if defined(CONFIG_IMQ_BEHAVIOR_AB) || defined(CONFIG_IMQ_BEHAVIOR_BB)
|
|
+ pr_info("\tHooking IMQ before NAT on POSTROUTING.\n");
|
|
+#else
|
|
+ pr_info("\tHooking IMQ after NAT on POSTROUTING.\n");
|
|
+#endif
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit imq_unhook(void)
|
|
+{
|
|
+ nf_unregister_hooks(imq_ops, ARRAY_SIZE(imq_ops));
|
|
+ nf_unregister_queue_imq_handler();
|
|
+}
|
|
+
|
|
+static void __exit imq_cleanup_devs(void)
|
|
+{
|
|
+ rtnl_link_unregister(&imq_link_ops);
|
|
+ memset(imq_devs_cache, 0, sizeof(imq_devs_cache));
|
|
+}
|
|
+
|
|
+static void __exit imq_exit_module(void)
|
|
+{
|
|
+ imq_unhook();
|
|
+ imq_cleanup_devs();
|
|
+ pr_info("IMQ driver unloaded successfully.\n");
|
|
+}
|
|
+
|
|
+module_init(imq_init_module);
|
|
+module_exit(imq_exit_module);
|
|
+
|
|
+module_param(numdevs, int, 0);
|
|
+module_param(numqueues, int, 0);
|
|
+MODULE_PARM_DESC(numdevs, "number of IMQ devices (how many imq* devices will be created)");
|
|
+MODULE_PARM_DESC(numqueues, "number of queues per IMQ device");
|
|
+MODULE_AUTHOR("http://www.linuximq.net");
|
|
+MODULE_DESCRIPTION("Pseudo-driver for the intermediate queue device. See http://www.linuximq.net/ for more information.");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS_RTNL_LINK("imq");
|
|
+
|
|
diff -Naur linux-3.10.30.org/drivers/net/Kconfig linux-3.10.30/drivers/net/Kconfig
|
|
--- linux-3.10.30.org/drivers/net/Kconfig 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/drivers/net/Kconfig 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -207,6 +207,125 @@
|
|
depends on RIONET
|
|
default "128"
|
|
|
|
+config IMQ
|
|
+ tristate "IMQ (intermediate queueing device) support"
|
|
+ depends on NETDEVICES && NETFILTER
|
|
+ ---help---
|
|
+ The IMQ device(s) is used as placeholder for QoS queueing
|
|
+ disciplines. Every packet entering/leaving the IP stack can be
|
|
+ directed through the IMQ device where it's enqueued/dequeued to the
|
|
+ attached qdisc. This allows you to treat network devices as classes
|
|
+ and distribute bandwidth among them. Iptables is used to specify
|
|
+ through which IMQ device, if any, packets travel.
|
|
+
|
|
+ More information at: http://www.linuximq.net/
|
|
+
|
|
+ To compile this driver as a module, choose M here: the module
|
|
+ will be called imq. If unsure, say N.
|
|
+
|
|
+choice
|
|
+ prompt "IMQ behavior (PRE/POSTROUTING)"
|
|
+ depends on IMQ
|
|
+ default IMQ_BEHAVIOR_AB
|
|
+ help
|
|
+ This setting defines how IMQ behaves in respect to its
|
|
+ hooking in PREROUTING and POSTROUTING.
|
|
+
|
|
+ IMQ can work in any of the following ways:
|
|
+
|
|
+ PREROUTING | POSTROUTING
|
|
+ -----------------|-------------------
|
|
+ #1 After NAT | After NAT
|
|
+ #2 After NAT | Before NAT
|
|
+ #3 Before NAT | After NAT
|
|
+ #4 Before NAT | Before NAT
|
|
+
|
|
+ The default behavior is to hook before NAT on PREROUTING
|
|
+ and after NAT on POSTROUTING (#3).
|
|
+
|
|
+ This settings are specially usefull when trying to use IMQ
|
|
+ to shape NATed clients.
|
|
+
|
|
+ More information can be found at: www.linuximq.net
|
|
+
|
|
+ If not sure leave the default settings alone.
|
|
+
|
|
+config IMQ_BEHAVIOR_AA
|
|
+ bool "IMQ AA"
|
|
+ help
|
|
+ This setting defines how IMQ behaves in respect to its
|
|
+ hooking in PREROUTING and POSTROUTING.
|
|
+
|
|
+ Choosing this option will make IMQ hook like this:
|
|
+
|
|
+ PREROUTING: After NAT
|
|
+ POSTROUTING: After NAT
|
|
+
|
|
+ More information can be found at: www.linuximq.net
|
|
+
|
|
+ If not sure leave the default settings alone.
|
|
+
|
|
+config IMQ_BEHAVIOR_AB
|
|
+ bool "IMQ AB"
|
|
+ help
|
|
+ This setting defines how IMQ behaves in respect to its
|
|
+ hooking in PREROUTING and POSTROUTING.
|
|
+
|
|
+ Choosing this option will make IMQ hook like this:
|
|
+
|
|
+ PREROUTING: After NAT
|
|
+ POSTROUTING: Before NAT
|
|
+
|
|
+ More information can be found at: www.linuximq.net
|
|
+
|
|
+ If not sure leave the default settings alone.
|
|
+
|
|
+config IMQ_BEHAVIOR_BA
|
|
+ bool "IMQ BA"
|
|
+ help
|
|
+ This setting defines how IMQ behaves in respect to its
|
|
+ hooking in PREROUTING and POSTROUTING.
|
|
+
|
|
+ Choosing this option will make IMQ hook like this:
|
|
+
|
|
+ PREROUTING: Before NAT
|
|
+ POSTROUTING: After NAT
|
|
+
|
|
+ More information can be found at: www.linuximq.net
|
|
+
|
|
+ If not sure leave the default settings alone.
|
|
+
|
|
+config IMQ_BEHAVIOR_BB
|
|
+ bool "IMQ BB"
|
|
+ help
|
|
+ This setting defines how IMQ behaves in respect to its
|
|
+ hooking in PREROUTING and POSTROUTING.
|
|
+
|
|
+ Choosing this option will make IMQ hook like this:
|
|
+
|
|
+ PREROUTING: Before NAT
|
|
+ POSTROUTING: Before NAT
|
|
+
|
|
+ More information can be found at: www.linuximq.net
|
|
+
|
|
+ If not sure leave the default settings alone.
|
|
+
|
|
+endchoice
|
|
+
|
|
+config IMQ_NUM_DEVS
|
|
+ int "Number of IMQ devices"
|
|
+ range 2 16
|
|
+ depends on IMQ
|
|
+ default "16"
|
|
+ help
|
|
+ This setting defines how many IMQ devices will be created.
|
|
+
|
|
+ The default value is 16.
|
|
+
|
|
+ More information can be found at: www.linuximq.net
|
|
+
|
|
+ If not sure leave the default settings alone.
|
|
+
|
|
config TUN
|
|
tristate "Universal TUN/TAP device driver support"
|
|
select CRC32
|
|
diff -Naur linux-3.10.30.org/drivers/net/Makefile linux-3.10.30/drivers/net/Makefile
|
|
--- linux-3.10.30.org/drivers/net/Makefile 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/drivers/net/Makefile 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -9,6 +9,7 @@
|
|
obj-$(CONFIG_DUMMY) += dummy.o
|
|
obj-$(CONFIG_EQUALIZER) += eql.o
|
|
obj-$(CONFIG_IFB) += ifb.o
|
|
+obj-$(CONFIG_IMQ) += imq.o
|
|
obj-$(CONFIG_MACVLAN) += macvlan.o
|
|
obj-$(CONFIG_MACVTAP) += macvtap.o
|
|
obj-$(CONFIG_MII) += mii.o
|
|
diff -Naur linux-3.10.30.org/include/linux/imq.h linux-3.10.30/include/linux/imq.h
|
|
--- linux-3.10.30.org/include/linux/imq.h 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-3.10.30/include/linux/imq.h 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -0,0 +1,13 @@
|
|
+#ifndef _IMQ_H
|
|
+#define _IMQ_H
|
|
+
|
|
+/* IFMASK (16 device indexes, 0 to 15) and flag(s) fit in 5 bits */
|
|
+#define IMQ_F_BITS 5
|
|
+
|
|
+#define IMQ_F_IFMASK 0x0f
|
|
+#define IMQ_F_ENQUEUE 0x10
|
|
+
|
|
+#define IMQ_MAX_DEVS (IMQ_F_IFMASK + 1)
|
|
+
|
|
+#endif /* _IMQ_H */
|
|
+
|
|
diff -Naur linux-3.10.30.org/include/linux/netfilter/xt_IMQ.h linux-3.10.30/include/linux/netfilter/xt_IMQ.h
|
|
--- linux-3.10.30.org/include/linux/netfilter/xt_IMQ.h 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-3.10.30/include/linux/netfilter/xt_IMQ.h 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -0,0 +1,9 @@
|
|
+#ifndef _XT_IMQ_H
|
|
+#define _XT_IMQ_H
|
|
+
|
|
+struct xt_imq_info {
|
|
+ unsigned int todev; /* target imq device */
|
|
+};
|
|
+
|
|
+#endif /* _XT_IMQ_H */
|
|
+
|
|
diff -Naur linux-3.10.30.org/include/linux/netfilter_ipv4/ipt_IMQ.h linux-3.10.30/include/linux/netfilter_ipv4/ipt_IMQ.h
|
|
--- linux-3.10.30.org/include/linux/netfilter_ipv4/ipt_IMQ.h 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-3.10.30/include/linux/netfilter_ipv4/ipt_IMQ.h 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -0,0 +1,10 @@
|
|
+#ifndef _IPT_IMQ_H
|
|
+#define _IPT_IMQ_H
|
|
+
|
|
+/* Backwards compatibility for old userspace */
|
|
+#include <linux/netfilter/xt_IMQ.h>
|
|
+
|
|
+#define ipt_imq_info xt_imq_info
|
|
+
|
|
+#endif /* _IPT_IMQ_H */
|
|
+
|
|
diff -Naur linux-3.10.30.org/include/linux/netfilter_ipv6/ip6t_IMQ.h linux-3.10.30/include/linux/netfilter_ipv6/ip6t_IMQ.h
|
|
--- linux-3.10.30.org/include/linux/netfilter_ipv6/ip6t_IMQ.h 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-3.10.30/include/linux/netfilter_ipv6/ip6t_IMQ.h 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -0,0 +1,10 @@
|
|
+#ifndef _IP6T_IMQ_H
|
|
+#define _IP6T_IMQ_H
|
|
+
|
|
+/* Backwards compatibility for old userspace */
|
|
+#include <linux/netfilter/xt_IMQ.h>
|
|
+
|
|
+#define ip6t_imq_info xt_imq_info
|
|
+
|
|
+#endif /* _IP6T_IMQ_H */
|
|
+
|
|
diff -Naur linux-3.10.30.org/include/linux/skbuff.h linux-3.10.30/include/linux/skbuff.h
|
|
--- linux-3.10.30.org/include/linux/skbuff.h 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/include/linux/skbuff.h 2014-02-14 20:29:05.379402305 +0100
|
|
@@ -33,6 +33,9 @@
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/netdev_features.h>
|
|
#include <net/flow_keys.h>
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+#include <linux/imq.h>
|
|
+#endif
|
|
|
|
/* Don't change this without changing skb_csum_unnecessary! */
|
|
#define CHECKSUM_NONE 0
|
|
@@ -414,6 +417,9 @@
|
|
* first. This is owned by whoever has the skb queued ATM.
|
|
*/
|
|
char cb[48] __aligned(8);
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ void *cb_next;
|
|
+#endif
|
|
|
|
unsigned long _skb_refdst;
|
|
#ifdef CONFIG_XFRM
|
|
@@ -449,6 +455,9 @@
|
|
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
|
|
struct nf_conntrack *nfct;
|
|
#endif
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ struct nf_queue_entry *nf_queue_entry;
|
|
+#endif
|
|
#ifdef CONFIG_BRIDGE_NETFILTER
|
|
struct nf_bridge_info *nf_bridge;
|
|
#endif
|
|
@@ -487,7 +496,9 @@
|
|
__u8 encapsulation:1;
|
|
/* 7/9 bit hole (depending on ndisc_nodetype presence) */
|
|
kmemcheck_bitfield_end(flags2);
|
|
-
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ __u8 imq_flags:IMQ_F_BITS;
|
|
+#endif
|
|
#ifdef CONFIG_NET_DMA
|
|
dma_cookie_t dma_cookie;
|
|
#endif
|
|
@@ -616,7 +627,10 @@
|
|
{
|
|
return (struct rtable *)skb_dst(skb);
|
|
}
|
|
-
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+extern int skb_save_cb(struct sk_buff *skb);
|
|
+extern int skb_restore_cb(struct sk_buff *skb);
|
|
+#endif
|
|
extern void kfree_skb(struct sk_buff *skb);
|
|
extern void kfree_skb_list(struct sk_buff *segs);
|
|
extern void skb_tx_error(struct sk_buff *skb);
|
|
@@ -2735,6 +2749,10 @@
|
|
nf_conntrack_get(src->nfct);
|
|
dst->nfctinfo = src->nfctinfo;
|
|
#endif
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ dst->imq_flags = src->imq_flags;
|
|
+ dst->nf_queue_entry = src->nf_queue_entry;
|
|
+#endif
|
|
#ifdef CONFIG_BRIDGE_NETFILTER
|
|
dst->nf_bridge = src->nf_bridge;
|
|
nf_bridge_get(src->nf_bridge);
|
|
diff -Naur linux-3.10.30.org/include/net/netfilter/nf_queue.h linux-3.10.30/include/net/netfilter/nf_queue.h
|
|
--- linux-3.10.30.org/include/net/netfilter/nf_queue.h 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/include/net/netfilter/nf_queue.h 2014-02-14 20:29:05.382736249 +0100
|
|
@@ -29,6 +29,12 @@
|
|
void nf_register_queue_handler(const struct nf_queue_handler *qh);
|
|
void nf_unregister_queue_handler(void);
|
|
extern void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict);
|
|
+extern void nf_queue_entry_release_refs(struct nf_queue_entry *entry);
|
|
+
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+extern void nf_register_queue_imq_handler(const struct nf_queue_handler *qh);
|
|
+extern void nf_unregister_queue_imq_handler(void);
|
|
+#endif
|
|
|
|
bool nf_queue_entry_get_refs(struct nf_queue_entry *entry);
|
|
void nf_queue_entry_release_refs(struct nf_queue_entry *entry);
|
|
diff -Naur linux-3.10.30.org/include/uapi/linux/netfilter.h linux-3.10.30/include/uapi/linux/netfilter.h
|
|
--- linux-3.10.30.org/include/uapi/linux/netfilter.h 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/include/uapi/linux/netfilter.h 2014-02-14 20:29:05.382736249 +0100
|
|
@@ -13,7 +13,8 @@
|
|
#define NF_QUEUE 3
|
|
#define NF_REPEAT 4
|
|
#define NF_STOP 5
|
|
-#define NF_MAX_VERDICT NF_STOP
|
|
+#define NF_IMQ_QUEUE 6
|
|
+#define NF_MAX_VERDICT NF_IMQ_QUEUE
|
|
|
|
/* we overload the higher bits for encoding auxiliary data such as the queue
|
|
* number or errno values. Not nice, but better than additional function
|
|
diff -Naur linux-3.10.30.org/net/core/dev.c linux-3.10.30/net/core/dev.c
|
|
--- linux-3.10.30.org/net/core/dev.c 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/core/dev.c 2014-02-14 20:29:05.382736249 +0100
|
|
@@ -129,6 +129,9 @@
|
|
#include <linux/inetdevice.h>
|
|
#include <linux/cpu_rmap.h>
|
|
#include <linux/static_key.h>
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+#include <linux/imq.h>
|
|
+#endif
|
|
|
|
#include "net-sysfs.h"
|
|
|
|
@@ -2573,7 +2576,12 @@
|
|
}
|
|
}
|
|
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ if (!list_empty(&ptype_all) &&
|
|
+ !(skb->imq_flags & IMQ_F_ENQUEUE))
|
|
+#else
|
|
if (!list_empty(&ptype_all))
|
|
+#endif
|
|
dev_queue_xmit_nit(skb, dev);
|
|
|
|
skb_len = skb->len;
|
|
diff -Naur linux-3.10.30.org/net/core/skbuff.c linux-3.10.30/net/core/skbuff.c
|
|
--- linux-3.10.30.org/net/core/skbuff.c 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/core/skbuff.c 2014-02-14 21:47:17.286039229 +0100
|
|
@@ -73,6 +73,9 @@
|
|
|
|
struct kmem_cache *skbuff_head_cache __read_mostly;
|
|
static struct kmem_cache *skbuff_fclone_cache __read_mostly;
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+static struct kmem_cache *skbuff_cb_store_cache __read_mostly;
|
|
+#endif
|
|
|
|
/**
|
|
* skb_panic - private function for out-of-line support
|
|
@@ -552,6 +555,29 @@
|
|
WARN_ON(in_irq());
|
|
skb->destructor(skb);
|
|
}
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ /*
|
|
+ * This should not happen. When it does, avoid memleak by restoring
|
|
+ * the chain of cb-backups.
|
|
+ */
|
|
+ while (skb->cb_next != NULL) {
|
|
+ if (net_ratelimit())
|
|
+ pr_warn("IMQ: kfree_skb: skb->cb_next: %08x\n",
|
|
+ (unsigned int)skb->cb_next);
|
|
+
|
|
+ skb_restore_cb(skb);
|
|
+ }
|
|
+ /*
|
|
+ * This should not happen either, nf_queue_entry is nullified in
|
|
+ * imq_dev_xmit(). If we have non-NULL nf_queue_entry then we are
|
|
+ * leaking entry pointers, maybe memory. We don't know if this is
|
|
+ * pointer to already freed memory, or should this be freed.
|
|
+ * If this happens we need to add refcounting, etc for nf_queue_entry.
|
|
+ */
|
|
+ if (skb->nf_queue_entry && net_ratelimit())
|
|
+ pr_warn("%s\n", "IMQ: kfree_skb: skb->nf_queue_entry != NULL");
|
|
+#endif
|
|
+
|
|
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
|
|
nf_conntrack_put(skb->nfct);
|
|
#endif
|
|
@@ -683,6 +709,10 @@
|
|
new->sp = secpath_get(old->sp);
|
|
#endif
|
|
memcpy(new->cb, old->cb, sizeof(old->cb));
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ new->cb_next = NULL;
|
|
+ /*skb_copy_stored_cb(new, old);*/
|
|
+#endif
|
|
new->csum = old->csum;
|
|
new->local_df = old->local_df;
|
|
new->pkt_type = old->pkt_type;
|
|
@@ -3050,6 +3080,15 @@
|
|
}
|
|
EXPORT_SYMBOL_GPL(skb_gro_receive);
|
|
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+/* Control buffer save/restore for IMQ devices */
|
|
+struct skb_cb_table {
|
|
+ char cb[48] __aligned(8);
|
|
+ void *cb_next;
|
|
+ atomic_t refcnt;
|
|
+};
|
|
+#endif
|
|
+
|
|
void __init skb_init(void)
|
|
{
|
|
skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
|
|
@@ -3063,6 +3102,13 @@
|
|
0,
|
|
SLAB_HWCACHE_ALIGN|SLAB_PANIC,
|
|
NULL);
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ skbuff_cb_store_cache = kmem_cache_create("skbuff_cb_store_cache",
|
|
+ sizeof(struct skb_cb_table),
|
|
+ 0,
|
|
+ SLAB_HWCACHE_ALIGN|SLAB_PANIC,
|
|
+ NULL);
|
|
+#endif
|
|
}
|
|
|
|
/**
|
|
@@ -3348,6 +3394,76 @@
|
|
EXPORT_SYMBOL_GPL(skb_complete_wifi_ack);
|
|
|
|
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+static DEFINE_SPINLOCK(skb_cb_store_lock);
|
|
+
|
|
+int skb_save_cb(struct sk_buff *skb)
|
|
+{
|
|
+ struct skb_cb_table *next;
|
|
+
|
|
+ next = kmem_cache_alloc(skbuff_cb_store_cache, GFP_ATOMIC);
|
|
+ if (!next)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(skb->cb) != sizeof(next->cb));
|
|
+
|
|
+ memcpy(next->cb, skb->cb, sizeof(skb->cb));
|
|
+ next->cb_next = skb->cb_next;
|
|
+
|
|
+ atomic_set(&next->refcnt, 1);
|
|
+
|
|
+ skb->cb_next = next;
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(skb_save_cb);
|
|
+
|
|
+int skb_restore_cb(struct sk_buff *skb)
|
|
+{
|
|
+ struct skb_cb_table *next;
|
|
+
|
|
+ if (!skb->cb_next)
|
|
+ return 0;
|
|
+
|
|
+ next = skb->cb_next;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(skb->cb) != sizeof(next->cb));
|
|
+
|
|
+ memcpy(skb->cb, next->cb, sizeof(skb->cb));
|
|
+ skb->cb_next = next->cb_next;
|
|
+
|
|
+ spin_lock(&skb_cb_store_lock);
|
|
+
|
|
+ if (atomic_dec_and_test(&next->refcnt))
|
|
+ kmem_cache_free(skbuff_cb_store_cache, next);
|
|
+
|
|
+ spin_unlock(&skb_cb_store_lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(skb_restore_cb);
|
|
+
|
|
+static void skb_copy_stored_cb(struct sk_buff *new, const struct sk_buff *__old)
|
|
+{
|
|
+ struct skb_cb_table *next;
|
|
+ struct sk_buff *old;
|
|
+
|
|
+ if (!__old->cb_next) {
|
|
+ new->cb_next = NULL;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ spin_lock(&skb_cb_store_lock);
|
|
+
|
|
+ old = (struct sk_buff *)__old;
|
|
+
|
|
+ next = old->cb_next;
|
|
+ atomic_inc(&next->refcnt);
|
|
+ new->cb_next = next;
|
|
+
|
|
+ spin_unlock(&skb_cb_store_lock);
|
|
+}
|
|
+#endif
|
|
+
|
|
/**
|
|
* skb_partial_csum_set - set up and verify partial csum values for packet
|
|
* @skb: the skb to set
|
|
diff -Naur linux-3.10.30.org/net/ipv6/ip6_output.c linux-3.10.30/net/ipv6/ip6_output.c
|
|
--- linux-3.10.30.org/net/ipv6/ip6_output.c 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/ipv6/ip6_output.c 2014-02-14 20:29:05.392738001 +0100
|
|
@@ -89,9 +89,6 @@
|
|
struct in6_addr *nexthop;
|
|
int ret;
|
|
|
|
- skb->protocol = htons(ETH_P_IPV6);
|
|
- skb->dev = dev;
|
|
-
|
|
if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) {
|
|
struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
|
|
|
|
@@ -168,6 +165,13 @@
|
|
return 0;
|
|
}
|
|
|
|
+ /*
|
|
+ * IMQ-patch: moved setting skb->dev and skb->protocol from
|
|
+ * ip6_finish_output2 to fix crashing at netif_skb_features().
|
|
+ */
|
|
+ skb->protocol = htons(ETH_P_IPV6);
|
|
+ skb->dev = dev;
|
|
+
|
|
return NF_HOOK_COND(NFPROTO_IPV6, NF_INET_POST_ROUTING, skb, NULL, dev,
|
|
ip6_finish_output,
|
|
!(IP6CB(skb)->flags & IP6SKB_REROUTED));
|
|
diff -Naur linux-3.10.30.org/net/ipv6/ip6_output.c.orig linux-3.10.30/net/ipv6/ip6_output.c.orig
|
|
--- linux-3.10.30.org/net/ipv6/ip6_output.c.orig 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-3.10.30/net/ipv6/ip6_output.c.orig 2014-02-14 20:29:05.392738001 +0100
|
|
@@ -0,0 +1,1580 @@
|
|
+/*
|
|
+ * IPv6 output functions
|
|
+ * Linux INET6 implementation
|
|
+ *
|
|
+ * Authors:
|
|
+ * Pedro Roque <roque@di.fc.ul.pt>
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * H. von Brand : Added missing #include <linux/string.h>
|
|
+ * Imran Patel : frag id should be in NBO
|
|
+ * Kazunori MIYAZAWA @USAGI
|
|
+ * : add ip6_append_data and related functions
|
|
+ * for datagram xmit
|
|
+ */
|
|
+
|
|
+#include <linux/errno.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/socket.h>
|
|
+#include <linux/net.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/in6.h>
|
|
+#include <linux/tcp.h>
|
|
+#include <linux/route.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+
|
|
+#include <linux/netfilter.h>
|
|
+#include <linux/netfilter_ipv6.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>
|
|
+#include <net/xfrm.h>
|
|
+#include <net/checksum.h>
|
|
+#include <linux/mroute6.h>
|
|
+
|
|
+int __ip6_local_out(struct sk_buff *skb)
|
|
+{
|
|
+ int len;
|
|
+
|
|
+ len = skb->len - sizeof(struct ipv6hdr);
|
|
+ if (len > IPV6_MAXPLEN)
|
|
+ len = 0;
|
|
+ ipv6_hdr(skb)->payload_len = htons(len);
|
|
+
|
|
+ return nf_hook(NFPROTO_IPV6, NF_INET_LOCAL_OUT, skb, NULL,
|
|
+ skb_dst(skb)->dev, dst_output);
|
|
+}
|
|
+
|
|
+int ip6_local_out(struct sk_buff *skb)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = __ip6_local_out(skb);
|
|
+ if (likely(err == 1))
|
|
+ err = dst_output(skb);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ip6_local_out);
|
|
+
|
|
+static int ip6_finish_output2(struct sk_buff *skb)
|
|
+{
|
|
+ struct dst_entry *dst = skb_dst(skb);
|
|
+ struct net_device *dev = dst->dev;
|
|
+ struct neighbour *neigh;
|
|
+ struct in6_addr *nexthop;
|
|
+ int ret;
|
|
+
|
|
+ skb->protocol = htons(ETH_P_IPV6);
|
|
+ skb->dev = dev;
|
|
+
|
|
+ if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) {
|
|
+ struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
|
|
+
|
|
+ if (!(dev->flags & IFF_LOOPBACK) && sk_mc_loop(skb->sk) &&
|
|
+ ((mroute6_socket(dev_net(dev), skb) &&
|
|
+ !(IP6CB(skb)->flags & IP6SKB_FORWARDED)) ||
|
|
+ ipv6_chk_mcast_addr(dev, &ipv6_hdr(skb)->daddr,
|
|
+ &ipv6_hdr(skb)->saddr))) {
|
|
+ struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
|
|
+
|
|
+ /* Do not check for IFF_ALLMULTI; multicast routing
|
|
+ is not supported in any case.
|
|
+ */
|
|
+ if (newskb)
|
|
+ NF_HOOK(NFPROTO_IPV6, NF_INET_POST_ROUTING,
|
|
+ newskb, NULL, newskb->dev,
|
|
+ dev_loopback_xmit);
|
|
+
|
|
+ if (ipv6_hdr(skb)->hop_limit == 0) {
|
|
+ IP6_INC_STATS(dev_net(dev), idev,
|
|
+ IPSTATS_MIB_OUTDISCARDS);
|
|
+ kfree_skb(skb);
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ IP6_UPD_PO_STATS(dev_net(dev), idev, IPSTATS_MIB_OUTMCAST,
|
|
+ skb->len);
|
|
+
|
|
+ if (IPV6_ADDR_MC_SCOPE(&ipv6_hdr(skb)->daddr) <=
|
|
+ IPV6_ADDR_SCOPE_NODELOCAL &&
|
|
+ !(dev->flags & IFF_LOOPBACK)) {
|
|
+ kfree_skb(skb);
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ rcu_read_lock_bh();
|
|
+ nexthop = rt6_nexthop((struct rt6_info *)dst);
|
|
+ neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
|
|
+ if (unlikely(!neigh))
|
|
+ neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
|
|
+ if (!IS_ERR(neigh)) {
|
|
+ ret = dst_neigh_output(dst, neigh, skb);
|
|
+ rcu_read_unlock_bh();
|
|
+ return ret;
|
|
+ }
|
|
+ rcu_read_unlock_bh();
|
|
+
|
|
+ IP6_INC_STATS(dev_net(dst->dev),
|
|
+ ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
|
|
+ kfree_skb(skb);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int ip6_finish_output(struct sk_buff *skb)
|
|
+{
|
|
+ if ((skb->len > ip6_skb_dst_mtu(skb) && !skb_is_gso(skb)) ||
|
|
+ dst_allfrag(skb_dst(skb)) ||
|
|
+ (IP6CB(skb)->frag_max_size && skb->len > IP6CB(skb)->frag_max_size))
|
|
+ return ip6_fragment(skb, ip6_finish_output2);
|
|
+ else
|
|
+ return ip6_finish_output2(skb);
|
|
+}
|
|
+
|
|
+int ip6_output(struct sk_buff *skb)
|
|
+{
|
|
+ struct net_device *dev = skb_dst(skb)->dev;
|
|
+ struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
|
|
+ if (unlikely(idev->cnf.disable_ipv6)) {
|
|
+ IP6_INC_STATS(dev_net(dev), idev,
|
|
+ IPSTATS_MIB_OUTDISCARDS);
|
|
+ kfree_skb(skb);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return NF_HOOK_COND(NFPROTO_IPV6, NF_INET_POST_ROUTING, skb, NULL, dev,
|
|
+ ip6_finish_output,
|
|
+ !(IP6CB(skb)->flags & IP6SKB_REROUTED));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * xmit an sk_buff (used by TCP, SCTP and DCCP)
|
|
+ */
|
|
+
|
|
+int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi6 *fl6,
|
|
+ struct ipv6_txoptions *opt, int tclass)
|
|
+{
|
|
+ struct net *net = sock_net(sk);
|
|
+ struct ipv6_pinfo *np = inet6_sk(sk);
|
|
+ struct in6_addr *first_hop = &fl6->daddr;
|
|
+ struct dst_entry *dst = skb_dst(skb);
|
|
+ struct ipv6hdr *hdr;
|
|
+ u8 proto = fl6->flowi6_proto;
|
|
+ int seg_len = skb->len;
|
|
+ int hlimit = -1;
|
|
+ u32 mtu;
|
|
+
|
|
+ if (opt) {
|
|
+ unsigned 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) + LL_RESERVED_SPACE(dst->dev);
|
|
+
|
|
+ if (skb_headroom(skb) < head_room) {
|
|
+ struct sk_buff *skb2 = skb_realloc_headroom(skb, head_room);
|
|
+ if (skb2 == NULL) {
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_OUTDISCARDS);
|
|
+ kfree_skb(skb);
|
|
+ return -ENOBUFS;
|
|
+ }
|
|
+ consume_skb(skb);
|
|
+ skb = skb2;
|
|
+ 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);
|
|
+ }
|
|
+
|
|
+ skb_push(skb, sizeof(struct ipv6hdr));
|
|
+ skb_reset_network_header(skb);
|
|
+ hdr = ipv6_hdr(skb);
|
|
+
|
|
+ /*
|
|
+ * Fill in the IPv6 header
|
|
+ */
|
|
+ if (np)
|
|
+ hlimit = np->hop_limit;
|
|
+ if (hlimit < 0)
|
|
+ hlimit = ip6_dst_hoplimit(dst);
|
|
+
|
|
+ ip6_flow_hdr(hdr, tclass, fl6->flowlabel);
|
|
+
|
|
+ hdr->payload_len = htons(seg_len);
|
|
+ hdr->nexthdr = proto;
|
|
+ hdr->hop_limit = hlimit;
|
|
+
|
|
+ hdr->saddr = fl6->saddr;
|
|
+ hdr->daddr = *first_hop;
|
|
+
|
|
+ skb->priority = sk->sk_priority;
|
|
+ skb->mark = sk->sk_mark;
|
|
+
|
|
+ mtu = dst_mtu(dst);
|
|
+ if ((skb->len <= mtu) || skb->local_df || skb_is_gso(skb)) {
|
|
+ IP6_UPD_PO_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_OUT, skb->len);
|
|
+ return NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, skb, NULL,
|
|
+ dst->dev, dst_output);
|
|
+ }
|
|
+
|
|
+ skb->dev = dst->dev;
|
|
+ ipv6_local_error(sk, EMSGSIZE, fl6, mtu);
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), IPSTATS_MIB_FRAGFAILS);
|
|
+ kfree_skb(skb);
|
|
+ return -EMSGSIZE;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL(ip6_xmit);
|
|
+
|
|
+static int ip6_call_ra_chain(struct sk_buff *skb, int sel)
|
|
+{
|
|
+ struct ip6_ra_chain *ra;
|
|
+ struct sock *last = NULL;
|
|
+
|
|
+ read_lock(&ip6_ra_lock);
|
|
+ for (ra = ip6_ra_chain; ra; ra = ra->next) {
|
|
+ struct sock *sk = ra->sk;
|
|
+ if (sk && ra->sel == sel &&
|
|
+ (!sk->sk_bound_dev_if ||
|
|
+ sk->sk_bound_dev_if == skb->dev->ifindex)) {
|
|
+ if (last) {
|
|
+ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
+ if (skb2)
|
|
+ rawv6_rcv(last, skb2);
|
|
+ }
|
|
+ last = sk;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (last) {
|
|
+ rawv6_rcv(last, skb);
|
|
+ read_unlock(&ip6_ra_lock);
|
|
+ return 1;
|
|
+ }
|
|
+ read_unlock(&ip6_ra_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ip6_forward_proxy_check(struct sk_buff *skb)
|
|
+{
|
|
+ struct ipv6hdr *hdr = ipv6_hdr(skb);
|
|
+ u8 nexthdr = hdr->nexthdr;
|
|
+ __be16 frag_off;
|
|
+ int offset;
|
|
+
|
|
+ if (ipv6_ext_hdr(nexthdr)) {
|
|
+ offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr, &frag_off);
|
|
+ if (offset < 0)
|
|
+ return 0;
|
|
+ } else
|
|
+ offset = sizeof(struct ipv6hdr);
|
|
+
|
|
+ if (nexthdr == IPPROTO_ICMPV6) {
|
|
+ struct icmp6hdr *icmp6;
|
|
+
|
|
+ if (!pskb_may_pull(skb, (skb_network_header(skb) +
|
|
+ offset + 1 - skb->data)))
|
|
+ return 0;
|
|
+
|
|
+ icmp6 = (struct icmp6hdr *)(skb_network_header(skb) + offset);
|
|
+
|
|
+ switch (icmp6->icmp6_type) {
|
|
+ case NDISC_ROUTER_SOLICITATION:
|
|
+ case NDISC_ROUTER_ADVERTISEMENT:
|
|
+ case NDISC_NEIGHBOUR_SOLICITATION:
|
|
+ case NDISC_NEIGHBOUR_ADVERTISEMENT:
|
|
+ case NDISC_REDIRECT:
|
|
+ /* For reaction involving unicast neighbor discovery
|
|
+ * message destined to the proxied address, pass it to
|
|
+ * input function.
|
|
+ */
|
|
+ return 1;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The proxying router can't forward traffic sent to a link-local
|
|
+ * address, so signal the sender and discard the packet. This
|
|
+ * behavior is clarified by the MIPv6 specification.
|
|
+ */
|
|
+ if (ipv6_addr_type(&hdr->daddr) & IPV6_ADDR_LINKLOCAL) {
|
|
+ dst_link_failure(skb);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline int ip6_forward_finish(struct sk_buff *skb)
|
|
+{
|
|
+ return dst_output(skb);
|
|
+}
|
|
+
|
|
+int ip6_forward(struct sk_buff *skb)
|
|
+{
|
|
+ struct dst_entry *dst = skb_dst(skb);
|
|
+ struct ipv6hdr *hdr = ipv6_hdr(skb);
|
|
+ struct inet6_skb_parm *opt = IP6CB(skb);
|
|
+ struct net *net = dev_net(dst->dev);
|
|
+ u32 mtu;
|
|
+
|
|
+ if (net->ipv6.devconf_all->forwarding == 0)
|
|
+ goto error;
|
|
+
|
|
+ if (skb_warn_if_lro(skb))
|
|
+ goto drop;
|
|
+
|
|
+ if (!xfrm6_policy_check(NULL, XFRM_POLICY_FWD, skb)) {
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_INDISCARDS);
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+ if (skb->pkt_type != PACKET_HOST)
|
|
+ goto drop;
|
|
+
|
|
+ skb_forward_csum(skb);
|
|
+
|
|
+ /*
|
|
+ * We DO NOT make any processing on
|
|
+ * RA packets, pushing them to user level AS IS
|
|
+ * without ane WARRANTY that application will be able
|
|
+ * to interpret them. The reason is that we
|
|
+ * cannot make anything clever here.
|
|
+ *
|
|
+ * We are not end-node, so that if packet contains
|
|
+ * AH/ESP, we cannot make anything.
|
|
+ * Defragmentation also would be mistake, RA packets
|
|
+ * cannot be fragmented, because there is no warranty
|
|
+ * that different fragments will go along one path. --ANK
|
|
+ */
|
|
+ if (unlikely(opt->flags & IP6SKB_ROUTERALERT)) {
|
|
+ if (ip6_call_ra_chain(skb, ntohs(opt->ra)))
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * check and decrement ttl
|
|
+ */
|
|
+ if (hdr->hop_limit <= 1) {
|
|
+ /* Force OUTPUT device used as source address */
|
|
+ skb->dev = dst->dev;
|
|
+ icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT, 0);
|
|
+ IP6_INC_STATS_BH(net,
|
|
+ ip6_dst_idev(dst), IPSTATS_MIB_INHDRERRORS);
|
|
+
|
|
+ kfree_skb(skb);
|
|
+ return -ETIMEDOUT;
|
|
+ }
|
|
+
|
|
+ /* XXX: idev->cnf.proxy_ndp? */
|
|
+ if (net->ipv6.devconf_all->proxy_ndp &&
|
|
+ pneigh_lookup(&nd_tbl, net, &hdr->daddr, skb->dev, 0)) {
|
|
+ int proxied = ip6_forward_proxy_check(skb);
|
|
+ if (proxied > 0)
|
|
+ return ip6_input(skb);
|
|
+ else if (proxied < 0) {
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(dst),
|
|
+ IPSTATS_MIB_INDISCARDS);
|
|
+ goto drop;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!xfrm6_route_forward(skb)) {
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_INDISCARDS);
|
|
+ goto drop;
|
|
+ }
|
|
+ dst = skb_dst(skb);
|
|
+
|
|
+ /* IPv6 specs say nothing about it, but it is clear that we cannot
|
|
+ send redirects to source routed frames.
|
|
+ We don't send redirects to frames decapsulated from IPsec.
|
|
+ */
|
|
+ if (skb->dev == dst->dev && opt->srcrt == 0 && !skb_sec_path(skb)) {
|
|
+ struct in6_addr *target = NULL;
|
|
+ struct inet_peer *peer;
|
|
+ struct rt6_info *rt;
|
|
+
|
|
+ /*
|
|
+ * incoming and outgoing devices are the same
|
|
+ * send a redirect.
|
|
+ */
|
|
+
|
|
+ rt = (struct rt6_info *) dst;
|
|
+ if (rt->rt6i_flags & RTF_GATEWAY)
|
|
+ target = &rt->rt6i_gateway;
|
|
+ else
|
|
+ target = &hdr->daddr;
|
|
+
|
|
+ peer = inet_getpeer_v6(net->ipv6.peers, &rt->rt6i_dst.addr, 1);
|
|
+
|
|
+ /* Limit redirects both by destination (here)
|
|
+ and by source (inside ndisc_send_redirect)
|
|
+ */
|
|
+ if (inet_peer_xrlim_allow(peer, 1*HZ))
|
|
+ ndisc_send_redirect(skb, target);
|
|
+ if (peer)
|
|
+ inet_putpeer(peer);
|
|
+ } else {
|
|
+ int addrtype = ipv6_addr_type(&hdr->saddr);
|
|
+
|
|
+ /* This check is security critical. */
|
|
+ if (addrtype == IPV6_ADDR_ANY ||
|
|
+ addrtype & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LOOPBACK))
|
|
+ goto error;
|
|
+ if (addrtype & IPV6_ADDR_LINKLOCAL) {
|
|
+ icmpv6_send(skb, ICMPV6_DEST_UNREACH,
|
|
+ ICMPV6_NOT_NEIGHBOUR, 0);
|
|
+ goto error;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mtu = dst_mtu(dst);
|
|
+ if (mtu < IPV6_MIN_MTU)
|
|
+ mtu = IPV6_MIN_MTU;
|
|
+
|
|
+ if ((!skb->local_df && skb->len > mtu && !skb_is_gso(skb)) ||
|
|
+ (IP6CB(skb)->frag_max_size && IP6CB(skb)->frag_max_size > mtu)) {
|
|
+ /* Again, force OUTPUT device used as source address */
|
|
+ skb->dev = dst->dev;
|
|
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
|
|
+ IP6_INC_STATS_BH(net,
|
|
+ ip6_dst_idev(dst), IPSTATS_MIB_INTOOBIGERRORS);
|
|
+ IP6_INC_STATS_BH(net,
|
|
+ ip6_dst_idev(dst), IPSTATS_MIB_FRAGFAILS);
|
|
+ kfree_skb(skb);
|
|
+ return -EMSGSIZE;
|
|
+ }
|
|
+
|
|
+ if (skb_cow(skb, dst->dev->hard_header_len)) {
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTDISCARDS);
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+ hdr = ipv6_hdr(skb);
|
|
+
|
|
+ /* Mangling hops number delayed to point after skb COW */
|
|
+
|
|
+ hdr->hop_limit--;
|
|
+
|
|
+ IP6_INC_STATS_BH(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTFORWDATAGRAMS);
|
|
+ IP6_ADD_STATS_BH(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTOCTETS, skb->len);
|
|
+ return NF_HOOK(NFPROTO_IPV6, NF_INET_FORWARD, skb, skb->dev, dst->dev,
|
|
+ ip6_forward_finish);
|
|
+
|
|
+error:
|
|
+ IP6_INC_STATS_BH(net, ip6_dst_idev(dst), IPSTATS_MIB_INADDRERRORS);
|
|
+drop:
|
|
+ kfree_skb(skb);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static void ip6_copy_metadata(struct sk_buff *to, struct sk_buff *from)
|
|
+{
|
|
+ to->pkt_type = from->pkt_type;
|
|
+ to->priority = from->priority;
|
|
+ to->protocol = from->protocol;
|
|
+ skb_dst_drop(to);
|
|
+ skb_dst_set(to, dst_clone(skb_dst(from)));
|
|
+ to->dev = from->dev;
|
|
+ to->mark = from->mark;
|
|
+
|
|
+#ifdef CONFIG_NET_SCHED
|
|
+ to->tc_index = from->tc_index;
|
|
+#endif
|
|
+ nf_copy(to, from);
|
|
+#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
|
|
+ to->nf_trace = from->nf_trace;
|
|
+#endif
|
|
+ skb_copy_secmark(to, from);
|
|
+}
|
|
+
|
|
+int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
|
|
+{
|
|
+ struct sk_buff *frag;
|
|
+ struct rt6_info *rt = (struct rt6_info*)skb_dst(skb);
|
|
+ struct ipv6_pinfo *np = skb->sk ? inet6_sk(skb->sk) : NULL;
|
|
+ struct ipv6hdr *tmp_hdr;
|
|
+ struct frag_hdr *fh;
|
|
+ unsigned int mtu, hlen, left, len;
|
|
+ int hroom, troom;
|
|
+ __be32 frag_id = 0;
|
|
+ int ptr, offset = 0, err=0;
|
|
+ u8 *prevhdr, nexthdr = 0;
|
|
+ struct net *net = dev_net(skb_dst(skb)->dev);
|
|
+
|
|
+ hlen = ip6_find_1stfragopt(skb, &prevhdr);
|
|
+ nexthdr = *prevhdr;
|
|
+
|
|
+ mtu = ip6_skb_dst_mtu(skb);
|
|
+
|
|
+ /* We must not fragment if the socket is set to force MTU discovery
|
|
+ * or if the skb it not generated by a local socket.
|
|
+ */
|
|
+ if (unlikely(!skb->local_df && skb->len > mtu) ||
|
|
+ (IP6CB(skb)->frag_max_size &&
|
|
+ IP6CB(skb)->frag_max_size > mtu)) {
|
|
+ if (skb->sk && dst_allfrag(skb_dst(skb)))
|
|
+ sk_nocaps_add(skb->sk, NETIF_F_GSO_MASK);
|
|
+
|
|
+ skb->dev = skb_dst(skb)->dev;
|
|
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_FRAGFAILS);
|
|
+ kfree_skb(skb);
|
|
+ return -EMSGSIZE;
|
|
+ }
|
|
+
|
|
+ if (np && np->frag_size < mtu) {
|
|
+ if (np->frag_size)
|
|
+ mtu = np->frag_size;
|
|
+ }
|
|
+ mtu -= hlen + sizeof(struct frag_hdr);
|
|
+
|
|
+ if (skb_has_frag_list(skb)) {
|
|
+ int first_len = skb_pagelen(skb);
|
|
+ struct sk_buff *frag2;
|
|
+
|
|
+ if (first_len - hlen > mtu ||
|
|
+ ((first_len - hlen) & 7) ||
|
|
+ skb_cloned(skb))
|
|
+ goto slow_path;
|
|
+
|
|
+ skb_walk_frags(skb, frag) {
|
|
+ /* Correct geometry. */
|
|
+ if (frag->len > mtu ||
|
|
+ ((frag->len & 7) && frag->next) ||
|
|
+ skb_headroom(frag) < hlen)
|
|
+ goto slow_path_clean;
|
|
+
|
|
+ /* Partially cloned skb? */
|
|
+ if (skb_shared(frag))
|
|
+ goto slow_path_clean;
|
|
+
|
|
+ BUG_ON(frag->sk);
|
|
+ if (skb->sk) {
|
|
+ frag->sk = skb->sk;
|
|
+ frag->destructor = sock_wfree;
|
|
+ }
|
|
+ skb->truesize -= frag->truesize;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ offset = 0;
|
|
+ frag = skb_shinfo(skb)->frag_list;
|
|
+ skb_frag_list_init(skb);
|
|
+ /* BUILD HEADER */
|
|
+
|
|
+ *prevhdr = NEXTHDR_FRAGMENT;
|
|
+ tmp_hdr = kmemdup(skb_network_header(skb), hlen, GFP_ATOMIC);
|
|
+ if (!tmp_hdr) {
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_FRAGFAILS);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ __skb_pull(skb, hlen);
|
|
+ fh = (struct frag_hdr*)__skb_push(skb, sizeof(struct frag_hdr));
|
|
+ __skb_push(skb, hlen);
|
|
+ skb_reset_network_header(skb);
|
|
+ memcpy(skb_network_header(skb), tmp_hdr, hlen);
|
|
+
|
|
+ ipv6_select_ident(fh, rt);
|
|
+ fh->nexthdr = nexthdr;
|
|
+ fh->reserved = 0;
|
|
+ fh->frag_off = htons(IP6_MF);
|
|
+ frag_id = fh->identification;
|
|
+
|
|
+ first_len = skb_pagelen(skb);
|
|
+ skb->data_len = first_len - skb_headlen(skb);
|
|
+ skb->len = first_len;
|
|
+ ipv6_hdr(skb)->payload_len = htons(first_len -
|
|
+ sizeof(struct ipv6hdr));
|
|
+
|
|
+ dst_hold(&rt->dst);
|
|
+
|
|
+ for (;;) {
|
|
+ /* Prepare header of the next frame,
|
|
+ * before previous one went down. */
|
|
+ if (frag) {
|
|
+ frag->ip_summed = CHECKSUM_NONE;
|
|
+ skb_reset_transport_header(frag);
|
|
+ fh = (struct frag_hdr*)__skb_push(frag, sizeof(struct frag_hdr));
|
|
+ __skb_push(frag, hlen);
|
|
+ skb_reset_network_header(frag);
|
|
+ memcpy(skb_network_header(frag), tmp_hdr,
|
|
+ hlen);
|
|
+ offset += skb->len - hlen - sizeof(struct frag_hdr);
|
|
+ fh->nexthdr = nexthdr;
|
|
+ fh->reserved = 0;
|
|
+ fh->frag_off = htons(offset);
|
|
+ if (frag->next != NULL)
|
|
+ fh->frag_off |= htons(IP6_MF);
|
|
+ fh->identification = frag_id;
|
|
+ ipv6_hdr(frag)->payload_len =
|
|
+ htons(frag->len -
|
|
+ sizeof(struct ipv6hdr));
|
|
+ ip6_copy_metadata(frag, skb);
|
|
+ }
|
|
+
|
|
+ err = output(skb);
|
|
+ if(!err)
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),
|
|
+ IPSTATS_MIB_FRAGCREATES);
|
|
+
|
|
+ if (err || !frag)
|
|
+ break;
|
|
+
|
|
+ skb = frag;
|
|
+ frag = skb->next;
|
|
+ skb->next = NULL;
|
|
+ }
|
|
+
|
|
+ kfree(tmp_hdr);
|
|
+
|
|
+ if (err == 0) {
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),
|
|
+ IPSTATS_MIB_FRAGOKS);
|
|
+ ip6_rt_put(rt);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ while (frag) {
|
|
+ skb = frag->next;
|
|
+ kfree_skb(frag);
|
|
+ frag = skb;
|
|
+ }
|
|
+
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),
|
|
+ IPSTATS_MIB_FRAGFAILS);
|
|
+ ip6_rt_put(rt);
|
|
+ return err;
|
|
+
|
|
+slow_path_clean:
|
|
+ skb_walk_frags(skb, frag2) {
|
|
+ if (frag2 == frag)
|
|
+ break;
|
|
+ frag2->sk = NULL;
|
|
+ frag2->destructor = NULL;
|
|
+ skb->truesize += frag2->truesize;
|
|
+ }
|
|
+ }
|
|
+
|
|
+slow_path:
|
|
+ if ((skb->ip_summed == CHECKSUM_PARTIAL) &&
|
|
+ skb_checksum_help(skb))
|
|
+ goto fail;
|
|
+
|
|
+ left = skb->len - hlen; /* Space per frame */
|
|
+ ptr = hlen; /* Where to start from */
|
|
+
|
|
+ /*
|
|
+ * Fragment the datagram.
|
|
+ */
|
|
+
|
|
+ *prevhdr = NEXTHDR_FRAGMENT;
|
|
+ hroom = LL_RESERVED_SPACE(rt->dst.dev);
|
|
+ troom = rt->dst.dev->needed_tailroom;
|
|
+
|
|
+ /*
|
|
+ * Keep copying data until we run out.
|
|
+ */
|
|
+ while(left > 0) {
|
|
+ len = left;
|
|
+ /* IF: it doesn't fit, use 'mtu' - the data space left */
|
|
+ if (len > mtu)
|
|
+ len = mtu;
|
|
+ /* IF: we are not sending up to and including the packet end
|
|
+ then align the next start on an eight byte boundary */
|
|
+ if (len < left) {
|
|
+ len &= ~7;
|
|
+ }
|
|
+ /*
|
|
+ * Allocate buffer.
|
|
+ */
|
|
+
|
|
+ if ((frag = alloc_skb(len + hlen + sizeof(struct frag_hdr) +
|
|
+ hroom + troom, GFP_ATOMIC)) == NULL) {
|
|
+ NETDEBUG(KERN_INFO "IPv6: frag: no memory for new fragment!\n");
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_FRAGFAILS);
|
|
+ err = -ENOMEM;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Set up data on packet
|
|
+ */
|
|
+
|
|
+ ip6_copy_metadata(frag, skb);
|
|
+ skb_reserve(frag, hroom);
|
|
+ skb_put(frag, len + hlen + sizeof(struct frag_hdr));
|
|
+ skb_reset_network_header(frag);
|
|
+ fh = (struct frag_hdr *)(skb_network_header(frag) + hlen);
|
|
+ frag->transport_header = (frag->network_header + hlen +
|
|
+ sizeof(struct frag_hdr));
|
|
+
|
|
+ /*
|
|
+ * Charge the memory for the fragment to any owner
|
|
+ * it might possess
|
|
+ */
|
|
+ if (skb->sk)
|
|
+ skb_set_owner_w(frag, skb->sk);
|
|
+
|
|
+ /*
|
|
+ * Copy the packet header into the new buffer.
|
|
+ */
|
|
+ skb_copy_from_linear_data(skb, skb_network_header(frag), hlen);
|
|
+
|
|
+ /*
|
|
+ * Build fragment header.
|
|
+ */
|
|
+ fh->nexthdr = nexthdr;
|
|
+ fh->reserved = 0;
|
|
+ if (!frag_id) {
|
|
+ ipv6_select_ident(fh, rt);
|
|
+ frag_id = fh->identification;
|
|
+ } else
|
|
+ fh->identification = frag_id;
|
|
+
|
|
+ /*
|
|
+ * Copy a block of the IP datagram.
|
|
+ */
|
|
+ if (skb_copy_bits(skb, ptr, skb_transport_header(frag), len))
|
|
+ BUG();
|
|
+ left -= len;
|
|
+
|
|
+ fh->frag_off = htons(offset);
|
|
+ if (left > 0)
|
|
+ fh->frag_off |= htons(IP6_MF);
|
|
+ ipv6_hdr(frag)->payload_len = htons(frag->len -
|
|
+ sizeof(struct ipv6hdr));
|
|
+
|
|
+ ptr += len;
|
|
+ offset += len;
|
|
+
|
|
+ /*
|
|
+ * Put this fragment into the sending queue.
|
|
+ */
|
|
+ err = output(frag);
|
|
+ if (err)
|
|
+ goto fail;
|
|
+
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_FRAGCREATES);
|
|
+ }
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_FRAGOKS);
|
|
+ consume_skb(skb);
|
|
+ return err;
|
|
+
|
|
+fail:
|
|
+ IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_FRAGFAILS);
|
|
+ kfree_skb(skb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline int ip6_rt_check(const struct rt6key *rt_key,
|
|
+ const struct in6_addr *fl_addr,
|
|
+ const struct in6_addr *addr_cache)
|
|
+{
|
|
+ return (rt_key->plen != 128 || !ipv6_addr_equal(fl_addr, &rt_key->addr)) &&
|
|
+ (addr_cache == NULL || !ipv6_addr_equal(fl_addr, addr_cache));
|
|
+}
|
|
+
|
|
+static struct dst_entry *ip6_sk_dst_check(struct sock *sk,
|
|
+ struct dst_entry *dst,
|
|
+ const struct flowi6 *fl6)
|
|
+{
|
|
+ struct ipv6_pinfo *np = inet6_sk(sk);
|
|
+ struct rt6_info *rt;
|
|
+
|
|
+ if (!dst)
|
|
+ goto out;
|
|
+
|
|
+ if (dst->ops->family != AF_INET6) {
|
|
+ dst_release(dst);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ rt = (struct rt6_info *)dst;
|
|
+ /* Yes, checking route validity in not connected
|
|
+ * case is not very simple. Take into account,
|
|
+ * that we do not support routing by source, TOS,
|
|
+ * and MSG_DONTROUTE --ANK (980726)
|
|
+ *
|
|
+ * 1. ip6_rt_check(): If route was host route,
|
|
+ * check that cached destination is current.
|
|
+ * If it is network route, we still may
|
|
+ * check its validity using saved pointer
|
|
+ * to the last used address: daddr_cache.
|
|
+ * We do not want to save whole address now,
|
|
+ * (because main consumer of this service
|
|
+ * is tcp, which has not this problem),
|
|
+ * so that the last trick works only on connected
|
|
+ * sockets.
|
|
+ * 2. oif also should be the same.
|
|
+ */
|
|
+ if (ip6_rt_check(&rt->rt6i_dst, &fl6->daddr, np->daddr_cache) ||
|
|
+#ifdef CONFIG_IPV6_SUBTREES
|
|
+ ip6_rt_check(&rt->rt6i_src, &fl6->saddr, np->saddr_cache) ||
|
|
+#endif
|
|
+ (fl6->flowi6_oif && fl6->flowi6_oif != dst->dev->ifindex)) {
|
|
+ dst_release(dst);
|
|
+ dst = NULL;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return dst;
|
|
+}
|
|
+
|
|
+static int ip6_dst_lookup_tail(struct sock *sk,
|
|
+ struct dst_entry **dst, struct flowi6 *fl6)
|
|
+{
|
|
+ struct net *net = sock_net(sk);
|
|
+#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
|
|
+ struct neighbour *n;
|
|
+ struct rt6_info *rt;
|
|
+#endif
|
|
+ int err;
|
|
+
|
|
+ if (*dst == NULL)
|
|
+ *dst = ip6_route_output(net, sk, fl6);
|
|
+
|
|
+ if ((err = (*dst)->error))
|
|
+ goto out_err_release;
|
|
+
|
|
+ if (ipv6_addr_any(&fl6->saddr)) {
|
|
+ struct rt6_info *rt = (struct rt6_info *) *dst;
|
|
+ err = ip6_route_get_saddr(net, rt, &fl6->daddr,
|
|
+ sk ? inet6_sk(sk)->srcprefs : 0,
|
|
+ &fl6->saddr);
|
|
+ if (err)
|
|
+ goto out_err_release;
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
|
|
+ /*
|
|
+ * Here if the dst entry we've looked up
|
|
+ * has a neighbour entry that is in the INCOMPLETE
|
|
+ * state and the src address from the flow is
|
|
+ * marked as OPTIMISTIC, we release the found
|
|
+ * dst entry and replace it instead with the
|
|
+ * dst entry of the nexthop router
|
|
+ */
|
|
+ rt = (struct rt6_info *) *dst;
|
|
+ rcu_read_lock_bh();
|
|
+ n = __ipv6_neigh_lookup_noref(rt->dst.dev, rt6_nexthop(rt));
|
|
+ err = n && !(n->nud_state & NUD_VALID) ? -EINVAL : 0;
|
|
+ rcu_read_unlock_bh();
|
|
+
|
|
+ if (err) {
|
|
+ struct inet6_ifaddr *ifp;
|
|
+ struct flowi6 fl_gw6;
|
|
+ int redirect;
|
|
+
|
|
+ ifp = ipv6_get_ifaddr(net, &fl6->saddr,
|
|
+ (*dst)->dev, 1);
|
|
+
|
|
+ redirect = (ifp && ifp->flags & IFA_F_OPTIMISTIC);
|
|
+ if (ifp)
|
|
+ in6_ifa_put(ifp);
|
|
+
|
|
+ if (redirect) {
|
|
+ /*
|
|
+ * We need to get the dst entry for the
|
|
+ * default router instead
|
|
+ */
|
|
+ dst_release(*dst);
|
|
+ memcpy(&fl_gw6, fl6, sizeof(struct flowi6));
|
|
+ memset(&fl_gw6.daddr, 0, sizeof(struct in6_addr));
|
|
+ *dst = ip6_route_output(net, sk, &fl_gw6);
|
|
+ if ((err = (*dst)->error))
|
|
+ goto out_err_release;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ return 0;
|
|
+
|
|
+out_err_release:
|
|
+ if (err == -ENETUNREACH)
|
|
+ IP6_INC_STATS_BH(net, NULL, IPSTATS_MIB_OUTNOROUTES);
|
|
+ dst_release(*dst);
|
|
+ *dst = NULL;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * ip6_dst_lookup - perform route lookup on flow
|
|
+ * @sk: socket which provides route info
|
|
+ * @dst: pointer to dst_entry * for result
|
|
+ * @fl6: flow to lookup
|
|
+ *
|
|
+ * This function performs a route lookup on the given flow.
|
|
+ *
|
|
+ * It returns zero on success, or a standard errno code on error.
|
|
+ */
|
|
+int ip6_dst_lookup(struct sock *sk, struct dst_entry **dst, struct flowi6 *fl6)
|
|
+{
|
|
+ *dst = NULL;
|
|
+ return ip6_dst_lookup_tail(sk, dst, fl6);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ip6_dst_lookup);
|
|
+
|
|
+/**
|
|
+ * ip6_dst_lookup_flow - perform route lookup on flow with ipsec
|
|
+ * @sk: socket which provides route info
|
|
+ * @fl6: flow to lookup
|
|
+ * @final_dst: final destination address for ipsec lookup
|
|
+ * @can_sleep: we are in a sleepable context
|
|
+ *
|
|
+ * This function performs a route lookup on the given flow.
|
|
+ *
|
|
+ * It returns a valid dst pointer on success, or a pointer encoded
|
|
+ * error code.
|
|
+ */
|
|
+struct dst_entry *ip6_dst_lookup_flow(struct sock *sk, struct flowi6 *fl6,
|
|
+ const struct in6_addr *final_dst,
|
|
+ bool can_sleep)
|
|
+{
|
|
+ struct dst_entry *dst = NULL;
|
|
+ int err;
|
|
+
|
|
+ err = ip6_dst_lookup_tail(sk, &dst, fl6);
|
|
+ if (err)
|
|
+ return ERR_PTR(err);
|
|
+ if (final_dst)
|
|
+ fl6->daddr = *final_dst;
|
|
+ if (can_sleep)
|
|
+ fl6->flowi6_flags |= FLOWI_FLAG_CAN_SLEEP;
|
|
+
|
|
+ return xfrm_lookup(sock_net(sk), dst, flowi6_to_flowi(fl6), sk, 0);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ip6_dst_lookup_flow);
|
|
+
|
|
+/**
|
|
+ * ip6_sk_dst_lookup_flow - perform socket cached route lookup on flow
|
|
+ * @sk: socket which provides the dst cache and route info
|
|
+ * @fl6: flow to lookup
|
|
+ * @final_dst: final destination address for ipsec lookup
|
|
+ * @can_sleep: we are in a sleepable context
|
|
+ *
|
|
+ * This function performs a route lookup on the given flow with the
|
|
+ * possibility of using the cached route in the socket if it is valid.
|
|
+ * It will take the socket dst lock when operating on the dst cache.
|
|
+ * As a result, this function can only be used in process context.
|
|
+ *
|
|
+ * It returns a valid dst pointer on success, or a pointer encoded
|
|
+ * error code.
|
|
+ */
|
|
+struct dst_entry *ip6_sk_dst_lookup_flow(struct sock *sk, struct flowi6 *fl6,
|
|
+ const struct in6_addr *final_dst,
|
|
+ bool can_sleep)
|
|
+{
|
|
+ struct dst_entry *dst = sk_dst_check(sk, inet6_sk(sk)->dst_cookie);
|
|
+ int err;
|
|
+
|
|
+ dst = ip6_sk_dst_check(sk, dst, fl6);
|
|
+
|
|
+ err = ip6_dst_lookup_tail(sk, &dst, fl6);
|
|
+ if (err)
|
|
+ return ERR_PTR(err);
|
|
+ if (final_dst)
|
|
+ fl6->daddr = *final_dst;
|
|
+ if (can_sleep)
|
|
+ fl6->flowi6_flags |= FLOWI_FLAG_CAN_SLEEP;
|
|
+
|
|
+ return xfrm_lookup(sock_net(sk), dst, flowi6_to_flowi(fl6), sk, 0);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ip6_sk_dst_lookup_flow);
|
|
+
|
|
+static inline int ip6_ufo_append_data(struct sock *sk,
|
|
+ int getfrag(void *from, char *to, int offset, int len,
|
|
+ int odd, struct sk_buff *skb),
|
|
+ void *from, int length, int hh_len, int fragheaderlen,
|
|
+ int transhdrlen, int mtu,unsigned int flags,
|
|
+ struct rt6_info *rt)
|
|
+
|
|
+{
|
|
+ struct sk_buff *skb;
|
|
+ int err;
|
|
+
|
|
+ /* There is support for UDP large send offload by network
|
|
+ * device, so create one single skb packet containing complete
|
|
+ * udp datagram
|
|
+ */
|
|
+ if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL) {
|
|
+ struct frag_hdr fhdr;
|
|
+
|
|
+ skb = sock_alloc_send_skb(sk,
|
|
+ hh_len + fragheaderlen + transhdrlen + 20,
|
|
+ (flags & MSG_DONTWAIT), &err);
|
|
+ if (skb == NULL)
|
|
+ return err;
|
|
+
|
|
+ /* reserve space for Hardware header */
|
|
+ skb_reserve(skb, hh_len);
|
|
+
|
|
+ /* create space for UDP/IP header */
|
|
+ skb_put(skb,fragheaderlen + transhdrlen);
|
|
+
|
|
+ /* initialize network header pointer */
|
|
+ skb_reset_network_header(skb);
|
|
+
|
|
+ /* initialize protocol header pointer */
|
|
+ skb->transport_header = skb->network_header + fragheaderlen;
|
|
+
|
|
+ skb->ip_summed = CHECKSUM_PARTIAL;
|
|
+ skb->csum = 0;
|
|
+
|
|
+ /* Specify the length of each IPv6 datagram fragment.
|
|
+ * It has to be a multiple of 8.
|
|
+ */
|
|
+ skb_shinfo(skb)->gso_size = (mtu - fragheaderlen -
|
|
+ sizeof(struct frag_hdr)) & ~7;
|
|
+ skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
|
|
+ ipv6_select_ident(&fhdr, rt);
|
|
+ skb_shinfo(skb)->ip6_frag_id = fhdr.identification;
|
|
+ __skb_queue_tail(&sk->sk_write_queue, skb);
|
|
+ }
|
|
+
|
|
+ return skb_append_datato_frags(sk, skb, getfrag, from,
|
|
+ (length - transhdrlen));
|
|
+}
|
|
+
|
|
+static inline struct ipv6_opt_hdr *ip6_opt_dup(struct ipv6_opt_hdr *src,
|
|
+ gfp_t gfp)
|
|
+{
|
|
+ return src ? kmemdup(src, (src->hdrlen + 1) * 8, gfp) : NULL;
|
|
+}
|
|
+
|
|
+static inline struct ipv6_rt_hdr *ip6_rthdr_dup(struct ipv6_rt_hdr *src,
|
|
+ gfp_t gfp)
|
|
+{
|
|
+ return src ? kmemdup(src, (src->hdrlen + 1) * 8, gfp) : NULL;
|
|
+}
|
|
+
|
|
+static void ip6_append_data_mtu(unsigned int *mtu,
|
|
+ int *maxfraglen,
|
|
+ unsigned int fragheaderlen,
|
|
+ struct sk_buff *skb,
|
|
+ struct rt6_info *rt,
|
|
+ bool pmtuprobe)
|
|
+{
|
|
+ if (!(rt->dst.flags & DST_XFRM_TUNNEL)) {
|
|
+ if (skb == NULL) {
|
|
+ /* first fragment, reserve header_len */
|
|
+ *mtu = *mtu - rt->dst.header_len;
|
|
+
|
|
+ } else {
|
|
+ /*
|
|
+ * this fragment is not first, the headers
|
|
+ * space is regarded as data space.
|
|
+ */
|
|
+ *mtu = min(*mtu, pmtuprobe ?
|
|
+ rt->dst.dev->mtu :
|
|
+ dst_mtu(rt->dst.path));
|
|
+ }
|
|
+ *maxfraglen = ((*mtu - fragheaderlen) & ~7)
|
|
+ + fragheaderlen - sizeof(struct frag_hdr);
|
|
+ }
|
|
+}
|
|
+
|
|
+int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
|
|
+ int offset, int len, int odd, struct sk_buff *skb),
|
|
+ void *from, int length, int transhdrlen,
|
|
+ int hlimit, int tclass, struct ipv6_txoptions *opt, struct flowi6 *fl6,
|
|
+ struct rt6_info *rt, unsigned int flags, int dontfrag)
|
|
+{
|
|
+ struct inet_sock *inet = inet_sk(sk);
|
|
+ struct ipv6_pinfo *np = inet6_sk(sk);
|
|
+ struct inet_cork *cork;
|
|
+ struct sk_buff *skb, *skb_prev = NULL;
|
|
+ unsigned int maxfraglen, fragheaderlen, mtu;
|
|
+ int exthdrlen;
|
|
+ int dst_exthdrlen;
|
|
+ int hh_len;
|
|
+ int copy;
|
|
+ int err;
|
|
+ int offset = 0;
|
|
+ __u8 tx_flags = 0;
|
|
+
|
|
+ if (flags&MSG_PROBE)
|
|
+ return 0;
|
|
+ cork = &inet->cork.base;
|
|
+ if (skb_queue_empty(&sk->sk_write_queue)) {
|
|
+ /*
|
|
+ * setup for corking
|
|
+ */
|
|
+ if (opt) {
|
|
+ if (WARN_ON(np->cork.opt))
|
|
+ return -EINVAL;
|
|
+
|
|
+ np->cork.opt = kzalloc(opt->tot_len, sk->sk_allocation);
|
|
+ if (unlikely(np->cork.opt == NULL))
|
|
+ return -ENOBUFS;
|
|
+
|
|
+ np->cork.opt->tot_len = opt->tot_len;
|
|
+ np->cork.opt->opt_flen = opt->opt_flen;
|
|
+ np->cork.opt->opt_nflen = opt->opt_nflen;
|
|
+
|
|
+ np->cork.opt->dst0opt = ip6_opt_dup(opt->dst0opt,
|
|
+ sk->sk_allocation);
|
|
+ if (opt->dst0opt && !np->cork.opt->dst0opt)
|
|
+ return -ENOBUFS;
|
|
+
|
|
+ np->cork.opt->dst1opt = ip6_opt_dup(opt->dst1opt,
|
|
+ sk->sk_allocation);
|
|
+ if (opt->dst1opt && !np->cork.opt->dst1opt)
|
|
+ return -ENOBUFS;
|
|
+
|
|
+ np->cork.opt->hopopt = ip6_opt_dup(opt->hopopt,
|
|
+ sk->sk_allocation);
|
|
+ if (opt->hopopt && !np->cork.opt->hopopt)
|
|
+ return -ENOBUFS;
|
|
+
|
|
+ np->cork.opt->srcrt = ip6_rthdr_dup(opt->srcrt,
|
|
+ sk->sk_allocation);
|
|
+ if (opt->srcrt && !np->cork.opt->srcrt)
|
|
+ return -ENOBUFS;
|
|
+
|
|
+ /* need source address above miyazawa*/
|
|
+ }
|
|
+ dst_hold(&rt->dst);
|
|
+ cork->dst = &rt->dst;
|
|
+ inet->cork.fl.u.ip6 = *fl6;
|
|
+ np->cork.hop_limit = hlimit;
|
|
+ np->cork.tclass = tclass;
|
|
+ if (rt->dst.flags & DST_XFRM_TUNNEL)
|
|
+ mtu = np->pmtudisc == IPV6_PMTUDISC_PROBE ?
|
|
+ rt->dst.dev->mtu : dst_mtu(&rt->dst);
|
|
+ else
|
|
+ mtu = np->pmtudisc == IPV6_PMTUDISC_PROBE ?
|
|
+ rt->dst.dev->mtu : dst_mtu(rt->dst.path);
|
|
+ if (np->frag_size < mtu) {
|
|
+ if (np->frag_size)
|
|
+ mtu = np->frag_size;
|
|
+ }
|
|
+ cork->fragsize = mtu;
|
|
+ if (dst_allfrag(rt->dst.path))
|
|
+ cork->flags |= IPCORK_ALLFRAG;
|
|
+ cork->length = 0;
|
|
+ exthdrlen = (opt ? opt->opt_flen : 0);
|
|
+ length += exthdrlen;
|
|
+ transhdrlen += exthdrlen;
|
|
+ dst_exthdrlen = rt->dst.header_len - rt->rt6i_nfheader_len;
|
|
+ } else {
|
|
+ rt = (struct rt6_info *)cork->dst;
|
|
+ fl6 = &inet->cork.fl.u.ip6;
|
|
+ opt = np->cork.opt;
|
|
+ transhdrlen = 0;
|
|
+ exthdrlen = 0;
|
|
+ dst_exthdrlen = 0;
|
|
+ mtu = cork->fragsize;
|
|
+ }
|
|
+
|
|
+ hh_len = LL_RESERVED_SPACE(rt->dst.dev);
|
|
+
|
|
+ fragheaderlen = sizeof(struct ipv6hdr) + rt->rt6i_nfheader_len +
|
|
+ (opt ? opt->opt_nflen : 0);
|
|
+ maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen - sizeof(struct frag_hdr);
|
|
+
|
|
+ if (mtu <= sizeof(struct ipv6hdr) + IPV6_MAXPLEN) {
|
|
+ if (cork->length + length > sizeof(struct ipv6hdr) + IPV6_MAXPLEN - fragheaderlen) {
|
|
+ ipv6_local_error(sk, EMSGSIZE, fl6, mtu-exthdrlen);
|
|
+ return -EMSGSIZE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* For UDP, check if TX timestamp is enabled */
|
|
+ if (sk->sk_type == SOCK_DGRAM)
|
|
+ sock_tx_timestamp(sk, &tx_flags);
|
|
+
|
|
+ /*
|
|
+ * Let's try using as much space as possible.
|
|
+ * Use MTU if total length of the message fits into the MTU.
|
|
+ * Otherwise, we need to reserve fragment header and
|
|
+ * fragment alignment (= 8-15 octects, in total).
|
|
+ *
|
|
+ * Note that we may need to "move" the data from the tail of
|
|
+ * of the buffer to the new fragment when we split
|
|
+ * the message.
|
|
+ *
|
|
+ * FIXME: It may be fragmented into multiple chunks
|
|
+ * at once if non-fragmentable extension headers
|
|
+ * are too large.
|
|
+ * --yoshfuji
|
|
+ */
|
|
+
|
|
+ if ((length > mtu) && dontfrag && (sk->sk_protocol == IPPROTO_UDP ||
|
|
+ sk->sk_protocol == IPPROTO_RAW)) {
|
|
+ ipv6_local_rxpmtu(sk, fl6, mtu-exthdrlen);
|
|
+ return -EMSGSIZE;
|
|
+ }
|
|
+
|
|
+ skb = skb_peek_tail(&sk->sk_write_queue);
|
|
+ cork->length += length;
|
|
+ if (((length > mtu) ||
|
|
+ (skb && skb_has_frags(skb))) &&
|
|
+ (sk->sk_protocol == IPPROTO_UDP) &&
|
|
+ (rt->dst.dev->features & NETIF_F_UFO)) {
|
|
+ err = ip6_ufo_append_data(sk, getfrag, from, length,
|
|
+ hh_len, fragheaderlen,
|
|
+ transhdrlen, mtu, flags, rt);
|
|
+ if (err)
|
|
+ goto error;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (!skb)
|
|
+ goto alloc_new_skb;
|
|
+
|
|
+ while (length > 0) {
|
|
+ /* Check if the remaining data fits into current packet. */
|
|
+ copy = (cork->length <= mtu && !(cork->flags & IPCORK_ALLFRAG) ? mtu : maxfraglen) - skb->len;
|
|
+ if (copy < length)
|
|
+ copy = maxfraglen - skb->len;
|
|
+
|
|
+ if (copy <= 0) {
|
|
+ char *data;
|
|
+ unsigned int datalen;
|
|
+ unsigned int fraglen;
|
|
+ unsigned int fraggap;
|
|
+ unsigned int alloclen;
|
|
+alloc_new_skb:
|
|
+ /* There's no room in the current skb */
|
|
+ if (skb)
|
|
+ fraggap = skb->len - maxfraglen;
|
|
+ else
|
|
+ fraggap = 0;
|
|
+ /* update mtu and maxfraglen if necessary */
|
|
+ if (skb == NULL || skb_prev == NULL)
|
|
+ ip6_append_data_mtu(&mtu, &maxfraglen,
|
|
+ fragheaderlen, skb, rt,
|
|
+ np->pmtudisc ==
|
|
+ IPV6_PMTUDISC_PROBE);
|
|
+
|
|
+ skb_prev = skb;
|
|
+
|
|
+ /*
|
|
+ * If remaining data exceeds the mtu,
|
|
+ * we know we need more fragment(s).
|
|
+ */
|
|
+ datalen = length + fraggap;
|
|
+
|
|
+ if (datalen > (cork->length <= mtu && !(cork->flags & IPCORK_ALLFRAG) ? mtu : maxfraglen) - fragheaderlen)
|
|
+ datalen = maxfraglen - fragheaderlen - rt->dst.trailer_len;
|
|
+ if ((flags & MSG_MORE) &&
|
|
+ !(rt->dst.dev->features&NETIF_F_SG))
|
|
+ alloclen = mtu;
|
|
+ else
|
|
+ alloclen = datalen + fragheaderlen;
|
|
+
|
|
+ alloclen += dst_exthdrlen;
|
|
+
|
|
+ if (datalen != length + fraggap) {
|
|
+ /*
|
|
+ * this is not the last fragment, the trailer
|
|
+ * space is regarded as data space.
|
|
+ */
|
|
+ datalen += rt->dst.trailer_len;
|
|
+ }
|
|
+
|
|
+ alloclen += rt->dst.trailer_len;
|
|
+ fraglen = datalen + fragheaderlen;
|
|
+
|
|
+ /*
|
|
+ * We just reserve space for fragment header.
|
|
+ * Note: this may be overallocation if the message
|
|
+ * (without MSG_MORE) fits into the MTU.
|
|
+ */
|
|
+ alloclen += sizeof(struct frag_hdr);
|
|
+
|
|
+ if (transhdrlen) {
|
|
+ skb = sock_alloc_send_skb(sk,
|
|
+ alloclen + hh_len,
|
|
+ (flags & MSG_DONTWAIT), &err);
|
|
+ } else {
|
|
+ skb = NULL;
|
|
+ if (atomic_read(&sk->sk_wmem_alloc) <=
|
|
+ 2 * sk->sk_sndbuf)
|
|
+ skb = sock_wmalloc(sk,
|
|
+ alloclen + hh_len, 1,
|
|
+ sk->sk_allocation);
|
|
+ if (unlikely(skb == NULL))
|
|
+ err = -ENOBUFS;
|
|
+ else {
|
|
+ /* Only the initial fragment
|
|
+ * is time stamped.
|
|
+ */
|
|
+ tx_flags = 0;
|
|
+ }
|
|
+ }
|
|
+ if (skb == NULL)
|
|
+ goto error;
|
|
+ /*
|
|
+ * Fill in the control structures
|
|
+ */
|
|
+ skb->ip_summed = CHECKSUM_NONE;
|
|
+ skb->csum = 0;
|
|
+ /* reserve for fragmentation and ipsec header */
|
|
+ skb_reserve(skb, hh_len + sizeof(struct frag_hdr) +
|
|
+ dst_exthdrlen);
|
|
+
|
|
+ if (sk->sk_type == SOCK_DGRAM)
|
|
+ skb_shinfo(skb)->tx_flags = tx_flags;
|
|
+
|
|
+ /*
|
|
+ * Find where to start putting bytes
|
|
+ */
|
|
+ data = skb_put(skb, fraglen);
|
|
+ skb_set_network_header(skb, exthdrlen);
|
|
+ data += fragheaderlen;
|
|
+ skb->transport_header = (skb->network_header +
|
|
+ fragheaderlen);
|
|
+ if (fraggap) {
|
|
+ skb->csum = skb_copy_and_csum_bits(
|
|
+ skb_prev, maxfraglen,
|
|
+ data + transhdrlen, fraggap, 0);
|
|
+ skb_prev->csum = csum_sub(skb_prev->csum,
|
|
+ skb->csum);
|
|
+ data += fraggap;
|
|
+ pskb_trim_unique(skb_prev, maxfraglen);
|
|
+ }
|
|
+ copy = datalen - transhdrlen - fraggap;
|
|
+
|
|
+ if (copy < 0) {
|
|
+ err = -EINVAL;
|
|
+ kfree_skb(skb);
|
|
+ goto error;
|
|
+ } else if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
|
|
+ err = -EFAULT;
|
|
+ kfree_skb(skb);
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ offset += copy;
|
|
+ length -= datalen - fraggap;
|
|
+ transhdrlen = 0;
|
|
+ exthdrlen = 0;
|
|
+ dst_exthdrlen = 0;
|
|
+
|
|
+ /*
|
|
+ * Put the packet on the pending queue
|
|
+ */
|
|
+ __skb_queue_tail(&sk->sk_write_queue, skb);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (copy > length)
|
|
+ copy = length;
|
|
+
|
|
+ if (!(rt->dst.dev->features&NETIF_F_SG)) {
|
|
+ unsigned int off;
|
|
+
|
|
+ off = skb->len;
|
|
+ if (getfrag(from, skb_put(skb, copy),
|
|
+ offset, copy, off, skb) < 0) {
|
|
+ __skb_trim(skb, off);
|
|
+ err = -EFAULT;
|
|
+ goto error;
|
|
+ }
|
|
+ } else {
|
|
+ int i = skb_shinfo(skb)->nr_frags;
|
|
+ struct page_frag *pfrag = sk_page_frag(sk);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ if (!sk_page_frag_refill(sk, pfrag))
|
|
+ goto error;
|
|
+
|
|
+ if (!skb_can_coalesce(skb, i, pfrag->page,
|
|
+ pfrag->offset)) {
|
|
+ err = -EMSGSIZE;
|
|
+ if (i == MAX_SKB_FRAGS)
|
|
+ goto error;
|
|
+
|
|
+ __skb_fill_page_desc(skb, i, pfrag->page,
|
|
+ pfrag->offset, 0);
|
|
+ skb_shinfo(skb)->nr_frags = ++i;
|
|
+ get_page(pfrag->page);
|
|
+ }
|
|
+ copy = min_t(int, copy, pfrag->size - pfrag->offset);
|
|
+ if (getfrag(from,
|
|
+ page_address(pfrag->page) + pfrag->offset,
|
|
+ offset, copy, skb->len, skb) < 0)
|
|
+ goto error_efault;
|
|
+
|
|
+ pfrag->offset += copy;
|
|
+ skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
|
|
+ skb->len += copy;
|
|
+ skb->data_len += copy;
|
|
+ skb->truesize += copy;
|
|
+ atomic_add(copy, &sk->sk_wmem_alloc);
|
|
+ }
|
|
+ offset += copy;
|
|
+ length -= copy;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error_efault:
|
|
+ err = -EFAULT;
|
|
+error:
|
|
+ cork->length -= length;
|
|
+ IP6_INC_STATS(sock_net(sk), rt->rt6i_idev, IPSTATS_MIB_OUTDISCARDS);
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ip6_append_data);
|
|
+
|
|
+static void ip6_cork_release(struct inet_sock *inet, struct ipv6_pinfo *np)
|
|
+{
|
|
+ if (np->cork.opt) {
|
|
+ kfree(np->cork.opt->dst0opt);
|
|
+ kfree(np->cork.opt->dst1opt);
|
|
+ kfree(np->cork.opt->hopopt);
|
|
+ kfree(np->cork.opt->srcrt);
|
|
+ kfree(np->cork.opt);
|
|
+ np->cork.opt = NULL;
|
|
+ }
|
|
+
|
|
+ if (inet->cork.base.dst) {
|
|
+ dst_release(inet->cork.base.dst);
|
|
+ inet->cork.base.dst = NULL;
|
|
+ inet->cork.base.flags &= ~IPCORK_ALLFRAG;
|
|
+ }
|
|
+ memset(&inet->cork.fl, 0, sizeof(inet->cork.fl));
|
|
+}
|
|
+
|
|
+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 net *net = sock_net(sk);
|
|
+ struct ipv6hdr *hdr;
|
|
+ struct ipv6_txoptions *opt = np->cork.opt;
|
|
+ struct rt6_info *rt = (struct rt6_info *)inet->cork.base.dst;
|
|
+ struct flowi6 *fl6 = &inet->cork.fl.u.ip6;
|
|
+ unsigned char proto = fl6->flowi6_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_network_header(skb))
|
|
+ __skb_pull(skb, skb_network_offset(skb));
|
|
+ while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
|
|
+ __skb_pull(tmp_skb, skb_network_header_len(skb));
|
|
+ *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;
|
|
+ tmp_skb->destructor = NULL;
|
|
+ tmp_skb->sk = NULL;
|
|
+ }
|
|
+
|
|
+ /* Allow local fragmentation. */
|
|
+ if (np->pmtudisc < IPV6_PMTUDISC_DO)
|
|
+ skb->local_df = 1;
|
|
+
|
|
+ *final_dst = fl6->daddr;
|
|
+ __skb_pull(skb, skb_network_header_len(skb));
|
|
+ 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_push(skb, sizeof(struct ipv6hdr));
|
|
+ skb_reset_network_header(skb);
|
|
+ hdr = ipv6_hdr(skb);
|
|
+
|
|
+ ip6_flow_hdr(hdr, np->cork.tclass, fl6->flowlabel);
|
|
+ hdr->hop_limit = np->cork.hop_limit;
|
|
+ hdr->nexthdr = proto;
|
|
+ hdr->saddr = fl6->saddr;
|
|
+ hdr->daddr = *final_dst;
|
|
+
|
|
+ skb->priority = sk->sk_priority;
|
|
+ skb->mark = sk->sk_mark;
|
|
+
|
|
+ skb_dst_set(skb, dst_clone(&rt->dst));
|
|
+ IP6_UPD_PO_STATS(net, rt->rt6i_idev, IPSTATS_MIB_OUT, skb->len);
|
|
+ if (proto == IPPROTO_ICMPV6) {
|
|
+ struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
|
|
+
|
|
+ ICMP6MSGOUT_INC_STATS_BH(net, idev, icmp6_hdr(skb)->icmp6_type);
|
|
+ ICMP6_INC_STATS_BH(net, idev, ICMP6_MIB_OUTMSGS);
|
|
+ }
|
|
+
|
|
+ err = ip6_local_out(skb);
|
|
+ if (err) {
|
|
+ if (err > 0)
|
|
+ err = net_xmit_errno(err);
|
|
+ if (err)
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ ip6_cork_release(inet, np);
|
|
+ return err;
|
|
+error:
|
|
+ IP6_INC_STATS(net, rt->rt6i_idev, IPSTATS_MIB_OUTDISCARDS);
|
|
+ goto out;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ip6_push_pending_frames);
|
|
+
|
|
+void ip6_flush_pending_frames(struct sock *sk)
|
|
+{
|
|
+ struct sk_buff *skb;
|
|
+
|
|
+ while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL) {
|
|
+ if (skb_dst(skb))
|
|
+ IP6_INC_STATS(sock_net(sk), ip6_dst_idev(skb_dst(skb)),
|
|
+ IPSTATS_MIB_OUTDISCARDS);
|
|
+ kfree_skb(skb);
|
|
+ }
|
|
+
|
|
+ ip6_cork_release(inet_sk(sk), inet6_sk(sk));
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ip6_flush_pending_frames);
|
|
diff -Naur linux-3.10.30.org/net/netfilter/core.c linux-3.10.30/net/netfilter/core.c
|
|
--- linux-3.10.30.org/net/netfilter/core.c 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/netfilter/core.c 2014-02-14 20:29:05.392738001 +0100
|
|
@@ -191,9 +191,11 @@
|
|
ret = NF_DROP_GETERR(verdict);
|
|
if (ret == 0)
|
|
ret = -EPERM;
|
|
- } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
|
|
+ } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE ||
|
|
+ (verdict & NF_VERDICT_MASK) == NF_IMQ_QUEUE) {
|
|
int err = nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
|
|
- verdict >> NF_VERDICT_QBITS);
|
|
+ verdict >> NF_VERDICT_QBITS,
|
|
+ verdict & NF_VERDICT_MASK);
|
|
if (err < 0) {
|
|
if (err == -ECANCELED)
|
|
goto next_hook;
|
|
diff -Naur linux-3.10.30.org/net/netfilter/Kconfig linux-3.10.30/net/netfilter/Kconfig
|
|
--- linux-3.10.30.org/net/netfilter/Kconfig 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/netfilter/Kconfig 2014-02-14 20:29:05.396071847 +0100
|
|
@@ -641,6 +641,18 @@
|
|
|
|
To compile it as a module, choose M here. If unsure, say N.
|
|
|
|
+config NETFILTER_XT_TARGET_IMQ
|
|
+ tristate '"IMQ" target support'
|
|
+ depends on NETFILTER_XTABLES
|
|
+ depends on IP_NF_MANGLE || IP6_NF_MANGLE
|
|
+ select IMQ
|
|
+ default m if NETFILTER_ADVANCED=n
|
|
+ help
|
|
+ This option adds a `IMQ' target which is used to specify if and
|
|
+ to which imq device packets should get enqueued/dequeued.
|
|
+
|
|
+ To compile it as a module, choose M here. If unsure, say N.
|
|
+
|
|
config NETFILTER_XT_TARGET_MARK
|
|
tristate '"MARK" target support'
|
|
depends on NETFILTER_ADVANCED
|
|
diff -Naur linux-3.10.30.org/net/netfilter/Makefile linux-3.10.30/net/netfilter/Makefile
|
|
--- linux-3.10.30.org/net/netfilter/Makefile 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/netfilter/Makefile 2014-02-14 20:29:05.396071847 +0100
|
|
@@ -82,6 +82,7 @@
|
|
obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o
|
|
obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o
|
|
obj-$(CONFIG_NETFILTER_XT_TARGET_HMARK) += xt_HMARK.o
|
|
+obj-$(CONFIG_NETFILTER_XT_TARGET_IMQ) += xt_IMQ.o
|
|
obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o
|
|
obj-$(CONFIG_NETFILTER_XT_TARGET_LOG) += xt_LOG.o
|
|
obj-$(CONFIG_NETFILTER_XT_TARGET_NETMAP) += xt_NETMAP.o
|
|
diff -Naur linux-3.10.30.org/net/netfilter/nf_internals.h linux-3.10.30/net/netfilter/nf_internals.h
|
|
--- linux-3.10.30.org/net/netfilter/nf_internals.h 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/netfilter/nf_internals.h 2014-02-14 20:29:05.396071847 +0100
|
|
@@ -29,7 +29,7 @@
|
|
struct net_device *indev,
|
|
struct net_device *outdev,
|
|
int (*okfn)(struct sk_buff *),
|
|
- unsigned int queuenum);
|
|
+ unsigned int queuenum, unsigned int queuetype);
|
|
extern int __init netfilter_queue_init(void);
|
|
|
|
/* nf_log.c */
|
|
diff -Naur linux-3.10.30.org/net/netfilter/nf_queue.c linux-3.10.30/net/netfilter/nf_queue.c
|
|
--- linux-3.10.30.org/net/netfilter/nf_queue.c 2014-02-13 22:48:15.000000000 +0100
|
|
+++ linux-3.10.30/net/netfilter/nf_queue.c 2014-02-14 20:29:05.396071847 +0100
|
|
@@ -27,6 +27,23 @@
|
|
*/
|
|
static const struct nf_queue_handler __rcu *queue_handler __read_mostly;
|
|
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+static const struct nf_queue_handler __rcu *queue_imq_handler __read_mostly;
|
|
+
|
|
+void nf_register_queue_imq_handler(const struct nf_queue_handler *qh)
|
|
+{
|
|
+ rcu_assign_pointer(queue_imq_handler, qh);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(nf_register_queue_imq_handler);
|
|
+
|
|
+void nf_unregister_queue_imq_handler(void)
|
|
+{
|
|
+ RCU_INIT_POINTER(queue_imq_handler, NULL);
|
|
+ synchronize_rcu();
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(nf_unregister_queue_imq_handler);
|
|
+#endif
|
|
+
|
|
/* return EBUSY when somebody else is registered, return EEXIST if the
|
|
* same handler is registered, return 0 in case of success. */
|
|
void nf_register_queue_handler(const struct nf_queue_handler *qh)
|
|
@@ -105,7 +122,8 @@
|
|
struct net_device *indev,
|
|
struct net_device *outdev,
|
|
int (*okfn)(struct sk_buff *),
|
|
- unsigned int queuenum)
|
|
+ unsigned int queuenum,
|
|
+ unsigned int queuetype)
|
|
{
|
|
int status = -ENOENT;
|
|
struct nf_queue_entry *entry = NULL;
|
|
@@ -115,7 +133,17 @@
|
|
/* QUEUE == DROP if no one is waiting, to be safe. */
|
|
rcu_read_lock();
|
|
|
|
- qh = rcu_dereference(queue_handler);
|
|
+ if (queuetype == NF_IMQ_QUEUE) {
|
|
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
|
|
+ qh = rcu_dereference(queue_imq_handler);
|
|
+#else
|
|
+ BUG();
|
|
+ goto err_unlock;
|
|
+#endif
|
|
+ } else {
|
|
+ qh = rcu_dereference(queue_handler);
|
|
+ }
|
|
+
|
|
if (!qh) {
|
|
status = -ESRCH;
|
|
goto err_unlock;
|
|
@@ -205,9 +233,11 @@
|
|
local_bh_enable();
|
|
break;
|
|
case NF_QUEUE:
|
|
+ case NF_IMQ_QUEUE:
|
|
err = nf_queue(skb, elem, entry->pf, entry->hook,
|
|
entry->indev, entry->outdev, entry->okfn,
|
|
- verdict >> NF_VERDICT_QBITS);
|
|
+ verdict >> NF_VERDICT_QBITS,
|
|
+ verdict & NF_VERDICT_MASK);
|
|
if (err < 0) {
|
|
if (err == -ECANCELED)
|
|
goto next_hook;
|
|
diff -Naur linux-3.10.30.org/net/netfilter/xt_IMQ.c linux-3.10.30/net/netfilter/xt_IMQ.c
|
|
--- linux-3.10.30.org/net/netfilter/xt_IMQ.c 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-3.10.30/net/netfilter/xt_IMQ.c 2014-02-14 20:29:05.396071847 +0100
|
|
@@ -0,0 +1,72 @@
|
|
+/*
|
|
+ * This target marks packets to be enqueued to an imq device
|
|
+ */
|
|
+#include <linux/module.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/netfilter/x_tables.h>
|
|
+#include <linux/netfilter/xt_IMQ.h>
|
|
+#include <linux/imq.h>
|
|
+
|
|
+static unsigned int imq_target(struct sk_buff *pskb,
|
|
+ const struct xt_action_param *par)
|
|
+{
|
|
+ const struct xt_imq_info *mr = par->targinfo;
|
|
+
|
|
+ pskb->imq_flags = (mr->todev & IMQ_F_IFMASK) | IMQ_F_ENQUEUE;
|
|
+
|
|
+ return XT_CONTINUE;
|
|
+}
|
|
+
|
|
+static int imq_checkentry(const struct xt_tgchk_param *par)
|
|
+{
|
|
+ struct xt_imq_info *mr = par->targinfo;
|
|
+
|
|
+ if (mr->todev > IMQ_MAX_DEVS - 1) {
|
|
+ pr_warn("IMQ: invalid device specified, highest is %u\n",
|
|
+ IMQ_MAX_DEVS - 1);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct xt_target xt_imq_reg[] __read_mostly = {
|
|
+ {
|
|
+ .name = "IMQ",
|
|
+ .family = AF_INET,
|
|
+ .checkentry = imq_checkentry,
|
|
+ .target = imq_target,
|
|
+ .targetsize = sizeof(struct xt_imq_info),
|
|
+ .table = "mangle",
|
|
+ .me = THIS_MODULE
|
|
+ },
|
|
+ {
|
|
+ .name = "IMQ",
|
|
+ .family = AF_INET6,
|
|
+ .checkentry = imq_checkentry,
|
|
+ .target = imq_target,
|
|
+ .targetsize = sizeof(struct xt_imq_info),
|
|
+ .table = "mangle",
|
|
+ .me = THIS_MODULE
|
|
+ },
|
|
+};
|
|
+
|
|
+static int __init imq_init(void)
|
|
+{
|
|
+ return xt_register_targets(xt_imq_reg, ARRAY_SIZE(xt_imq_reg));
|
|
+}
|
|
+
|
|
+static void __exit imq_fini(void)
|
|
+{
|
|
+ xt_unregister_targets(xt_imq_reg, ARRAY_SIZE(xt_imq_reg));
|
|
+}
|
|
+
|
|
+module_init(imq_init);
|
|
+module_exit(imq_fini);
|
|
+
|
|
+MODULE_AUTHOR("http://www.linuximq.net");
|
|
+MODULE_DESCRIPTION("Pseudo-driver for the intermediate queue device. See http://www.linuximq.net/ for more information.");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS("ipt_IMQ");
|
|
+MODULE_ALIAS("ip6t_IMQ");
|
|
+
|