2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2019-02-08 13:55:31 +01:00
/* Copyright (c) 2014 Mahesh Bandewar <maheshb@google.com>
*/
# include "ipvlan.h"
static unsigned int ipvlan_netid __read_mostly ;
struct ipvlan_netns {
unsigned int ipvl_nf_hook_refcnt ;
} ;
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 ;
int addr_type ;
void * lyr3h ;
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 ( port , skb , & addr_type ) ;
if ( ! lyr3h )
goto out ;
addr = ipvlan_addr_lookup ( port , lyr3h , addr_type , true ) ;
out :
return addr ;
}
static 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 :
{
struct iphdr * ip4h = ip_hdr ( skb ) ;
int err ;
err = ip_route_input_noref ( skb , ip4h - > daddr , ip4h - > saddr ,
ip4h - > tos , sdev ) ;
if ( unlikely ( err ) )
goto out ;
break ;
}
# if IS_ENABLED(CONFIG_IPV6)
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 ,
skb , flags ) ;
skb_dst_set ( skb , dst ) ;
break ;
}
# endif
default :
break ;
}
out :
return skb ;
}
static const struct l3mdev_ops ipvl_l3mdev_ops = {
. l3mdev_l3_rcv = ipvlan_l3_rcv ,
} ;
static 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 ;
}
static const struct nf_hook_ops ipvl_nfops [ ] = {
{
. hook = ipvlan_nf_input ,
. pf = NFPROTO_IPV4 ,
. hooknum = NF_INET_LOCAL_IN ,
. priority = INT_MAX ,
} ,
# if IS_ENABLED(CONFIG_IPV6)
{
. hook = ipvlan_nf_input ,
. pf = NFPROTO_IPV6 ,
. hooknum = NF_INET_LOCAL_IN ,
. priority = INT_MAX ,
} ,
# endif
} ;
static int ipvlan_register_nf_hook ( struct net * net )
{
struct ipvlan_netns * vnet = net_generic ( net , ipvlan_netid ) ;
int err = 0 ;
if ( ! vnet - > ipvl_nf_hook_refcnt ) {
err = nf_register_net_hooks ( net , ipvl_nfops ,
ARRAY_SIZE ( ipvl_nfops ) ) ;
if ( ! err )
vnet - > ipvl_nf_hook_refcnt = 1 ;
} else {
vnet - > ipvl_nf_hook_refcnt + + ;
}
return err ;
}
static void ipvlan_unregister_nf_hook ( struct net * net )
{
struct ipvlan_netns * vnet = net_generic ( net , ipvlan_netid ) ;
if ( WARN_ON ( ! vnet - > ipvl_nf_hook_refcnt ) )
return ;
vnet - > ipvl_nf_hook_refcnt - - ;
if ( ! vnet - > ipvl_nf_hook_refcnt )
nf_unregister_net_hooks ( net , ipvl_nfops ,
ARRAY_SIZE ( ipvl_nfops ) ) ;
}
void ipvlan_migrate_l3s_hook ( struct net * oldnet , struct net * newnet )
{
struct ipvlan_netns * old_vnet ;
ASSERT_RTNL ( ) ;
old_vnet = net_generic ( oldnet , ipvlan_netid ) ;
if ( ! old_vnet - > ipvl_nf_hook_refcnt )
return ;
ipvlan_register_nf_hook ( newnet ) ;
ipvlan_unregister_nf_hook ( oldnet ) ;
}
static void ipvlan_ns_exit ( struct net * net )
{
struct ipvlan_netns * vnet = net_generic ( net , ipvlan_netid ) ;
if ( WARN_ON_ONCE ( vnet - > ipvl_nf_hook_refcnt ) ) {
vnet - > ipvl_nf_hook_refcnt = 0 ;
nf_unregister_net_hooks ( net , ipvl_nfops ,
ARRAY_SIZE ( ipvl_nfops ) ) ;
}
}
static struct pernet_operations ipvlan_net_ops = {
. id = & ipvlan_netid ,
. size = sizeof ( struct ipvlan_netns ) ,
. exit = ipvlan_ns_exit ,
} ;
int ipvlan_l3s_init ( void )
{
return register_pernet_subsys ( & ipvlan_net_ops ) ;
}
void ipvlan_l3s_cleanup ( void )
{
unregister_pernet_subsys ( & ipvlan_net_ops ) ;
}
int ipvlan_l3s_register ( struct ipvl_port * port )
{
struct net_device * dev = port - > dev ;
int ret ;
ASSERT_RTNL ( ) ;
ret = ipvlan_register_nf_hook ( read_pnet ( & port - > pnet ) ) ;
if ( ! ret ) {
dev - > l3mdev_ops = & ipvl_l3mdev_ops ;
2019-02-08 15:00:17 -08:00
dev - > priv_flags | = IFF_L3MDEV_RX_HANDLER ;
2019-02-08 13:55:31 +01:00
}
return ret ;
}
void ipvlan_l3s_unregister ( struct ipvl_port * port )
{
struct net_device * dev = port - > dev ;
ASSERT_RTNL ( ) ;
2019-02-08 15:00:17 -08:00
dev - > priv_flags & = ~ IFF_L3MDEV_RX_HANDLER ;
2019-02-08 13:55:31 +01:00
ipvlan_unregister_nf_hook ( read_pnet ( & port - > pnet ) ) ;
dev - > l3mdev_ops = NULL ;
}