diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index adb826f55e604c2d766c44bc260d1ac9aa96bc07..e66c3bed6f18c9c7f08a7b60b04f9e965f5ef8f6 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -44,6 +44,9 @@ #define IPVLAN_QBACKLOG_LIMIT 1000 +extern int sysctl_ipvlan_loop_qlen; +extern int sysctl_ipvlan_loop_delay; + typedef enum { IPVL_IPV6 = 0, IPVL_ICMPV6, @@ -75,6 +78,10 @@ struct ipvl_dev { netdev_features_t sfeatures; u32 msg_enable; spinlock_t addrs_lock; + int local_packets_cached; + unsigned long local_timeout; + struct timer_list local_free_timer; + struct sk_buff_head local_xmit_queue; }; struct ipvl_addr { diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index cfdcfd67d02a9a894bb5b151d69ed273569c557b..39cefb987c09bbbcda597887784fc2b32b092e7a 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -748,9 +748,37 @@ static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev) return dev_queue_xmit(skb); } +static int ipvlan_l2e_local_xmit_event(struct ipvl_dev *ipvlan, + struct sk_buff **pskb) +{ + struct sk_buff *nskb, *tskb; + + while ((ipvlan->local_packets_cached >= sysctl_ipvlan_loop_qlen) && + (tskb = skb_dequeue(&ipvlan->local_xmit_queue))) { + ipvlan->local_packets_cached -= tskb->truesize; + if (ipvlan->local_packets_cached < 0 || + skb_queue_empty(&ipvlan->local_xmit_queue)) + ipvlan->local_packets_cached = 0; + kfree_skb(tskb); + } + + nskb = skb_clone(*pskb, GFP_ATOMIC); + if (!nskb) + return NET_XMIT_DROP; + + ipvlan->local_timeout = jiffies + + (sysctl_ipvlan_loop_delay * HZ) / 1000; + mod_timer(&ipvlan->local_free_timer, ipvlan->local_timeout); + skb_queue_tail(&ipvlan->local_xmit_queue, *pskb); + ipvlan->local_packets_cached += (*pskb)->truesize; + *pskb = nskb; + + return 0; +} + static int ipvlan_xmit_mode_l2e(struct sk_buff *skb, struct net_device *dev) { - const struct ipvl_dev *ipvlan = netdev_priv(dev); + struct ipvl_dev *ipvlan = netdev_priv(dev); struct ethhdr *eth = eth_hdr(skb); struct ipvl_addr *addr; void *lyr3h; @@ -767,6 +795,10 @@ static int ipvlan_xmit_mode_l2e(struct sk_buff *skb, struct net_device *dev) consume_skb(skb); return NET_XMIT_DROP; } + + if (unlikely(ipvlan_l2e_local_xmit_event(ipvlan, + &skb))) + return NET_XMIT_DROP; return ipvlan_rcv_frame(addr, &skb, true); } } diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 290a6a404b1b2e543bb7c5b5db164e1d7ada6285..e9c9948b5122f0c6353f96674d8ca89e0c40312a 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -9,10 +9,43 @@ #include "ipvlan.h" +int sysctl_ipvlan_loop_qlen = 131072; +int sysctl_ipvlan_loop_delay = 10; static int ipvlan_default_mode = IPVLAN_MODE_L3; module_param(ipvlan_default_mode, int, 0400); MODULE_PARM_DESC(ipvlan_default_mode, "set ipvlan default mode: 0 for l2, 1 for l3, 2 for l2e, 3 for l3s, others invalid now"); +static struct ctl_table_header *ipvlan_table_hrd; +static struct ctl_table ipvlan_table[] = { + { + .procname = "loop_delay", + .data = &sysctl_ipvlan_loop_delay, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "loop_qlen", + .data = &sysctl_ipvlan_loop_qlen, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { } +}; + +static int ipvlan_sysctl_init(void) +{ + ipvlan_table_hrd = register_net_sysctl(&init_net, + "net/ipvlan", ipvlan_table); + return !ipvlan_table_hrd ? -ENOMEM : 0; +} + +static void ipvlan_sysctl_exit(void) +{ + unregister_net_sysctl_table(ipvlan_table_hrd); +} + static unsigned int ipvlan_netid __read_mostly; struct ipvlan_netns { @@ -223,6 +256,32 @@ static int ipvlan_init(struct net_device *dev) return 0; } +static void ipvlan_local_free_handler(struct timer_list *t) +{ + struct ipvl_dev *ipvlan = from_timer(ipvlan, t, local_free_timer); + + skb_queue_purge(&ipvlan->local_xmit_queue); + ipvlan->local_packets_cached = 0; +} + +static inline void ipvlan_local_init(struct net_device *dev) +{ + struct ipvl_dev *ipvlan = netdev_priv(dev); + + ipvlan->local_packets_cached = 0; + skb_queue_head_init(&ipvlan->local_xmit_queue); + timer_setup(&ipvlan->local_free_timer, + ipvlan_local_free_handler, 0); +} + +static inline void ipvlan_local_uninit(struct net_device *dev) +{ + struct ipvl_dev *ipvlan = netdev_priv(dev); + + del_timer(&ipvlan->local_free_timer); + skb_queue_purge(&ipvlan->local_xmit_queue); +} + static void ipvlan_uninit(struct net_device *dev) { struct ipvl_dev *ipvlan = netdev_priv(dev); @@ -249,6 +308,7 @@ static int ipvlan_open(struct net_device *dev) else dev->flags &= ~IFF_NOARP; + ipvlan_local_init(dev); rcu_read_lock(); list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) ipvlan_ht_addr_add(ipvlan, addr); @@ -268,6 +328,7 @@ static int ipvlan_stop(struct net_device *dev) dev_uc_del(phy_dev, phy_dev->dev_addr); + ipvlan_local_uninit(dev); rcu_read_lock(); list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) ipvlan_ht_addr_del(addr); @@ -1098,6 +1159,9 @@ static int __init ipvlan_init_module(void) goto error; } + err = ipvlan_sysctl_init(); + if (err < 0) + pr_err("ipvlan proc init failed, continue\n"); return 0; error: unregister_inetaddr_notifier(&ipvlan_addr4_notifier_block); @@ -1125,6 +1189,7 @@ static void __exit ipvlan_cleanup_module(void) unregister_inet6addr_validator_notifier( &ipvlan_addr6_vtor_notifier_block); #endif + ipvlan_sysctl_exit(); } module_init(ipvlan_init_module);