From 7e14ea1521d9249d9de7f0ea39c9af054745eebd Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:07 +0100 Subject: xfrm6: Add IPsec protocol multiplexer This patch adds an IPsec protocol multiplexer for ipv6. With this it is possible to add alternative protocol handlers, as needed for IPsec virtual tunnel interfaces. Signed-off-by: Steffen Klassert --- net/ipv6/Makefile | 2 +- net/ipv6/xfrm6_policy.c | 7 ++ net/ipv6/xfrm6_protocol.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 net/ipv6/xfrm6_protocol.c (limited to 'net/ipv6') diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 17bb830..2fe6836 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -16,7 +16,7 @@ ipv6-$(CONFIG_SYSCTL) = sysctl_net_ipv6.o ipv6-$(CONFIG_IPV6_MROUTE) += ip6mr.o ipv6-$(CONFIG_XFRM) += xfrm6_policy.o xfrm6_state.o xfrm6_input.o \ - xfrm6_output.o + xfrm6_output.o xfrm6_protocol.o ipv6-$(CONFIG_NETFILTER) += netfilter.o ipv6-$(CONFIG_IPV6_MULTIPLE_TABLES) += fib6_rules.o ipv6-$(CONFIG_PROC_FS) += proc.o diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c index 5f8e128..2a0bbda 100644 --- a/net/ipv6/xfrm6_policy.c +++ b/net/ipv6/xfrm6_policy.c @@ -389,11 +389,17 @@ int __init xfrm6_init(void) if (ret) goto out_policy; + ret = xfrm6_protocol_init(); + if (ret) + goto out_state; + #ifdef CONFIG_SYSCTL register_pernet_subsys(&xfrm6_net_ops); #endif out: return ret; +out_state: + xfrm6_state_fini(); out_policy: xfrm6_policy_fini(); goto out; @@ -404,6 +410,7 @@ void xfrm6_fini(void) #ifdef CONFIG_SYSCTL unregister_pernet_subsys(&xfrm6_net_ops); #endif + xfrm6_protocol_fini(); xfrm6_policy_fini(); xfrm6_state_fini(); dst_entries_destroy(&xfrm6_dst_ops); diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c new file mode 100644 index 0000000..6ab989c --- /dev/null +++ b/net/ipv6/xfrm6_protocol.c @@ -0,0 +1,270 @@ +/* xfrm6_protocol.c - Generic xfrm protocol multiplexer for ipv6. + * + * Copyright (C) 2013 secunet Security Networks AG + * + * Author: + * Steffen Klassert + * + * Based on: + * net/ipv4/xfrm4_protocol.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. + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct xfrm6_protocol __rcu *esp6_handlers __read_mostly; +static struct xfrm6_protocol __rcu *ah6_handlers __read_mostly; +static struct xfrm6_protocol __rcu *ipcomp6_handlers __read_mostly; +static DEFINE_MUTEX(xfrm6_protocol_mutex); + +static inline struct xfrm6_protocol __rcu **proto_handlers(u8 protocol) +{ + switch (protocol) { + case IPPROTO_ESP: + return &esp6_handlers; + case IPPROTO_AH: + return &ah6_handlers; + case IPPROTO_COMP: + return &ipcomp6_handlers; + } + + return NULL; +} + +#define for_each_protocol_rcu(head, handler) \ + for (handler = rcu_dereference(head); \ + handler != NULL; \ + handler = rcu_dereference(handler->next)) \ + +int xfrm6_rcv_cb(struct sk_buff *skb, u8 protocol, int err) +{ + int ret; + struct xfrm6_protocol *handler; + + for_each_protocol_rcu(*proto_handlers(protocol), handler) + if ((ret = handler->cb_handler(skb, err)) <= 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(xfrm6_rcv_cb); + +static int xfrm6_esp_rcv(struct sk_buff *skb) +{ + int ret; + struct xfrm6_protocol *handler; + + XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; + + for_each_protocol_rcu(esp6_handlers, handler) + if ((ret = handler->handler(skb)) != -EINVAL) + return ret; + + icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0); + + kfree_skb(skb); + return 0; +} + +static void xfrm6_esp_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + u8 type, u8 code, int offset, __be32 info) +{ + struct xfrm6_protocol *handler; + + for_each_protocol_rcu(esp6_handlers, handler) + if (!handler->err_handler(skb, opt, type, code, offset, info)) + break; +} + +static int xfrm6_ah_rcv(struct sk_buff *skb) +{ + int ret; + struct xfrm6_protocol *handler; + + XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; + + for_each_protocol_rcu(ah6_handlers, handler) + if ((ret = handler->handler(skb)) != -EINVAL) + return ret; + + icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0); + + kfree_skb(skb); + return 0; +} + +static void xfrm6_ah_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + u8 type, u8 code, int offset, __be32 info) +{ + struct xfrm6_protocol *handler; + + for_each_protocol_rcu(ah6_handlers, handler) + if (!handler->err_handler(skb, opt, type, code, offset, info)) + break; +} + +static int xfrm6_ipcomp_rcv(struct sk_buff *skb) +{ + int ret; + struct xfrm6_protocol *handler; + + XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; + + for_each_protocol_rcu(ipcomp6_handlers, handler) + if ((ret = handler->handler(skb)) != -EINVAL) + return ret; + + icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0); + + kfree_skb(skb); + return 0; +} + +static void xfrm6_ipcomp_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + u8 type, u8 code, int offset, __be32 info) +{ + struct xfrm6_protocol *handler; + + for_each_protocol_rcu(ipcomp6_handlers, handler) + if (!handler->err_handler(skb, opt, type, code, offset, info)) + break; +} + +static const struct inet6_protocol esp6_protocol = { + .handler = xfrm6_esp_rcv, + .err_handler = xfrm6_esp_err, + .flags = INET6_PROTO_NOPOLICY, +}; + +static const struct inet6_protocol ah6_protocol = { + .handler = xfrm6_ah_rcv, + .err_handler = xfrm6_ah_err, + .flags = INET6_PROTO_NOPOLICY, +}; + +static const struct inet6_protocol ipcomp6_protocol = { + .handler = xfrm6_ipcomp_rcv, + .err_handler = xfrm6_ipcomp_err, + .flags = INET6_PROTO_NOPOLICY, +}; + +static struct xfrm_input_afinfo xfrm6_input_afinfo = { + .family = AF_INET6, + .owner = THIS_MODULE, + .callback = xfrm6_rcv_cb, +}; + +static inline const struct inet6_protocol *netproto(unsigned char protocol) +{ + switch (protocol) { + case IPPROTO_ESP: + return &esp6_protocol; + case IPPROTO_AH: + return &ah6_protocol; + case IPPROTO_COMP: + return &ipcomp6_protocol; + } + + return NULL; +} + +int xfrm6_protocol_register(struct xfrm6_protocol *handler, + unsigned char protocol) +{ + struct xfrm6_protocol __rcu **pprev; + struct xfrm6_protocol *t; + bool add_netproto = false; + + int ret = -EEXIST; + int priority = handler->priority; + + mutex_lock(&xfrm6_protocol_mutex); + + if (!rcu_dereference_protected(*proto_handlers(protocol), + lockdep_is_held(&xfrm6_protocol_mutex))) + add_netproto = true; + + for (pprev = proto_handlers(protocol); + (t = rcu_dereference_protected(*pprev, + lockdep_is_held(&xfrm6_protocol_mutex))) != NULL; + pprev = &t->next) { + if (t->priority < priority) + break; + if (t->priority == priority) + goto err; + } + + handler->next = *pprev; + rcu_assign_pointer(*pprev, handler); + + ret = 0; + +err: + mutex_unlock(&xfrm6_protocol_mutex); + + if (add_netproto) { + if (inet6_add_protocol(netproto(protocol), protocol)) { + pr_err("%s: can't add protocol\n", __func__); + ret = -EAGAIN; + } + } + + return ret; +} +EXPORT_SYMBOL(xfrm6_protocol_register); + +int xfrm6_protocol_deregister(struct xfrm6_protocol *handler, + unsigned char protocol) +{ + struct xfrm6_protocol __rcu **pprev; + struct xfrm6_protocol *t; + int ret = -ENOENT; + + mutex_lock(&xfrm6_protocol_mutex); + + for (pprev = proto_handlers(protocol); + (t = rcu_dereference_protected(*pprev, + lockdep_is_held(&xfrm6_protocol_mutex))) != NULL; + pprev = &t->next) { + if (t == handler) { + *pprev = handler->next; + ret = 0; + break; + } + } + + if (!rcu_dereference_protected(*proto_handlers(protocol), + lockdep_is_held(&xfrm6_protocol_mutex))) { + if (inet6_del_protocol(netproto(protocol), protocol) < 0) { + pr_err("%s: can't remove protocol\n", __func__); + ret = -EAGAIN; + } + } + + mutex_unlock(&xfrm6_protocol_mutex); + + synchronize_net(); + + return ret; +} +EXPORT_SYMBOL(xfrm6_protocol_deregister); + +int __init xfrm6_protocol_init(void) +{ + return xfrm_input_register_afinfo(&xfrm6_input_afinfo); +} + +void xfrm6_protocol_fini(void) +{ + xfrm_input_unregister_afinfo(&xfrm6_input_afinfo); +} -- cgit v1.1 From d5860c5ccfcc20131634527ee3bc4a11a5168dd7 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:07 +0100 Subject: esp6: Use the IPsec protocol multiplexer API Switch esp6 to use the new IPsec protocol multiplexer. Signed-off-by: Steffen Klassert --- net/ipv6/esp6.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 6eef8a7..d15da13 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -421,8 +421,8 @@ static u32 esp6_get_mtu(struct xfrm_state *x, int mtu) net_adj) & ~(blksize - 1)) + net_adj - 2; } -static void esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, - u8 type, u8 code, int offset, __be32 info) +static int esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + u8 type, u8 code, int offset, __be32 info) { struct net *net = dev_net(skb->dev); const struct ipv6hdr *iph = (const struct ipv6hdr *)skb->data; @@ -431,18 +431,20 @@ static void esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, if (type != ICMPV6_PKT_TOOBIG && type != NDISC_REDIRECT) - return; + return 0; x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, esph->spi, IPPROTO_ESP, AF_INET6); if (!x) - return; + return 0; if (type == NDISC_REDIRECT) ip6_redirect(skb, net, skb->dev->ifindex, 0); else ip6_update_pmtu(skb, net, info, 0, 0); xfrm_state_put(x); + + return 0; } static void esp6_destroy(struct xfrm_state *x) @@ -614,6 +616,11 @@ error: return err; } +static int esp6_rcv_cb(struct sk_buff *skb, int err) +{ + return 0; +} + static const struct xfrm_type esp6_type = { .description = "ESP6", @@ -628,10 +635,11 @@ static const struct xfrm_type esp6_type = .hdr_offset = xfrm6_find_1stfragopt, }; -static const struct inet6_protocol esp6_protocol = { - .handler = xfrm6_rcv, +static struct xfrm6_protocol esp6_protocol = { + .handler = xfrm6_rcv, + .cb_handler = esp6_rcv_cb, .err_handler = esp6_err, - .flags = INET6_PROTO_NOPOLICY, + .priority = 0, }; static int __init esp6_init(void) @@ -640,7 +648,7 @@ static int __init esp6_init(void) pr_info("%s: can't add xfrm type\n", __func__); return -EAGAIN; } - if (inet6_add_protocol(&esp6_protocol, IPPROTO_ESP) < 0) { + if (xfrm6_protocol_register(&esp6_protocol, IPPROTO_ESP) < 0) { pr_info("%s: can't add protocol\n", __func__); xfrm_unregister_type(&esp6_type, AF_INET6); return -EAGAIN; @@ -651,7 +659,7 @@ static int __init esp6_init(void) static void __exit esp6_fini(void) { - if (inet6_del_protocol(&esp6_protocol, IPPROTO_ESP) < 0) + if (xfrm6_protocol_deregister(&esp6_protocol, IPPROTO_ESP) < 0) pr_info("%s: can't remove protocol\n", __func__); if (xfrm_unregister_type(&esp6_type, AF_INET6) < 0) pr_info("%s: can't remove xfrm type\n", __func__); -- cgit v1.1 From e924d2d68738f3c63e460a829d4a0eb32e0638e3 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:07 +0100 Subject: ah6: Use the IPsec protocol multiplexer API Switch ah6 to use the new IPsec protocol multiplexer. Signed-off-by: Steffen Klassert --- net/ipv6/ah6.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c index 6c5f094..72a4930 100644 --- a/net/ipv6/ah6.c +++ b/net/ipv6/ah6.c @@ -643,8 +643,8 @@ out: return err; } -static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, - u8 type, u8 code, int offset, __be32 info) +static int ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + u8 type, u8 code, int offset, __be32 info) { struct net *net = dev_net(skb->dev); struct ipv6hdr *iph = (struct ipv6hdr*)skb->data; @@ -653,17 +653,19 @@ static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, if (type != ICMPV6_PKT_TOOBIG && type != NDISC_REDIRECT) - return; + return 0; x = xfrm_state_lookup(net, skb->mark, (xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET6); if (!x) - return; + return 0; if (type == NDISC_REDIRECT) ip6_redirect(skb, net, skb->dev->ifindex, 0); else ip6_update_pmtu(skb, net, info, 0, 0); xfrm_state_put(x); + + return 0; } static int ah6_init_state(struct xfrm_state *x) @@ -748,6 +750,11 @@ static void ah6_destroy(struct xfrm_state *x) kfree(ahp); } +static int ah6_rcv_cb(struct sk_buff *skb, int err) +{ + return 0; +} + static const struct xfrm_type ah6_type = { .description = "AH6", @@ -761,10 +768,11 @@ static const struct xfrm_type ah6_type = .hdr_offset = xfrm6_find_1stfragopt, }; -static const struct inet6_protocol ah6_protocol = { +static struct xfrm6_protocol ah6_protocol = { .handler = xfrm6_rcv, + .cb_handler = ah6_rcv_cb, .err_handler = ah6_err, - .flags = INET6_PROTO_NOPOLICY, + .priority = 0, }; static int __init ah6_init(void) @@ -774,7 +782,7 @@ static int __init ah6_init(void) return -EAGAIN; } - if (inet6_add_protocol(&ah6_protocol, IPPROTO_AH) < 0) { + if (xfrm6_protocol_register(&ah6_protocol, IPPROTO_AH) < 0) { pr_info("%s: can't add protocol\n", __func__); xfrm_unregister_type(&ah6_type, AF_INET6); return -EAGAIN; @@ -785,7 +793,7 @@ static int __init ah6_init(void) static void __exit ah6_fini(void) { - if (inet6_del_protocol(&ah6_protocol, IPPROTO_AH) < 0) + if (xfrm6_protocol_deregister(&ah6_protocol, IPPROTO_AH) < 0) pr_info("%s: can't remove protocol\n", __func__); if (xfrm_unregister_type(&ah6_type, AF_INET6) < 0) -- cgit v1.1 From 59b84351c0ee97501782988af5ec9c004c4d30ac Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:07 +0100 Subject: ipcomp6: Use the IPsec protocol multiplexer API Switch ipcomp6 to use the new IPsec protocol multiplexer. Signed-off-by: Steffen Klassert --- net/ipv6/ipcomp6.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ipcomp6.c b/net/ipv6/ipcomp6.c index da9becb..d1c793c 100644 --- a/net/ipv6/ipcomp6.c +++ b/net/ipv6/ipcomp6.c @@ -53,7 +53,7 @@ #include #include -static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, +static int ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, u8 type, u8 code, int offset, __be32 info) { struct net *net = dev_net(skb->dev); @@ -65,19 +65,21 @@ static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, if (type != ICMPV6_PKT_TOOBIG && type != NDISC_REDIRECT) - return; + return 0; spi = htonl(ntohs(ipcomph->cpi)); x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, spi, IPPROTO_COMP, AF_INET6); if (!x) - return; + return 0; if (type == NDISC_REDIRECT) ip6_redirect(skb, net, skb->dev->ifindex, 0); else ip6_update_pmtu(skb, net, info, 0, 0); xfrm_state_put(x); + + return 0; } static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x) @@ -174,6 +176,11 @@ out: return err; } +static int ipcomp6_rcv_cb(struct sk_buff *skb, int err) +{ + return 0; +} + static const struct xfrm_type ipcomp6_type = { .description = "IPCOMP6", @@ -186,11 +193,12 @@ static const struct xfrm_type ipcomp6_type = .hdr_offset = xfrm6_find_1stfragopt, }; -static const struct inet6_protocol ipcomp6_protocol = +static struct xfrm6_protocol ipcomp6_protocol = { .handler = xfrm6_rcv, + .cb_handler = ipcomp6_rcv_cb, .err_handler = ipcomp6_err, - .flags = INET6_PROTO_NOPOLICY, + .priority = 0, }; static int __init ipcomp6_init(void) @@ -199,7 +207,7 @@ static int __init ipcomp6_init(void) pr_info("%s: can't add xfrm type\n", __func__); return -EAGAIN; } - if (inet6_add_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) { + if (xfrm6_protocol_register(&ipcomp6_protocol, IPPROTO_COMP) < 0) { pr_info("%s: can't add protocol\n", __func__); xfrm_unregister_type(&ipcomp6_type, AF_INET6); return -EAGAIN; @@ -209,7 +217,7 @@ static int __init ipcomp6_init(void) static void __exit ipcomp6_fini(void) { - if (inet6_del_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) + if (xfrm6_protocol_deregister(&ipcomp6_protocol, IPPROTO_COMP) < 0) pr_info("%s: can't remove protocol\n", __func__); if (xfrm_unregister_type(&ipcomp6_type, AF_INET6) < 0) pr_info("%s: can't remove xfrm type\n", __func__); -- cgit v1.1 From 7c85258152d639868091c8c4bb6b5364c108f074 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:08 +0100 Subject: vti6: Remove dst_entry caching Unlike ip6_tunnel, vti6 can lookup multiple different dst entries, dependent of the configured xfrm states. Therefore it does not make sense to cache a dst_entry. Signed-off-by: Steffen Klassert --- net/ipv6/ip6_vti.c | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index 8649143..f5ba4d4 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -278,7 +278,6 @@ static void vti6_dev_uninit(struct net_device *dev) RCU_INIT_POINTER(ip6n->tnls_wc[0], NULL); else vti6_tnl_unlink(ip6n, t); - ip6_tnl_dst_reset(t); dev_put(dev); } @@ -356,11 +355,10 @@ vti6_addr_conflict(const struct ip6_tnl *t, const struct ipv6hdr *hdr) **/ static int vti6_xmit(struct sk_buff *skb, struct net_device *dev) { - struct net *net = dev_net(dev); struct ip6_tnl *t = netdev_priv(dev); struct net_device_stats *stats = &t->dev->stats; - struct dst_entry *dst = NULL, *ndst = NULL; - struct flowi6 fl6; + struct dst_entry *dst = skb_dst(skb); + struct flowi fl; struct ipv6hdr *ipv6h = ipv6_hdr(skb); struct net_device *tdev; int err = -1; @@ -369,21 +367,19 @@ static int vti6_xmit(struct sk_buff *skb, struct net_device *dev) !ip6_tnl_xmit_ctl(t) || vti6_addr_conflict(t, ipv6h)) return err; - dst = ip6_tnl_dst_check(t); - if (!dst) { - memcpy(&fl6, &t->fl.u.ip6, sizeof(fl6)); + memset(&fl, 0, sizeof(fl)); + skb->mark = be32_to_cpu(t->parms.o_key); + xfrm_decode_session(skb, &fl, AF_INET6); - ndst = ip6_route_output(net, NULL, &fl6); + if (!dst) + goto tx_err_link_failure; - if (ndst->error) - goto tx_err_link_failure; - ndst = xfrm_lookup(net, ndst, flowi6_to_flowi(&fl6), NULL, 0); - if (IS_ERR(ndst)) { - err = PTR_ERR(ndst); - ndst = NULL; - goto tx_err_link_failure; - } - dst = ndst; + dst_hold(dst); + dst = xfrm_lookup(t->net, dst, &fl, NULL, 0); + if (IS_ERR(dst)) { + err = PTR_ERR(dst); + dst = NULL; + goto tx_err_link_failure; } if (!dst->xfrm || dst->xfrm->props.mode != XFRM_MODE_TUNNEL) @@ -399,21 +395,19 @@ static int vti6_xmit(struct sk_buff *skb, struct net_device *dev) } - skb_dst_drop(skb); - skb_dst_set_noref(skb, dst); + memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); + skb_scrub_packet(skb, !net_eq(t->net, dev_net(dev))); + skb_dst_set(skb, dst); + skb->dev = skb_dst(skb)->dev; ip6tunnel_xmit(skb, dev); - if (ndst) { - dev->mtu = dst_mtu(ndst); - ip6_tnl_dst_store(t, ndst); - } return 0; tx_err_link_failure: stats->tx_carrier_errors++; dst_link_failure(skb); tx_err_dst_release: - dst_release(ndst); + dst_release(dst); return err; } -- cgit v1.1 From 7cf9fdb5c771c61771e4e39efe18e2dbc8c8bfa4 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:08 +0100 Subject: vti6: Remove caching of flow informations. Unlike ip6_tunnel, vti6 does not use the the tunnel endpoint addresses to do route and xfrm lookups. So no need to cache the flow informations. It also does not make sense to calculate the mtu based on such flow informations, so remove this too. Signed-off-by: Steffen Klassert --- net/ipv6/ip6_vti.c | 31 ------------------------------- 1 file changed, 31 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index f5ba4d4..cc4a758 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -443,19 +443,10 @@ static void vti6_link_config(struct ip6_tnl *t) struct dst_entry *dst; struct net_device *dev = t->dev; struct __ip6_tnl_parm *p = &t->parms; - struct flowi6 *fl6 = &t->fl.u.ip6; memcpy(dev->dev_addr, &p->laddr, sizeof(struct in6_addr)); memcpy(dev->broadcast, &p->raddr, sizeof(struct in6_addr)); - /* Set up flowi template */ - fl6->saddr = p->laddr; - fl6->daddr = p->raddr; - fl6->flowi6_oif = p->link; - fl6->flowi6_mark = be32_to_cpu(p->i_key); - fl6->flowi6_proto = p->proto; - fl6->flowlabel = 0; - p->flags &= ~(IP6_TNL_F_CAP_XMIT | IP6_TNL_F_CAP_RCV | IP6_TNL_F_CAP_PER_PACKET); p->flags |= ip6_tnl_get_cap(t, &p->laddr, &p->raddr); @@ -466,28 +457,6 @@ static void vti6_link_config(struct ip6_tnl *t) dev->flags &= ~IFF_POINTOPOINT; dev->iflink = p->link; - - if (p->flags & IP6_TNL_F_CAP_XMIT) { - - dst = ip6_route_output(dev_net(dev), NULL, fl6); - if (dst->error) - return; - - dst = xfrm_lookup(dev_net(dev), dst, flowi6_to_flowi(fl6), - NULL, 0); - if (IS_ERR(dst)) - return; - - if (dst->dev) { - dev->hard_header_len = dst->dev->hard_header_len; - - dev->mtu = dst_mtu(dst); - - if (dev->mtu < IPV6_MIN_MTU) - dev->mtu = IPV6_MIN_MTU; - } - dst_release(dst); - } } /** -- cgit v1.1 From fa9ad96d4905c3e2013bcce18c104108275c4c08 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:08 +0100 Subject: vti6: Update the ipv6 side to use its own receive hook. With this patch, vti6 uses the IPsec protocol multiplexer to register its own receive side hooks for ESP, AH and IPCOMP. Vti6 now does the following on receive side: 1. Do an input policy check for the IPsec packet we received. This is required because this packet could be already prosecces by IPsec, so an inbuond policy check is needed. 2. Mark the packet with the i_key. The policy and the state must match this key now. Policy and state belong to the vti namespace and policy enforcement is done at the further layers. 3. Call the generic xfrm layer to do decryption and decapsulation. 4. Wait for a callback from the xfrm layer to properly clean the skb to not leak informations on namespace transitions and update the device statistics. On transmit side: 1. Mark the packet with the o_key. The policy and the state must match this key now. 2. Do a xfrm_lookup on the original packet with the mark applied. 3. Check if we got an IPsec route. 4. Clean the skb to not leak informations on namespace transitions. 5. Attach the dst_enty we got from the xfrm_lookup to the skb. 6. Call dst_output to do the IPsec processing. 7. Do the device statistics. Signed-off-by: Steffen Klassert --- net/ipv6/ip6_vti.c | 176 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 149 insertions(+), 27 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index cc4a758..226854a 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -287,11 +287,8 @@ static int vti6_rcv(struct sk_buff *skb) const struct ipv6hdr *ipv6h = ipv6_hdr(skb); rcu_read_lock(); - if ((t = vti6_tnl_lookup(dev_net(skb->dev), &ipv6h->saddr, &ipv6h->daddr)) != NULL) { - struct pcpu_sw_netstats *tstats; - if (t->parms.proto != IPPROTO_IPV6 && t->parms.proto != 0) { rcu_read_unlock(); goto discard; @@ -308,27 +305,58 @@ static int vti6_rcv(struct sk_buff *skb) goto discard; } - tstats = this_cpu_ptr(t->dev->tstats); - u64_stats_update_begin(&tstats->syncp); - tstats->rx_packets++; - tstats->rx_bytes += skb->len; - u64_stats_update_end(&tstats->syncp); - - skb->mark = 0; - secpath_reset(skb); - skb->dev = t->dev; + XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = t; + skb->mark = be32_to_cpu(t->parms.i_key); rcu_read_unlock(); - return 0; + + return xfrm6_rcv(skb); } rcu_read_unlock(); - return 1; - + return -EINVAL; discard: kfree_skb(skb); return 0; } +static int vti6_rcv_cb(struct sk_buff *skb, int err) +{ + unsigned short family; + struct net_device *dev; + struct pcpu_sw_netstats *tstats; + struct xfrm_state *x; + struct ip6_tnl *t = XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6; + + if (!t) + return 1; + + dev = t->dev; + + if (err) { + dev->stats.rx_errors++; + dev->stats.rx_dropped++; + + return 0; + } + + x = xfrm_input_state(skb); + family = x->inner_mode->afinfo->family; + + if (!xfrm_policy_check(NULL, XFRM_POLICY_IN, skb, family)) + return -EPERM; + + skb_scrub_packet(skb, !net_eq(t->net, dev_net(skb->dev))); + skb->dev = dev; + + tstats = this_cpu_ptr(dev->tstats); + u64_stats_update_begin(&tstats->syncp); + tstats->rx_packets++; + tstats->rx_bytes += skb->len; + u64_stats_update_end(&tstats->syncp); + + return 0; +} + /** * vti6_addr_conflict - compare packet addresses to tunnel's own * @t: the outgoing tunnel device @@ -394,7 +422,6 @@ static int vti6_xmit(struct sk_buff *skb, struct net_device *dev) goto tx_err_dst_release; } - memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); skb_scrub_packet(skb, !net_eq(t->net, dev_net(dev))); skb_dst_set(skb, dst); @@ -438,9 +465,60 @@ tx_err: return NETDEV_TX_OK; } +static int vti6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + u8 type, u8 code, int offset, __be32 info) +{ + __be32 spi; + struct xfrm_state *x; + struct ip6_tnl *t; + struct ip_esp_hdr *esph; + struct ip_auth_hdr *ah; + struct ip_comp_hdr *ipch; + struct net *net = dev_net(skb->dev); + const struct ipv6hdr *iph = (const struct ipv6hdr *)skb->data; + int protocol = iph->nexthdr; + + t = vti6_tnl_lookup(dev_net(skb->dev), &iph->daddr, &iph->saddr); + if (!t) + return -1; + + switch (protocol) { + case IPPROTO_ESP: + esph = (struct ip_esp_hdr *)(skb->data + offset); + spi = esph->spi; + break; + case IPPROTO_AH: + ah = (struct ip_auth_hdr *)(skb->data + offset); + spi = ah->spi; + break; + case IPPROTO_COMP: + ipch = (struct ip_comp_hdr *)(skb->data + offset); + spi = htonl(ntohs(ipch->cpi)); + break; + default: + return 0; + } + + if (type != ICMPV6_PKT_TOOBIG && + type != NDISC_REDIRECT) + return 0; + + x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, + spi, protocol, AF_INET6); + if (!x) + return 0; + + if (type == NDISC_REDIRECT) + ip6_redirect(skb, net, skb->dev->ifindex, 0); + else + ip6_update_pmtu(skb, net, info, 0, 0); + xfrm_state_put(x); + + return 0; +} + static void vti6_link_config(struct ip6_tnl *t) { - struct dst_entry *dst; struct net_device *dev = t->dev; struct __ip6_tnl_parm *p = &t->parms; @@ -871,11 +949,6 @@ static struct rtnl_link_ops vti6_link_ops __read_mostly = { .fill_info = vti6_fill_info, }; -static struct xfrm_tunnel_notifier vti6_handler __read_mostly = { - .handler = vti6_rcv, - .priority = 1, -}; - static void __net_exit vti6_destroy_tunnels(struct vti6_net *ip6n) { int h; @@ -947,6 +1020,27 @@ static struct pernet_operations vti6_net_ops = { .size = sizeof(struct vti6_net), }; +static struct xfrm6_protocol vti_esp6_protocol __read_mostly = { + .handler = vti6_rcv, + .cb_handler = vti6_rcv_cb, + .err_handler = vti6_err, + .priority = 100, +}; + +static struct xfrm6_protocol vti_ah6_protocol __read_mostly = { + .handler = vti6_rcv, + .cb_handler = vti6_rcv_cb, + .err_handler = vti6_err, + .priority = 100, +}; + +static struct xfrm6_protocol vti_ipcomp6_protocol __read_mostly = { + .handler = vti6_rcv, + .cb_handler = vti6_rcv_cb, + .err_handler = vti6_err, + .priority = 100, +}; + /** * vti6_tunnel_init - register protocol and reserve needed resources * @@ -960,11 +1054,33 @@ static int __init vti6_tunnel_init(void) if (err < 0) goto out_pernet; - err = xfrm6_mode_tunnel_input_register(&vti6_handler); + err = xfrm6_protocol_register(&vti_esp6_protocol, IPPROTO_ESP); if (err < 0) { - pr_err("%s: can't register vti6\n", __func__); + unregister_pernet_device(&vti6_net_ops); + pr_err("%s: can't register vti6 protocol\n", __func__); + goto out; } + + err = xfrm6_protocol_register(&vti_ah6_protocol, IPPROTO_AH); + if (err < 0) { + xfrm6_protocol_deregister(&vti_esp6_protocol, IPPROTO_ESP); + unregister_pernet_device(&vti6_net_ops); + pr_err("%s: can't register vti6 protocol\n", __func__); + + goto out; + } + + err = xfrm6_protocol_register(&vti_ipcomp6_protocol, IPPROTO_COMP); + if (err < 0) { + xfrm6_protocol_deregister(&vti_ah6_protocol, IPPROTO_AH); + xfrm6_protocol_deregister(&vti_esp6_protocol, IPPROTO_ESP); + unregister_pernet_device(&vti6_net_ops); + pr_err("%s: can't register vti6 protocol\n", __func__); + + goto out; + } + err = rtnl_link_register(&vti6_link_ops); if (err < 0) goto rtnl_link_failed; @@ -972,7 +1088,9 @@ static int __init vti6_tunnel_init(void) return 0; rtnl_link_failed: - xfrm6_mode_tunnel_input_deregister(&vti6_handler); + xfrm6_protocol_deregister(&vti_ipcomp6_protocol, IPPROTO_COMP); + xfrm6_protocol_deregister(&vti_ah6_protocol, IPPROTO_AH); + xfrm6_protocol_deregister(&vti_esp6_protocol, IPPROTO_ESP); out: unregister_pernet_device(&vti6_net_ops); out_pernet: @@ -985,8 +1103,12 @@ out_pernet: static void __exit vti6_tunnel_cleanup(void) { rtnl_link_unregister(&vti6_link_ops); - if (xfrm6_mode_tunnel_input_deregister(&vti6_handler)) - pr_info("%s: can't deregister vti6\n", __func__); + if (xfrm6_protocol_deregister(&vti_ipcomp6_protocol, IPPROTO_COMP)) + pr_info("%s: can't deregister protocol\n", __func__); + if (xfrm6_protocol_deregister(&vti_ah6_protocol, IPPROTO_AH)) + pr_info("%s: can't deregister protocol\n", __func__); + if (xfrm6_protocol_deregister(&vti_esp6_protocol, IPPROTO_ESP)) + pr_info("%s: can't deregister protocol\n", __func__); unregister_pernet_device(&vti6_net_ops); } -- cgit v1.1 From 573ce1c11b0d93a08b988d2713ef02214404aad1 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:08 +0100 Subject: xfrm6: Remove xfrm_tunnel_notifier This was used from vti and is replaced by the IPsec protocol multiplexer hooks. It is now unused, so remove it. Signed-off-by: Steffen Klassert --- net/ipv6/xfrm6_mode_tunnel.c | 63 -------------------------------------------- 1 file changed, 63 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/xfrm6_mode_tunnel.c b/net/ipv6/xfrm6_mode_tunnel.c index cb04f7a..901ef6f 100644 --- a/net/ipv6/xfrm6_mode_tunnel.c +++ b/net/ipv6/xfrm6_mode_tunnel.c @@ -18,65 +18,6 @@ #include #include -/* Informational hook. The decap is still done here. */ -static struct xfrm_tunnel_notifier __rcu *rcv_notify_handlers __read_mostly; -static DEFINE_MUTEX(xfrm6_mode_tunnel_input_mutex); - -int xfrm6_mode_tunnel_input_register(struct xfrm_tunnel_notifier *handler) -{ - struct xfrm_tunnel_notifier __rcu **pprev; - struct xfrm_tunnel_notifier *t; - int ret = -EEXIST; - int priority = handler->priority; - - mutex_lock(&xfrm6_mode_tunnel_input_mutex); - - for (pprev = &rcv_notify_handlers; - (t = rcu_dereference_protected(*pprev, - lockdep_is_held(&xfrm6_mode_tunnel_input_mutex))) != NULL; - pprev = &t->next) { - if (t->priority > priority) - break; - if (t->priority == priority) - goto err; - - } - - handler->next = *pprev; - rcu_assign_pointer(*pprev, handler); - - ret = 0; - -err: - mutex_unlock(&xfrm6_mode_tunnel_input_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(xfrm6_mode_tunnel_input_register); - -int xfrm6_mode_tunnel_input_deregister(struct xfrm_tunnel_notifier *handler) -{ - struct xfrm_tunnel_notifier __rcu **pprev; - struct xfrm_tunnel_notifier *t; - int ret = -ENOENT; - - mutex_lock(&xfrm6_mode_tunnel_input_mutex); - for (pprev = &rcv_notify_handlers; - (t = rcu_dereference_protected(*pprev, - lockdep_is_held(&xfrm6_mode_tunnel_input_mutex))) != NULL; - pprev = &t->next) { - if (t == handler) { - *pprev = handler->next; - ret = 0; - break; - } - } - mutex_unlock(&xfrm6_mode_tunnel_input_mutex); - synchronize_net(); - - return ret; -} -EXPORT_SYMBOL_GPL(xfrm6_mode_tunnel_input_deregister); - static inline void ipip6_ecn_decapsulate(struct sk_buff *skb) { const struct ipv6hdr *outer_iph = ipv6_hdr(skb); @@ -130,7 +71,6 @@ static int xfrm6_mode_tunnel_output(struct xfrm_state *x, struct sk_buff *skb) static int xfrm6_mode_tunnel_input(struct xfrm_state *x, struct sk_buff *skb) { - struct xfrm_tunnel_notifier *handler; int err = -EINVAL; if (XFRM_MODE_SKB_CB(skb)->protocol != IPPROTO_IPV6) @@ -138,9 +78,6 @@ static int xfrm6_mode_tunnel_input(struct xfrm_state *x, struct sk_buff *skb) if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) goto out; - for_each_input_rcu(rcv_notify_handlers, handler) - handler->handler(skb); - err = skb_unclone(skb, GFP_ATOMIC); if (err) goto out; -- cgit v1.1 From 22e1b23dafa8554ef722454e8b84645820cbbc17 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:08 +0100 Subject: vti6: Support inter address family tunneling. With this patch we can tunnel ipv4 traffic via a vti6 interface. A vti6 interface can now have an ipv4 address and ipv4 traffic can be routed via a vti6 interface. The resulting traffic is xfrm transformed and tunneled through ipv6 if matching IPsec policies and states are present. Signed-off-by: Steffen Klassert --- net/ipv6/ip6_vti.c | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index 226854a..8d189aa 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -380,30 +380,22 @@ vti6_addr_conflict(const struct ip6_tnl *t, const struct ipv6hdr *hdr) * vti6_xmit - send a packet * @skb: the outgoing socket buffer * @dev: the outgoing tunnel device + * @fl: the flow informations for the xfrm_lookup **/ -static int vti6_xmit(struct sk_buff *skb, struct net_device *dev) +static int +vti6_xmit(struct sk_buff *skb, struct net_device *dev, struct flowi *fl) { struct ip6_tnl *t = netdev_priv(dev); struct net_device_stats *stats = &t->dev->stats; struct dst_entry *dst = skb_dst(skb); - struct flowi fl; - struct ipv6hdr *ipv6h = ipv6_hdr(skb); struct net_device *tdev; int err = -1; - if ((t->parms.proto != IPPROTO_IPV6 && t->parms.proto != 0) || - !ip6_tnl_xmit_ctl(t) || vti6_addr_conflict(t, ipv6h)) - return err; - - memset(&fl, 0, sizeof(fl)); - skb->mark = be32_to_cpu(t->parms.o_key); - xfrm_decode_session(skb, &fl, AF_INET6); - if (!dst) goto tx_err_link_failure; dst_hold(dst); - dst = xfrm_lookup(t->net, dst, &fl, NULL, 0); + dst = xfrm_lookup(t->net, dst, fl, NULL, 0); if (IS_ERR(dst)) { err = PTR_ERR(dst); dst = NULL; @@ -422,12 +414,22 @@ static int vti6_xmit(struct sk_buff *skb, struct net_device *dev) goto tx_err_dst_release; } - memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); skb_scrub_packet(skb, !net_eq(t->net, dev_net(dev))); skb_dst_set(skb, dst); skb->dev = skb_dst(skb)->dev; - ip6tunnel_xmit(skb, dev); + err = dst_output(skb); + if (net_xmit_eval(err) == 0) { + struct pcpu_sw_netstats *tstats = this_cpu_ptr(dev->tstats); + + u64_stats_update_begin(&tstats->syncp); + tstats->tx_bytes += skb->len; + tstats->tx_packets++; + u64_stats_update_end(&tstats->syncp); + } else { + stats->tx_errors++; + stats->tx_aborted_errors++; + } return 0; tx_err_link_failure: @@ -443,16 +445,33 @@ vti6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip6_tnl *t = netdev_priv(dev); struct net_device_stats *stats = &t->dev->stats; + struct ipv6hdr *ipv6h; + struct flowi fl; int ret; + memset(&fl, 0, sizeof(fl)); + skb->mark = be32_to_cpu(t->parms.o_key); + switch (skb->protocol) { case htons(ETH_P_IPV6): - ret = vti6_xmit(skb, dev); + ipv6h = ipv6_hdr(skb); + + if ((t->parms.proto != IPPROTO_IPV6 && t->parms.proto != 0) || + !ip6_tnl_xmit_ctl(t) || vti6_addr_conflict(t, ipv6h)) + goto tx_err; + + xfrm_decode_session(skb, &fl, AF_INET6); + memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); + break; + case htons(ETH_P_IP): + xfrm_decode_session(skb, &fl, AF_INET); + memset(IPCB(skb), 0, sizeof(*IPCB(skb))); break; default: goto tx_err; } + ret = vti6_xmit(skb, dev, &fl); if (ret < 0) goto tx_err; -- cgit v1.1 From 26be8e2db4352d1a69fa40b1126023efd2a26197 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:09 +0100 Subject: vti6: Check the tunnel endpoints of the xfrm state and the vti interface The tunnel endpoints of the xfrm_state we got from the xfrm_lookup must match the tunnel endpoints of the vti interface. This patch ensures this matching. Signed-off-by: Steffen Klassert --- net/ipv6/ip6_vti.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index 8d189aa..6085793 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -376,6 +376,29 @@ vti6_addr_conflict(const struct ip6_tnl *t, const struct ipv6hdr *hdr) return ipv6_addr_equal(&t->parms.raddr, &hdr->saddr); } +static bool vti6_state_check(const struct xfrm_state *x, + const struct in6_addr *dst, + const struct in6_addr *src) +{ + xfrm_address_t *daddr = (xfrm_address_t *)dst; + xfrm_address_t *saddr = (xfrm_address_t *)src; + + /* if there is no transform then this tunnel is not functional. + * Or if the xfrm is not mode tunnel. + */ + if (!x || x->props.mode != XFRM_MODE_TUNNEL || + x->props.family != AF_INET6) + return false; + + if (ipv6_addr_any(dst)) + return xfrm_addr_equal(saddr, &x->props.saddr, AF_INET6); + + if (!xfrm_state_addr_check(x, daddr, saddr, AF_INET6)) + return false; + + return true; +} + /** * vti6_xmit - send a packet * @skb: the outgoing socket buffer @@ -402,7 +425,7 @@ vti6_xmit(struct sk_buff *skb, struct net_device *dev, struct flowi *fl) goto tx_err_link_failure; } - if (!dst->xfrm || dst->xfrm->props.mode != XFRM_MODE_TUNNEL) + if (!vti6_state_check(dst->xfrm, &t->parms.raddr, &t->parms.laddr)) goto tx_err_link_failure; tdev = dst->dev; -- cgit v1.1 From 61220ab349485d911083d0b7990ccd3db6c63297 Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Fri, 14 Mar 2014 07:28:09 +0100 Subject: vti6: Enable namespace changing vti6 is now fully namespace aware, so allow namespace changing for vti devices. Signed-off-by: Steffen Klassert --- net/ipv6/ip6_vti.c | 1 - 1 file changed, 1 deletion(-) (limited to 'net/ipv6') diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index 6085793..b7c0f82 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -803,7 +803,6 @@ static void vti6_dev_setup(struct net_device *dev) t = netdev_priv(dev); dev->flags |= IFF_NOARP; dev->addr_len = sizeof(struct in6_addr); - dev->features |= NETIF_F_NETNS_LOCAL; dev->priv_flags &= ~IFF_XMIT_DST_RELEASE; } -- cgit v1.1