ipvlan: Introduce l3s mode
[cascardo/linux.git] / drivers / net / ipvlan / ipvlan_core.c
index b5f9511..b4e9907 100644 (file)
@@ -560,6 +560,7 @@ int ipvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
        case IPVLAN_MODE_L2:
                return ipvlan_xmit_mode_l2(skb, dev);
        case IPVLAN_MODE_L3:
+       case IPVLAN_MODE_L3S:
                return ipvlan_xmit_mode_l3(skb, dev);
        }
 
@@ -664,6 +665,8 @@ rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb)
                return ipvlan_handle_mode_l2(pskb, port);
        case IPVLAN_MODE_L3:
                return ipvlan_handle_mode_l3(pskb, port);
+       case IPVLAN_MODE_L3S:
+               return RX_HANDLER_PASS;
        }
 
        /* Should not reach here */
@@ -672,3 +675,94 @@ rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb)
        kfree_skb(skb);
        return RX_HANDLER_CONSUMED;
 }
+
+static struct ipvl_addr *ipvlan_skb_to_addr(struct sk_buff *skb,
+                                           struct net_device *dev)
+{
+       struct ipvl_addr *addr = NULL;
+       struct ipvl_port *port;
+       void *lyr3h;
+       int addr_type;
+
+       if (!dev || !netif_is_ipvlan_port(dev))
+               goto out;
+
+       port = ipvlan_port_get_rcu(dev);
+       if (!port || port->mode != IPVLAN_MODE_L3S)
+               goto out;
+
+       lyr3h = ipvlan_get_L3_hdr(skb, &addr_type);
+       if (!lyr3h)
+               goto out;
+
+       addr = ipvlan_addr_lookup(port, lyr3h, addr_type, true);
+out:
+       return addr;
+}
+
+struct sk_buff *ipvlan_l3_rcv(struct net_device *dev, struct sk_buff *skb,
+                             u16 proto)
+{
+       struct ipvl_addr *addr;
+       struct net_device *sdev;
+
+       addr = ipvlan_skb_to_addr(skb, dev);
+       if (!addr)
+               goto out;
+
+       sdev = addr->master->dev;
+       switch (proto) {
+       case AF_INET:
+       {
+               int err;
+               struct iphdr *ip4h = ip_hdr(skb);
+
+               err = ip_route_input_noref(skb, ip4h->daddr, ip4h->saddr,
+                                          ip4h->tos, sdev);
+               if (unlikely(err))
+                       goto out;
+               break;
+       }
+       case AF_INET6:
+       {
+               struct dst_entry *dst;
+               struct ipv6hdr *ip6h = ipv6_hdr(skb);
+               int flags = RT6_LOOKUP_F_HAS_SADDR;
+               struct flowi6 fl6 = {
+                       .flowi6_iif   = sdev->ifindex,
+                       .daddr        = ip6h->daddr,
+                       .saddr        = ip6h->saddr,
+                       .flowlabel    = ip6_flowinfo(ip6h),
+                       .flowi6_mark  = skb->mark,
+                       .flowi6_proto = ip6h->nexthdr,
+               };
+
+               skb_dst_drop(skb);
+               dst = ip6_route_input_lookup(dev_net(sdev), sdev, &fl6, flags);
+               skb_dst_set(skb, dst);
+               break;
+       }
+       default:
+               break;
+       }
+
+out:
+       return skb;
+}
+
+unsigned int ipvlan_nf_input(void *priv, struct sk_buff *skb,
+                            const struct nf_hook_state *state)
+{
+       struct ipvl_addr *addr;
+       unsigned int len;
+
+       addr = ipvlan_skb_to_addr(skb, skb->dev);
+       if (!addr)
+               goto out;
+
+       skb->dev = addr->master->dev;
+       len = skb->len + ETH_HLEN;
+       ipvlan_count_rx(addr->master, len, true, false);
+out:
+       return NF_ACCEPT;
+}