xt_TPROXY does not function properly under ddwrt (/linux 4.4) when used with IPv6After digging the issue for about two days, it looks like to me that it’s caused by a bug in ipv6.ko.
Unfortunately the rootfs is readonly on ddwrt, thus patching ipv6.ko
is not a way for me to go.
The reason xt_TPROXY
does not work is that ipv6.ko
does not mark the newly accept
ed socket as no_srccheck
. no_srccheck
is used to mark a connection as “transparent”, and xt_TPROXY
only redirects packets on these “transparent” connections. As ipv6.ko
failes to mark the connection as “transparent”, xt_TPROXY
drops the subsequent packets, including ACK
from the remote side to our SYN/ACK
.
As the ACK
from remote side is dropped, the connection is never established from the router’s perspective, and stays in SYN_RECV
state forever. Although the connection is seen as ESTABLISHED
by the remote side once ACK
is sent.
After trying and failing to replace ddwrt’s own ipv6.ko
in startup commands with mount -o bind
, I worked around the bug in xt_TPROXY.ko
by initializing no_srcheck
myself, as follows.
(Since tabs are converted to spaces by the editor, the patch below might not apply cleanly. I also attached the patch here, which should apply without errors.)
12345678910111213141516171819202122 diff -uNr a/net/netfilter/xt_TPROXY.c b/net/netfilter/xt_TPROXY.c--- a/net/netfilter/xt_TPROXY.c 2018-10-28 20:43:56.811086856 +0800+++ b/net/netfilter/xt_TPROXY.c 2018-10-28 20:43:45.663160887 +0800@@ -480,6 +480,18 @@ laddr = tproxy_laddr6(skb, &tgi->laddr.in6, &iph->daddr); lport = tgi->lport ? tgi->lport : hp->dest; + /* Workaroud a bug in ipv6 of 4.4 kernel. The new socket's `no_srccheck` is not+ * initialized properly on `accept` there. */+ if (sk && sk->sk_state == TCP_NEW_SYN_RECV) {+ struct sock *listener;++ listener = nf_tproxy_get_sock_v6(par->net, tproto, &iph->saddr, laddr,+ hp->source, lport, par->in,+ NFT_LOOKUP_LISTENER);+ inet_rsk(inet_reqsk(sk))->no_srccheck = inet_sk(listener)->transparent;+ /* Refcount on `listener` was not taken. */+ }+ /* UDP has no TCP_TIME_WAIT state, so we never enter here */ if (sk && sk->sk_state == TCP_TIME_WAIT) /* reopening a TIME_WAIT connection needs special handling */
After digging the issue for about two days, it looks like to me that it’s caused by a bug in ipv6.ko.
Unfortunately the rootfs is readonly on ddwrt, thus patching ipv6.ko
is not a way for me to go.
The reason xt_TPROXY
does not work is that ipv6.ko
does not mark the newly accept
ed socket as no_srccheck
. no_srccheck
is used to mark a connection as “transparent”, and xt_TPROXY
only redirects packets on these “transparent” connections. As ipv6.ko
failes to mark the connection as “transparent”, xt_TPROXY
drops the subsequent packets, including ACK
from the remote side to our SYN/ACK
.
As the ACK
from remote side is dropped, the connection is never established from the router’s perspective, and stays in SYN_RECV
state forever. Although the connection is seen as ESTABLISHED
by the remote side once ACK
is sent.
After trying and failing to replace ddwrt’s own ipv6.ko
in startup commands with mount -o bind
, I worked around the bug in xt_TPROXY.ko
by initializing no_srcheck
myself, as follows.
(Since tabs are converted to spaces by the editor, the patch below might not apply cleanly. I also attached the patch here, which should apply without errors.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | diff -uNr a/net/netfilter/xt_TPROXY.c b/net/netfilter/xt_TPROXY.c --- a/net/netfilter/xt_TPROXY.c 2018-10-28 20:43:56.811086856 +0800 +++ b/net/netfilter/xt_TPROXY.c 2018-10-28 20:43:45.663160887 +0800 @@ -480,6 +480,18 @@ laddr = tproxy_laddr6(skb, &tgi->laddr.in6, &iph->daddr); lport = tgi->lport ? tgi->lport : hp->dest; + /* Workaroud a bug in ipv6 of 4.4 kernel. The new socket's `no_srccheck` is not + * initialized properly on `accept` there. */ + if (sk && sk->sk_state == TCP_NEW_SYN_RECV) { + struct sock *listener; + + listener = nf_tproxy_get_sock_v6(par->net, tproto, &iph->saddr, laddr, + hp->source, lport, par->in, + NFT_LOOKUP_LISTENER); + inet_rsk(inet_reqsk(sk))->no_srccheck = inet_sk(listener)->transparent; + /* Refcount on `listener` was not taken. */ + } + /* UDP has no TCP_TIME_WAIT state, so we never enter here */ if (sk && sk->sk_state == TCP_TIME_WAIT) /* reopening a TIME_WAIT connection needs special handling */ |