netfilter: bridge: detect NAT66 correctly and change MAC address
[sfrench/cifs-2.6.git] / net / bridge / br_netfilter.c
index 6cb642c43451d00d77e03184a14c5ac86eb1ee62..9ac0c6417c773623a63d09a1e771f39c8ec34615 100644 (file)
@@ -326,30 +326,63 @@ free_skb:
 static bool daddr_was_changed(const struct sk_buff *skb,
                              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 *********************************************/
-/* Undo the changes made for ip6tables PREROUTING and continue the
- * bridge PRE_ROUTING hook.
+/* PF_BRIDGE/PRE_ROUTING: Undo the changes made for ip6tables
+ * PREROUTING and continue the bridge PRE_ROUTING hook. See comment
+ * 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)
 {
        struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
        struct rtable *rt;
+       struct net_device *dev = skb->dev;
+       const struct nf_ipv6_ops *v6ops = nf_get_ipv6_ops();
 
        if (nf_bridge->pkt_otherhost) {
                skb->pkt_type = PACKET_OTHERHOST;
                nf_bridge->pkt_otherhost = false;
        }
        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 (!rt) {
-               kfree_skb(skb);
-               return 0;
+               if (skb_dst(skb)->error) {
+                       kfree_skb(skb);
+                       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;
        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,
                                           const struct nf_hook_state *state)
 {
+       struct nf_bridge_info *nf_bridge;
        const struct ipv6hdr *hdr;
        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))
                return NF_DROP;
 
+       nf_bridge = nf_bridge_info_get(skb);
+       nf_bridge->ipv6_daddr = ipv6_hdr(skb)->daddr;
+
        skb->protocol = htons(ETH_P_IPV6);
        NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING, state->sk, skb,
                skb->dev, NULL,