mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-10 15:58:47 +00:00
netfilter: bridge: detect NAT66 correctly and change MAC address
IPv4 iptables allows to REDIRECT/DNAT/SNAT any traffic over a bridge. e.g. REDIRECT $ sysctl -w net.bridge.bridge-nf-call-iptables=1 $ iptables -t nat -A PREROUTING -p tcp -m tcp --dport 8080 \ -j REDIRECT --to-ports 81 This does not work with ip6tables on a bridge in NAT66 scenario because the REDIRECT/DNAT/SNAT is not correctly detected. The bridge pre-routing (finish) netfilter hook has to check for a possible redirect and then fix the destination mac address. This allows to use the ip6tables rules for local REDIRECT/DNAT/SNAT REDIRECT similar to the IPv4 iptables version. e.g. REDIRECT $ sysctl -w net.bridge.bridge-nf-call-ip6tables=1 $ ip6tables -t nat -A PREROUTING -p tcp -m tcp --dport 8080 \ -j REDIRECT --to-ports 81 This patch makes it possible to use IPv6 NAT66 on a bridge. It was tested on a bridge with two interfaces using SNAT/DNAT NAT66 rules. Reported-by: Artie Hamilton <artiemhamilton@yahoo.com> Signed-off-by: Sven Eckelmann <sven@open-mesh.com> [bernhard.thaler@wvnet.at: rebased, add indirect call to ip6_route_input()] [bernhard.thaler@wvnet.at: rebased, split into separate patches] Signed-off-by: Bernhard Thaler <bernhard.thaler@wvnet.at> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
parent
8cae308d2b
commit
72b31f7271
@ -25,6 +25,7 @@ void ipv6_netfilter_fini(void);
|
|||||||
struct nf_ipv6_ops {
|
struct nf_ipv6_ops {
|
||||||
int (*chk_addr)(struct net *net, const struct in6_addr *addr,
|
int (*chk_addr)(struct net *net, const struct in6_addr *addr,
|
||||||
const struct net_device *dev, int strict);
|
const struct net_device *dev, int strict);
|
||||||
|
void (*route_input)(struct sk_buff *skb);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct nf_ipv6_ops __rcu *nf_ipv6_ops;
|
extern const struct nf_ipv6_ops __rcu *nf_ipv6_ops;
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <net/flow_dissector.h>
|
#include <net/flow_dissector.h>
|
||||||
#include <linux/splice.h>
|
#include <linux/splice.h>
|
||||||
|
#include <linux/in6.h>
|
||||||
|
|
||||||
/* A. Checksumming of received packets by device.
|
/* A. Checksumming of received packets by device.
|
||||||
*
|
*
|
||||||
@ -179,7 +180,10 @@ struct nf_bridge_info {
|
|||||||
struct net_device *physoutdev;
|
struct net_device *physoutdev;
|
||||||
char neigh_header[8];
|
char neigh_header[8];
|
||||||
};
|
};
|
||||||
__be32 ipv4_daddr;
|
union {
|
||||||
|
__be32 ipv4_daddr;
|
||||||
|
struct in6_addr ipv6_daddr;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -326,30 +326,63 @@ free_skb:
|
|||||||
static bool daddr_was_changed(const struct sk_buff *skb,
|
static bool daddr_was_changed(const struct sk_buff *skb,
|
||||||
const struct nf_bridge_info *nf_bridge)
|
const struct nf_bridge_info *nf_bridge)
|
||||||
{
|
{
|
||||||
return ip_hdr(skb)->daddr != nf_bridge->ipv4_daddr;
|
switch (skb->protocol) {
|
||||||
|
case htons(ETH_P_IP):
|
||||||
|
return ip_hdr(skb)->daddr != nf_bridge->ipv4_daddr;
|
||||||
|
case htons(ETH_P_IPV6):
|
||||||
|
return memcmp(&nf_bridge->ipv6_daddr, &ipv6_hdr(skb)->daddr,
|
||||||
|
sizeof(ipv6_hdr(skb)->daddr)) != 0;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PF_BRIDGE/PRE_ROUTING *********************************************/
|
/* PF_BRIDGE/PRE_ROUTING: Undo the changes made for ip6tables
|
||||||
/* Undo the changes made for ip6tables PREROUTING and continue the
|
* PREROUTING and continue the bridge PRE_ROUTING hook. See comment
|
||||||
* bridge PRE_ROUTING hook.
|
* for br_nf_pre_routing_finish(), same logic is used here but
|
||||||
|
* equivalent IPv6 function ip6_route_input() called indirectly.
|
||||||
*/
|
*/
|
||||||
static int br_nf_pre_routing_finish_ipv6(struct sock *sk, struct sk_buff *skb)
|
static int br_nf_pre_routing_finish_ipv6(struct sock *sk, struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
|
struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
|
||||||
struct rtable *rt;
|
struct rtable *rt;
|
||||||
|
struct net_device *dev = skb->dev;
|
||||||
|
const struct nf_ipv6_ops *v6ops = nf_get_ipv6_ops();
|
||||||
|
|
||||||
if (nf_bridge->pkt_otherhost) {
|
if (nf_bridge->pkt_otherhost) {
|
||||||
skb->pkt_type = PACKET_OTHERHOST;
|
skb->pkt_type = PACKET_OTHERHOST;
|
||||||
nf_bridge->pkt_otherhost = false;
|
nf_bridge->pkt_otherhost = false;
|
||||||
}
|
}
|
||||||
nf_bridge->mask &= ~BRNF_NF_BRIDGE_PREROUTING;
|
nf_bridge->mask &= ~BRNF_NF_BRIDGE_PREROUTING;
|
||||||
|
if (daddr_was_changed(skb, nf_bridge)) {
|
||||||
|
skb_dst_drop(skb);
|
||||||
|
v6ops->route_input(skb);
|
||||||
|
|
||||||
rt = bridge_parent_rtable(nf_bridge->physindev);
|
if (skb_dst(skb)->error) {
|
||||||
if (!rt) {
|
kfree_skb(skb);
|
||||||
kfree_skb(skb);
|
return 0;
|
||||||
return 0;
|
}
|
||||||
|
|
||||||
|
if (skb_dst(skb)->dev == dev) {
|
||||||
|
skb->dev = nf_bridge->physindev;
|
||||||
|
nf_bridge_update_protocol(skb);
|
||||||
|
nf_bridge_push_encap_header(skb);
|
||||||
|
NF_HOOK_THRESH(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,
|
||||||
|
sk, skb, skb->dev, NULL,
|
||||||
|
br_nf_pre_routing_finish_bridge,
|
||||||
|
1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ether_addr_copy(eth_hdr(skb)->h_dest, dev->dev_addr);
|
||||||
|
skb->pkt_type = PACKET_HOST;
|
||||||
|
} else {
|
||||||
|
rt = bridge_parent_rtable(nf_bridge->physindev);
|
||||||
|
if (!rt) {
|
||||||
|
kfree_skb(skb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
skb_dst_set_noref(skb, &rt->dst);
|
||||||
}
|
}
|
||||||
skb_dst_set_noref(skb, &rt->dst);
|
|
||||||
|
|
||||||
skb->dev = nf_bridge->physindev;
|
skb->dev = nf_bridge->physindev;
|
||||||
nf_bridge_update_protocol(skb);
|
nf_bridge_update_protocol(skb);
|
||||||
@ -579,6 +612,7 @@ static unsigned int br_nf_pre_routing_ipv6(const struct nf_hook_ops *ops,
|
|||||||
struct sk_buff *skb,
|
struct sk_buff *skb,
|
||||||
const struct nf_hook_state *state)
|
const struct nf_hook_state *state)
|
||||||
{
|
{
|
||||||
|
struct nf_bridge_info *nf_bridge;
|
||||||
const struct ipv6hdr *hdr;
|
const struct ipv6hdr *hdr;
|
||||||
u32 pkt_len;
|
u32 pkt_len;
|
||||||
|
|
||||||
@ -610,6 +644,9 @@ static unsigned int br_nf_pre_routing_ipv6(const struct nf_hook_ops *ops,
|
|||||||
if (!setup_pre_routing(skb))
|
if (!setup_pre_routing(skb))
|
||||||
return NF_DROP;
|
return NF_DROP;
|
||||||
|
|
||||||
|
nf_bridge = nf_bridge_info_get(skb);
|
||||||
|
nf_bridge->ipv6_daddr = ipv6_hdr(skb)->daddr;
|
||||||
|
|
||||||
skb->protocol = htons(ETH_P_IPV6);
|
skb->protocol = htons(ETH_P_IPV6);
|
||||||
NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING, state->sk, skb,
|
NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING, state->sk, skb,
|
||||||
skb->dev, NULL,
|
skb->dev, NULL,
|
||||||
|
@ -191,6 +191,7 @@ static __sum16 nf_ip6_checksum_partial(struct sk_buff *skb, unsigned int hook,
|
|||||||
|
|
||||||
static const struct nf_ipv6_ops ipv6ops = {
|
static const struct nf_ipv6_ops ipv6ops = {
|
||||||
.chk_addr = ipv6_chk_addr,
|
.chk_addr = ipv6_chk_addr,
|
||||||
|
.route_input = ip6_route_input
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct nf_afinfo nf_ip6_afinfo = {
|
static const struct nf_afinfo nf_ip6_afinfo = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user