aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-02-10 20:33:35 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2020-02-12 20:13:12 +0100
commitfb2b4ccf24b5ce56e58de9d99b67f2a6258e36b3 (patch)
treeb13c19389bc60d0d8f3a210ad14378a7a4fc335b
parentchacha20poly1305: defensively protect against large inputs (diff)
downloadwireguard-linux-compat-fb2b4ccf24b5ce56e58de9d99b67f2a6258e36b3.tar.xz
wireguard-linux-compat-fb2b4ccf24b5ce56e58de9d99b67f2a6258e36b3.zip
netns: ensure that icmp src address is correct with nat
This is a small test to ensure that icmp_ndo_send is actually doing the right with with regards to the source address. It tests this by ensuring that the error comes back along the right path. Also, backport the new ndo function for this. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--src/compat/compat.h122
-rw-r--r--src/device.c4
-rwxr-xr-xsrc/tests/netns.sh11
3 files changed, 101 insertions, 36 deletions
diff --git a/src/compat/compat.h b/src/compat/compat.h
index 948d3fe..5978fe9 100644
--- a/src/compat/compat.h
+++ b/src/compat/compat.h
@@ -932,6 +932,94 @@ static inline void skb_mark_not_on_list(struct sk_buff *skb)
#define chacha20_neon zinc_chacha20_neon
#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
+#include <linux/skbuff.h>
+static inline int skb_ensure_writable(struct sk_buff *skb, int write_len)
+{
+ if (!pskb_may_pull(skb, write_len))
+ return -ENOMEM;
+
+ if (!skb_cloned(skb) || skb_clone_writable(skb, write_len))
+ return 0;
+
+ return pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
+}
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
+#if IS_ENABLED(CONFIG_NF_NAT)
+#include <linux/ip.h>
+#include <linux/icmpv6.h>
+#include <net/ipv6.h>
+#include <net/icmp.h>
+#include <net/netfilter/nf_conntrack.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0)
+#include <net/netfilter/nf_nat_core.h>
+#endif
+static inline void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
+{
+ struct sk_buff *cloned_skb = NULL;
+ enum ip_conntrack_info ctinfo;
+ struct nf_conn *ct;
+ __be32 orig_ip;
+
+ ct = nf_ct_get(skb_in, &ctinfo);
+ if (!ct || !(ct->status & IPS_SRC_NAT)) {
+ icmp_send(skb_in, type, code, info);
+ return;
+ }
+
+ if (skb_shared(skb_in))
+ skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
+
+ if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
+ (skb_network_header(skb_in) + sizeof(struct iphdr)) >
+ skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
+ skb_network_offset(skb_in) + sizeof(struct iphdr))))
+ goto out;
+
+ orig_ip = ip_hdr(skb_in)->saddr;
+ ip_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.ip;
+ icmp_send(skb_in, type, code, info);
+ ip_hdr(skb_in)->saddr = orig_ip;
+out:
+ consume_skb(cloned_skb);
+}
+static inline void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
+{
+ struct sk_buff *cloned_skb = NULL;
+ enum ip_conntrack_info ctinfo;
+ struct in6_addr orig_ip;
+ struct nf_conn *ct;
+
+ ct = nf_ct_get(skb_in, &ctinfo);
+ if (!ct || !(ct->status & IPS_SRC_NAT)) {
+ icmpv6_send(skb_in, type, code, info);
+ return;
+ }
+
+ if (skb_shared(skb_in))
+ skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
+
+ if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
+ (skb_network_header(skb_in) + sizeof(struct ipv6hdr)) >
+ skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
+ skb_network_offset(skb_in) + sizeof(struct ipv6hdr))))
+ goto out;
+
+ orig_ip = ipv6_hdr(skb_in)->saddr;
+ ipv6_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.in6;
+ icmpv6_send(skb_in, type, code, info);
+ ipv6_hdr(skb_in)->saddr = orig_ip;
+out:
+ consume_skb(cloned_skb);
+}
+#else
+#define icmp_ndo_send icmp_send
+#define icmpv6_ndo_send icmpv6_send
+#endif
+#endif
+
#if defined(ISUBUNTU1604)
#include <linux/siphash.h>
#ifndef _WG_LINUX_SIPHASH_H
@@ -956,40 +1044,6 @@ static inline void skb_mark_not_on_list(struct sk_buff *skb)
#define BUILD_BUG_ON(x)
#endif
-/* https://lkml.kernel.org/r/20170624021727.17835-1-Jason@zx2c4.com */
-#if IS_ENABLED(CONFIG_NF_CONNTRACK)
-#include <linux/ip.h>
-#include <linux/icmpv6.h>
-#include <net/ipv6.h>
-#include <net/icmp.h>
-#include <net/netfilter/nf_conntrack.h>
-#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0)
-#include <net/netfilter/nf_nat_core.h>
-#endif
-static inline void new_icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info)
-{
- enum ip_conntrack_info ctinfo;
- struct nf_conn *ct = nf_ct_get(skb_in, &ctinfo);
- if (skb_network_header(skb_in) < skb_in->head || (skb_network_header(skb_in) + sizeof(struct iphdr)) > skb_tail_pointer(skb_in))
- return;
- if (ct)
- ip_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.ip;
- icmp_send(skb_in, type, code, info);
-}
-static inline void new_icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
-{
- enum ip_conntrack_info ctinfo;
- struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
- if (skb_network_header(skb) < skb->head || (skb_network_header(skb) + sizeof(struct ipv6hdr)) > skb_tail_pointer(skb))
- return;
- if (ct)
- ipv6_hdr(skb)->saddr = ct->tuplehash[0].tuple.src.u3.in6;
- icmpv6_send(skb, type, code, info);
-}
-#define icmp_send(a,b,c,d) new_icmp_send(a,b,c,d)
-#define icmpv6_send(a,b,c,d) new_icmpv6_send(a,b,c,d)
-#endif
-
/* PaX compatibility */
#ifdef CONSTIFY_PLUGIN
#include <linux/cache.h>
diff --git a/src/device.c b/src/device.c
index e888ac3..2883bad 100644
--- a/src/device.c
+++ b/src/device.c
@@ -211,9 +211,9 @@ err_peer:
err:
++dev->stats.tx_errors;
if (skb->protocol == htons(ETH_P_IP))
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
+ icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
else if (skb->protocol == htons(ETH_P_IPV6))
- icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0);
+ icmpv6_ndo_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0);
kfree_skb(skb);
return ret;
}
diff --git a/src/tests/netns.sh b/src/tests/netns.sh
index 315ccc4..ba5f83a 100755
--- a/src/tests/netns.sh
+++ b/src/tests/netns.sh
@@ -24,6 +24,7 @@
set -e
exec 3>&1
+export LANG=C
export WG_HIDE_KEYS=never
netns0="wg-test-$$-0"
netns1="wg-test-$$-1"
@@ -300,7 +301,17 @@ if [[ $(ip1 -4 rule show all) == *suppress_prefixlength* ]]; then
n1 ping -W 1 -c 100 -f abab::1111
fi
+# Have ns2 NAT into wg0 packets from ns0, but return an icmp error along the right route.
+n2 iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -d 192.168.241.0/24 -j SNAT --to 192.168.241.2
+n0 iptables -t filter -A INPUT \! -s 10.0.0.0/24 -i vethrs -j DROP # Manual rpfilter just to be explicit.
+n2 bash -c 'printf 1 > /proc/sys/net/ipv4/ip_forward'
+ip0 -4 route add 192.168.241.1 via 10.0.0.100
+n2 wg set wg0 peer "$pub1" remove
+[[ $(! n0 ping -W 1 -c 1 192.168.241.1 || false) == *"From 10.0.0.100 icmp_seq=1 Destination Host Unreachable"* ]]
+
n0 iptables -t nat -F
+n0 iptables -t filter -F
+n2 iptables -t nat -F
ip0 link del vethrc
ip0 link del vethrs
ip1 link del wg0