netfilter: nf_nat: support user-specified SNAT rules in LOCAL_IN
Subsystems (2)
RFC PATCH 1/1 netfilter: nf_nat: support user-specified SNAT rules in LOCAL_IN
The attached patch adds support for user-specified SNAT rules in nat/LOCAL_IN. One possible use case is explained in the changelog.
The downside is a small performance penalty: NAT already registers a hook for LOCAL_IN, with this patch we invoke nf_nat_rule_find() for each new connection going through LOCAL_IN. It would be possible quite easily to make this behaviour controllable through a module parameter, but I'd prefer to keep those to the necessary minimum.
Any opinions or objections to this patch? Otherwise I'll queue it up in my nf-next tree.
commit a9d252f5dbee323808b4356e9edcf41e2ea2d225
Author: Patrick McHardy <[email protected]> Date: Wed Jun 16 16:55:44 2010 +0200
netfilter: nf_nat: support user-specified SNAT rules in LOCAL_IN
2.6.34 introduced 'conntrack zones' to deal with cases where packets from multiple identical networks are handled by conntrack/NAT. Packets are looped through veth devices, during which they are NATed to private addresses, after which they can continue normally through the stack and possibly have NAT rules applied a second time.
This works well, but is needlessly complicated for cases where only a single SNAT/DNAT mapping needs to be applied to these packets. In that case, all that needs to be done is to assign each network to a seperate zone and perform NAT as usual. However this doesn't work for packets destined for the machine performing NAT itself since its corrently not possible to configure SNAT mappings for the LOCAL_IN chain.
This patch adds a new INPUT chain to the NAT table and changes the targets performing SNAT to be usable in that chain.
Example usage with two identical networks (192.168.0.0/24) on eth0/eth1:
# assign packets from each interface to a seperate zone and mark them for NAT
iptables -t raw -A PREROUTING -i eth0 -j CT --zone 1 iptables -t raw -A PREROUTING -i eth0 -j MARK --set-mark 1 iptables -t raw -A PREROUTING -i eth1 -j CT --zone 2 iptabels -t raw -A PREROUTING -i eth1 -j MARK --set-mark 2
# SNAT packets to private networks: eth0 -> 10.0.0.0/24, eth1 -> 10.0.1.0/24
iptables -t nat -A INPUT -m mark --mark 1 -j NETMAP --to 10.0.0.0/24
iptables -t nat -A POSTROUTING -m mark --mark 1 -j NETMAP --to 10.0.0.0/24
iptables -t nat -A INPUT -m mark --mark 2 -j NETMAP --to 10.0.1.0/24
iptables -t nat -A POSTROUTING -m mark --mark 2 -j NETMAP --to 10.0.1.0/24
# assign packets in the reverse direction to proper zone
iptables -t raw -A PREROUTING -d 10.0.0.0/24 -j CT --zone 1
iptables -t raw -A OUTPUT -d 10.0.0.0/24 -j CT --zone 1
iptables -t raw -A PREROUTING -d 10.0.1.0/24 -j CT --zone 2
iptables -t raw -A OUTPUT -d 10.0.1.0/24 -j CT --zone 2
# Optional: DNAT new connections in the reverse direction to the correct addresses.
iptables -t nat -A PREROUTING -d 10.0.0.0/24 -j NETMAP --to 192.168.0.0/24
iptables -t nat -A OUTPUT -d 10.0.0.0/24 -j NETMAP --to 192.168.0.0/24
iptables -t nat -A PREROUTING -d 10.0.1.0/24 -j NETMAP --to 192.168.0.0/24
iptables -t nat -A OUTPUT -d 10.0.1.0/24 -j NETMAP --to 192.168.0.0/24
include/net/netfilter/nf_nat_rule.h +0 -2
@@ -12,6 +12,4 @@ extern int nf_nat_rule_find(struct sk_buff *skb,const struct net_device *out, struct nf_conn *ct); -extern unsigned int -alloc_null_binding (struct nf_conn *ct,unsigned int hooknum);#endif /* _NF_NAT_RULE_H */
net/ipv4/netfilter/ipt_NETMAP.c +4 -2
@@ -48,7 +48,8 @@ netmap_tg(struct sk_buff *skb, const struct xt_target_param *par)NF_CT_ASSERT (par->hooknum ==NF_INET_PRE_ROUTING || par->hooknum ==NF_INET_POST_ROUTING || - par->hooknum ==NF_INET_LOCAL_OUT ); + par->hooknum ==NF_INET_LOCAL_OUT || + par->hooknum ==NF_INET_LOCAL_IN ); ct =nf_ct_get (skb, &ctinfo); netmask = ~(mr->range[0 ].min_ip ^ mr->range[0 ].max_ip);
@@ -77,7 +78,8 @@ static struct xt_target netmap_tg_reg __read_mostly = { .table = "nat", .hooks = (1 <<NF_INET_PRE_ROUTING ) | (1 <<NF_INET_POST_ROUTING ) | - (1 << NF_INET_LOCAL_OUT), + (1 <<NF_INET_LOCAL_OUT ) | + (1 << NF_INET_LOCAL_IN), .checkentry = netmap_tg_check, .me =THIS_MODULE };
net/ipv4/netfilter/nf_nat_rule.c +6 -4
@@ -27,7 +27,8 @@#define NAT_VALID_HOOKS ((1 << NF_INET_PRE_ROUTING) | \ (1 << NF_INET_POST_ROUTING) | \ - (1 << NF_INET_LOCAL_OUT)) + (1 << NF_INET_LOCAL_OUT) | \ + (1 << NF_INET_LOCAL_IN))static const struct xt_table nat_table = { .name ="nat" ,
@@ -44,7 +45,8 @@ ipt_snat_target(struct sk_buff *skb, const struct xt_target_param *par)enum ip_conntrack_info ctinfo;const struct nf_nat_multi_range_compat *mr = par->targinfo; -NF_CT_ASSERT (par->hooknum ==NF_INET_POST_ROUTING ); +NF_CT_ASSERT (par->hooknum ==NF_INET_POST_ROUTING || + par->hooknum ==NF_INET_LOCAL_IN ); ct =nf_ct_get (skb, &ctinfo);
@@ -98,7 +100,7 @@ static bool ipt_dnat_checkentry(const struct xt_tgchk_param *par)return true; } -unsigned int +static unsigned int alloc_null_binding (struct nf_conn *ct,unsigned int hooknum) {/* Force range to this IP; let proto decide mapping for
@@ -140,7 +142,7 @@ static struct xt_target ipt_snat_reg __read_mostly = { .target = ipt_snat_target, .targetsize = sizeof(struct nf_nat_multi_range_compat), .table = "nat", - .hooks = 1 << NF_INET_POST_ROUTING, + .hooks = (1 << NF_INET_POST_ROUTING) | (1 << NF_INET_LOCAL_IN), .checkentry = ipt_snat_checkentry, .family = AF_INET, };
net/ipv4/netfilter/nf_nat_standalone.c +1 -7
@@ -131,13 +131,7 @@ nf_nat_fn(unsigned int hooknum,if (!nf_nat_initialized (ct, maniptype)) {unsigned int ret; -if (hooknum ==NF_INET_LOCAL_IN ) -/* LOCAL_IN hook doesn't have a chain! */ - ret =alloc_null_binding (ct, hooknum); -else - ret =nf_nat_rule_find (skb, hooknum, in, out, - ct); - + ret =nf_nat_rule_find (skb, hooknum, in, out, ct);if (ret !=NF_ACCEPT ) {return ret; }
Quoted 15 lines · Patrick McHardy
This works well, but is needlessly complicated for cases where only a single SNAT/DNAT mapping needs to be applied to these packets. In that case, all that needs to be done is to assign each network to a seperate zone and perform NAT as usual. However this doesn't work for packets destined for the machine performing NAT itself since its corrently not possible to configure SNAT mappings for the LOCAL_IN chain. Example usage with two identical networks (192.168.0.0/24) on eth0/eth1: # assign packets from each interface to a seperate zone and mark them for NAT iptables -t raw -A PREROUTING -i eth0 -j CT --zone 1 iptables -t raw -A PREROUTING -i eth0 -j MARK --set-mark 1 iptables -t raw -A PREROUTING -i eth1 -j CT --zone 2
Quoted 8 lines
iptabels -t raw -A PREROUTING -i eth1 -j MARK --set-mark 2 # SNAT packets to private networks: eth0 -> 10.0.0.0/24, eth1 -> 10.0.1.0/24 iptables -t nat -A INPUT -m mark --mark 1 -j NETMAP --to 10.0.0.0/24 iptables -t nat -A POSTROUTING -m mark --mark 1 -j NETMAP --to 10.0.0.0/24 iptables -t nat -A INPUT -m mark --mark 2 -j NETMAP --to 10.0.1.0/24 iptables -t nat -A POSTROUTING -m mark --mark 2 -j NETMAP --to 10.0.1.0/24
I am not sure I follow whatever this is supposed to do.
Packet from eth0: src=10.0.0.15 dst=10.0.1.22 INPUT#NETMAP will dst transform that to dst=10.0.0.22 POSTROUTING#NETMAP will src transform that to src=10.0.0.15
Is is this step that makes no sense to me.
If you had two same-subnet networks, I'd rather
# raw table as shown -t nat -A PREROUTING -m mark --mark 1 -j DNAT/NETMAP --to 10.0.0.0/24 -t nat -A POSTROUTING -m mark --mark 1 -j SNAT/NETMAP --to 10.0.1.0/24
So that the packet then is src=10.0.1.15 dst=10.0.0.22.
That should also work when contacting the veth router itself, since it should have an address from each subnet.
Quoted 27 lines · Jan Engelhardt
Quoted 15 lines · Patrick McHardy
This works well, but is needlessly complicated for cases where only a single SNAT/DNAT mapping needs to be applied to these packets. In that case, all that needs to be done is to assign each network to a seperate zone and perform NAT as usual. However this doesn't work for packets destined for the machine performing NAT itself since its corrently not possible to configure SNAT mappings for the LOCAL_IN chain. Example usage with two identical networks (192.168.0.0/24) on eth0/eth1: # assign packets from each interface to a seperate zone and mark them for NAT iptables -t raw -A PREROUTING -i eth0 -j CT --zone 1 iptables -t raw -A PREROUTING -i eth0 -j MARK --set-mark 1 iptables -t raw -A PREROUTING -i eth1 -j CT --zone 2Quoted 9 lines
iptabels -t raw -A PREROUTING -i eth1 -j MARK --set-mark 2 # SNAT packets to private networks: eth0 -> 10.0.0.0/24, eth1 -> 10.0.1.0/24 iptables -t nat -A INPUT -m mark --mark 1 -j NETMAP --to 10.0.0.0/24 iptables -t nat -A POSTROUTING -m mark --mark 1 -j NETMAP --to 10.0.0.0/24 iptables -t nat -A INPUT -m mark --mark 2 -j NETMAP --to 10.0.1.0/24 iptables -t nat -A POSTROUTING -m mark --mark 2 -j NETMAP --to 10.0.1.0/24
Quoted 4 lines
I am not sure I follow whatever this is supposed to do.
Packet from eth0: src=10.0.0.15 dst=10.0.1.22 INPUT#NETMAP will dst transform that to dst=10.0.0.22
nat/INPUT performs source NAT, not destination NAT.
Quoted 3 lines
POSTROUTING#NETMAP will src transform that to src=10.0.0.15
Is is this step that makes no sense to me.
Does it make sense now?
Quoted 7 lines · Patrick McHardy
Quoted 5 lines · Jan Engelhardt
I am not sure I follow whatever this is supposed to do.
Packet from eth0: src=10.0.0.15 dst=10.0.1.22 INPUT#NETMAP will dst transform that to dst=10.0.0.22
Quoted 7 lines
nat/INPUT performs source NAT, not destination NAT.
Quoted 3 lines
POSTROUTING#NETMAP will src transform that to src=10.0.0.15
Is is this step that makes no sense to me.
Does it make sense now?
Somewhat, but there's still
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
However this doesn't work for packets destined for the machine performing NAT itself
Why would it not? What would cause misdelivery if PREROUTING was used instead of INPUT?
Quoted 22 lines · Jan Engelhardt
Quoted 7 lines · Patrick McHardy
Quoted 5 lines · Jan Engelhardt
I am not sure I follow whatever this is supposed to do.
Packet from eth0: src=10.0.0.15 dst=10.0.1.22 INPUT#NETMAP will dst transform that to dst=10.0.0.22
Quoted 9 lines
nat/INPUT performs source NAT, not destination NAT.
Quoted 4 lines
POSTROUTING#NETMAP will src transform that to src=10.0.0.15
Is is this step that makes no sense to me.
Does it make sense now?
Somewhat, but there's still
Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
However this doesn't work for packets destined for the machine performing NAT itself
Why would it not? What would cause misdelivery if PREROUTING was used instead of INPUT?
PREROUTING performs DNAT. The purpose is to map the two identical networks to non-clashing networks. Just consider two connections from the same source address and port number to the same destination.
Quoted 27 lines · Patrick McHardy
Quoted 26 lines · Jan Engelhardt
Quoted 7 lines · Patrick McHardy
Quoted 5 lines · Jan Engelhardt
I am not sure I follow whatever this is supposed to do.
Packet from eth0: src=10.0.0.15 dst=10.0.1.22 INPUT#NETMAP will dst transform that to dst=10.0.0.22
Quoted 9 lines
nat/INPUT performs source NAT, not destination NAT.
Quoted 4 lines
POSTROUTING#NETMAP will src transform that to src=10.0.0.15
Is is this step that makes no sense to me.
Does it make sense now?
Somewhat, but there's still
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
However this doesn't work for packets destined for the machine performing NAT itself
Quoted 9 lines
Quoted 3 lines
Why would it not? What would cause misdelivery if PREROUTING was used instead of INPUT?
PREROUTING performs DNAT. The purpose is to map the two identical networks to non-clashing networks. Just consider two connections from the same source address and port number to the same destination.
If veth0 has 10.0.0.0/24 and veth1 has 10.0.0.0/24, wouldn't Linux's ARP mechanism already be confused, in that it only sends ARP to the first network matching the subnet?
Quoted 11 lines · Jan Engelhardt
Quoted 6 lines · Patrick McHardy
PREROUTING performs DNAT. The purpose is to map the two identical networks to non-clashing networks. Just consider two connections from the same source address and port number to the same destination.
Quoted 5 lines
If veth0 has 10.0.0.0/24 and veth1 has 10.0.0.0/24, wouldn't Linux's ARP mechanism already be confused, in that it only sends ARP to the first network matching the subnet?
This patch is intended to be used *without* looping packets through veth. But good point, I chose that example to simplify things, the use case I'm interested in is actually tunnels. Apparently it wasn't the best possible example :)
Quoted 11 lines · Patrick McHardy
Quoted 10 lines
Quoted 5 lines
PREROUTING performs DNAT. The purpose is to map the two identical networks to non-clashing networks. Just consider two connections from the same source address and port number to the same destination.
If veth0 has 10.0.0.0/24 and veth1 has 10.0.0.0/24, wouldn't Linux's ARP mechanism already be confused, in that it only sends ARP to the first network matching the subnet?
Quoted 4 lines
This patch is intended to be used *without* looping packets through veth. But good point, I chose that example to simplify things, the use case I'm interested in is actually tunnels. Apparently it wasn't the best possible example :)
Now you completely lost me. Without separate namespaces and veth to exchange packets between them,
# ip a
8: iptnl1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN
link/ipip 5.6.7.8 peer 1.2.3.4
inet 10.0.0.1/24 scope global iptnl1
9: iptnl2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN
link/ipip 5.6.7.8 peer 9.10.11.12
inet 10.0.0.1/24 scope global iptnl2# ip r 10.0.0.0/24 dev iptnl1 proto kernel scope link src 10.0.0.1 10.0.0.0/24 dev iptnl2 proto kernel scope link src 10.0.0.1
will lead to exclusive delivery to iptnl1 for packets that originate from the router itself.
And for pure forwarding without considering the router,
-t mangle -A PREROUTING -i iptnl1 -j MARK 1 + -j CT 1
-t mangle -A PREROUTING -i iptnl2 -j MARK 1 + -j CT 2
-t nat -A PREROUTING -m mark --mark 1 -d 10.0.0.1 -j RETURN
-t nat -A PREROUTING -m mark --mark 1 -d 10.0.1.0/24 -j NETMAP --to 10.0.0.0/24 -t nat -A POSTROUTING -m mark --mark 1 -s 10.0.0.0/24 -j NETMAP --to 10.0.1.0/24
ip ru a from 10.0.0.0/24 to 10.0.0.0/24 dev iptnl1 table 1 ip r a default via iptnl2 table 1
ip ru a from 10.0.0.0/24 to 10.0.0.0/24 dev iptnl2 table 2 ip r a default via iptnl1 table 2
Seems sufficient.
Quoted 31 lines · Jan Engelhardt
Quoted 12 lines · Patrick McHardy
Quoted 7 lines
Quoted 7 lines
PREROUTING performs DNAT. The purpose is to map the two identical networks to non-clashing networks. Just consider two connections from the same source address and port number to the same destination.
Quoted 5 lines
If veth0 has 10.0.0.0/24 and veth1 has 10.0.0.0/24, wouldn't Linux's ARP mechanism already be confused, in that it only sends ARP to the first network matching the subnet?
Quoted 5 lines
This patch is intended to be used *without* looping packets through veth. But good point, I chose that example to simplify things, the use case I'm interested in is actually tunnels. Apparently it wasn't the best possible example :)
Now you completely lost me. Without separate namespaces and veth to exchange packets between them,
# ip a 8: iptnl1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN link/ipip 5.6.7.8 peer 1.2.3.4 inet 10.0.0.1/24 scope global iptnl1 9: iptnl2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN link/ipip 5.6.7.8 peer 9.10.11.12 inet 10.0.0.1/24 scope global iptnl2
Quoted 8 lines
# ip r 10.0.0.0/24 dev iptnl1 proto kernel scope link src 10.0.0.1 10.0.0.0/24 dev iptnl2 proto kernel scope link src 10.0.0.1will lead to exclusive delivery to iptnl1 for packets that originate from the router itself.
...
Seems sufficient.
How is that sufficient for talking to both networks?
Quoted 32 lines · Patrick McHardy
Quoted 23 lines
Quoted 11 lines
Quoted 6 lines
Quoted 1 line
If veth0 has 10.0.0.0/24 and veth1 has 10.0.0.0/24, wouldn't Linux's ARP mechanism already be confused, in that it only sends ARP to the first network matching the subnet?
This patch is intended to be used *without* looping packets through veth. But good point, I chose that example to simplify things, the use case I'm interested in is actually tunnels. Apparently it wasn't the best possible example :)
Now you completely lost me. Without separate namespaces and veth to exchange packets between them,
# ip a 8: iptnl1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN link/ipip 5.6.7.8 peer 1.2.3.4 inet 10.0.0.1/24 scope global iptnl1 9: iptnl2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN link/ipip 5.6.7.8 peer 9.10.11.12 inet 10.0.0.1/24 scope global iptnl2Quoted 8 lines
# ip r 10.0.0.0/24 dev iptnl1 proto kernel scope link src 10.0.0.1 10.0.0.0/24 dev iptnl2 proto kernel scope link src 10.0.0.1 will lead to exclusive delivery to iptnl1 for packets that originate from the router itself. ... Seems sufficient.
Quoted 1 line
How is that sufficient for talking to both networks?
Well I would like to know that.
I've just been trying to set this up in practice with three VMs, and somewhere after the nat table the routing table seems to drop packets and I can't figure the hell out why. Damn there's no TRACE target in routing, nor are there statistics.
*raw
:PREROUTING ACCEPT [26388:4065836]
:OUTPUT ACCEPT [26:2742]
-A PREROUTING -i iptnl6 -j CT --zone 6
-A PREROUTING -i iptnl13 -j CT --zone 13
COMMIT
# Completed on Mon Jun 28 16:38:13 2010
# Generated by iptables-save v1.4.8 on Mon Jun 28 16:38:13 2010
*nat
:PREROUTING ACCEPT [45485:7326470]
:INPUT ACCEPT [5:973]
:OUTPUT ACCEPT [9:646]
:POSTROUTING ACCEPT [9:646]
-A PREROUTING -s 10.0.0.0/24 -d 10.0.1.0/24 -i iptnl6 -j NETMAP --to 10.0.0.0/24 -A PREROUTING -s 10.0.0.0/24 -d 10.0.1.0/24 -i iptnl13 -j NETMAP --to 10.0.0.0/24 -A POSTROUTING -s 10.0.0.0/24 -d 10.0.0.0/24 -o iptnl6 -j NETMAP --to 10.0.1.0/24 -A POSTROUTING -s 10.0.0.0/24 -d 10.0.0.0/24 -o iptnl13 -j NETMAP --to 10.0.1.0/24 COMMIT
vestra:/proc/net # ip ru l
0: from all lookup local 32764: from all iif iptnl6 lookup 6 32765: from all iif iptnl13 lookup 13 32766: from all lookup main 32767: from all lookup default
vestra:/proc/net # ip r l table 6
default dev iptnl13 scope link
vestra:/proc/net # ip r l table 13
default dev iptnl6 scope link
It really _should_ be enough to do that.
(Yes, rp_filter=0, log_martians=1.)