ipvs: register hooks only with services
authorJulian Anastasov <ja@ssi.bg>
Sun, 21 Jun 2020 15:40:30 +0000 (18:40 +0300)
committerPablo Neira Ayuso <pablo@netfilter.org>
Tue, 30 Jun 2020 16:37:39 +0000 (18:37 +0200)
Keep the IPVS hooks registered in Netfilter only
while there are configured virtual services. This
saves CPU cycles while IPVS is loaded but not used.

Signed-off-by: Julian Anastasov <ja@ssi.bg>
Reviewed-by: Simon Horman <horms@verge.net.au>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/ip_vs.h
net/netfilter/ipvs/ip_vs_core.c
net/netfilter/ipvs/ip_vs_ctl.c

index 83be2d9..0c98812 100644 (file)
@@ -874,6 +874,7 @@ struct netns_ipvs {
        struct ip_vs_stats              tot_stats;  /* Statistics & est. */
 
        int                     num_services;    /* no of virtual services */
+       int                     num_services6;   /* IPv6 virtual services */
 
        /* Trash for destinations */
        struct list_head        dest_trash;
@@ -960,6 +961,7 @@ struct netns_ipvs {
         * are not supported when synchronization is enabled.
         */
        unsigned int            mixed_address_family_dests;
+       unsigned int            hooks_afmask;   /* &1=AF_INET, &2=AF_INET6 */
 };
 
 #define DEFAULT_SYNC_THRESHOLD 3
@@ -1670,6 +1672,9 @@ static inline void ip_vs_unregister_conntrack(struct ip_vs_service *svc)
 #endif
 }
 
+int ip_vs_register_hooks(struct netns_ipvs *ipvs, unsigned int af);
+void ip_vs_unregister_hooks(struct netns_ipvs *ipvs, unsigned int af);
+
 static inline int
 ip_vs_dest_conn_overhead(struct ip_vs_dest *dest)
 {
index aa6a603..ca36701 100644 (file)
@@ -2256,7 +2256,7 @@ ip_vs_forward_icmp_v6(void *priv, struct sk_buff *skb,
 #endif
 
 
-static const struct nf_hook_ops ip_vs_ops[] = {
+static const struct nf_hook_ops ip_vs_ops4[] = {
        /* After packet filtering, change source only for VS/NAT */
        {
                .hook           = ip_vs_reply4,
@@ -2302,7 +2302,10 @@ static const struct nf_hook_ops ip_vs_ops[] = {
                .hooknum        = NF_INET_FORWARD,
                .priority       = 100,
        },
+};
+
 #ifdef CONFIG_IP_VS_IPV6
+static const struct nf_hook_ops ip_vs_ops6[] = {
        /* After packet filtering, change source only for VS/NAT */
        {
                .hook           = ip_vs_reply6,
@@ -2348,8 +2351,64 @@ static const struct nf_hook_ops ip_vs_ops[] = {
                .hooknum        = NF_INET_FORWARD,
                .priority       = 100,
        },
-#endif
 };
+#endif
+
+int ip_vs_register_hooks(struct netns_ipvs *ipvs, unsigned int af)
+{
+       const struct nf_hook_ops *ops;
+       unsigned int count;
+       unsigned int afmask;
+       int ret = 0;
+
+       if (af == AF_INET6) {
+#ifdef CONFIG_IP_VS_IPV6
+               ops = ip_vs_ops6;
+               count = ARRAY_SIZE(ip_vs_ops6);
+               afmask = 2;
+#else
+               return -EINVAL;
+#endif
+       } else {
+               ops = ip_vs_ops4;
+               count = ARRAY_SIZE(ip_vs_ops4);
+               afmask = 1;
+       }
+
+       if (!(ipvs->hooks_afmask & afmask)) {
+               ret = nf_register_net_hooks(ipvs->net, ops, count);
+               if (ret >= 0)
+                       ipvs->hooks_afmask |= afmask;
+       }
+       return ret;
+}
+
+void ip_vs_unregister_hooks(struct netns_ipvs *ipvs, unsigned int af)
+{
+       const struct nf_hook_ops *ops;
+       unsigned int count;
+       unsigned int afmask;
+
+       if (af == AF_INET6) {
+#ifdef CONFIG_IP_VS_IPV6
+               ops = ip_vs_ops6;
+               count = ARRAY_SIZE(ip_vs_ops6);
+               afmask = 2;
+#else
+               return;
+#endif
+       } else {
+               ops = ip_vs_ops4;
+               count = ARRAY_SIZE(ip_vs_ops4);
+               afmask = 1;
+       }
+
+       if (ipvs->hooks_afmask & afmask) {
+               nf_unregister_net_hooks(ipvs->net, ops, count);
+               ipvs->hooks_afmask &= ~afmask;
+       }
+}
+
 /*
  *     Initialize IP Virtual Server netns mem.
  */
@@ -2425,19 +2484,6 @@ static void __net_exit __ip_vs_cleanup_batch(struct list_head *net_list)
        }
 }
 
-static int __net_init __ip_vs_dev_init(struct net *net)
-{
-       int ret;
-
-       ret = nf_register_net_hooks(net, ip_vs_ops, ARRAY_SIZE(ip_vs_ops));
-       if (ret < 0)
-               goto hook_fail;
-       return 0;
-
-hook_fail:
-       return ret;
-}
-
 static void __net_exit __ip_vs_dev_cleanup_batch(struct list_head *net_list)
 {
        struct netns_ipvs *ipvs;
@@ -2446,7 +2492,8 @@ static void __net_exit __ip_vs_dev_cleanup_batch(struct list_head *net_list)
        EnterFunction(2);
        list_for_each_entry(net, net_list, exit_list) {
                ipvs = net_ipvs(net);
-               nf_unregister_net_hooks(net, ip_vs_ops, ARRAY_SIZE(ip_vs_ops));
+               ip_vs_unregister_hooks(ipvs, AF_INET);
+               ip_vs_unregister_hooks(ipvs, AF_INET6);
                ipvs->enable = 0;       /* Disable packet reception */
                smp_wmb();
                ip_vs_sync_net_cleanup(ipvs);
@@ -2462,7 +2509,6 @@ static struct pernet_operations ipvs_core_ops = {
 };
 
 static struct pernet_operations ipvs_core_dev_ops = {
-       .init = __ip_vs_dev_init,
        .exit_batch = __ip_vs_dev_cleanup_batch,
 };
 
index 412656c..0eed388 100644 (file)
@@ -1272,6 +1272,7 @@ ip_vs_add_service(struct netns_ipvs *ipvs, struct ip_vs_service_user_kern *u,
        struct ip_vs_scheduler *sched = NULL;
        struct ip_vs_pe *pe = NULL;
        struct ip_vs_service *svc = NULL;
+       int ret_hooks = -1;
 
        /* increase the module use count */
        if (!ip_vs_use_count_inc())
@@ -1313,6 +1314,14 @@ ip_vs_add_service(struct netns_ipvs *ipvs, struct ip_vs_service_user_kern *u,
        }
 #endif
 
+       if ((u->af == AF_INET && !ipvs->num_services) ||
+           (u->af == AF_INET6 && !ipvs->num_services6)) {
+               ret = ip_vs_register_hooks(ipvs, u->af);
+               if (ret < 0)
+                       goto out_err;
+               ret_hooks = ret;
+       }
+
        svc = kzalloc(sizeof(struct ip_vs_service), GFP_KERNEL);
        if (svc == NULL) {
                IP_VS_DBG(1, "%s(): no memory\n", __func__);
@@ -1374,6 +1383,8 @@ ip_vs_add_service(struct netns_ipvs *ipvs, struct ip_vs_service_user_kern *u,
        /* Count only IPv4 services for old get/setsockopt interface */
        if (svc->af == AF_INET)
                ipvs->num_services++;
+       else if (svc->af == AF_INET6)
+               ipvs->num_services6++;
 
        /* Hash the service into the service table */
        ip_vs_svc_hash(svc);
@@ -1385,6 +1396,8 @@ ip_vs_add_service(struct netns_ipvs *ipvs, struct ip_vs_service_user_kern *u,
 
 
  out_err:
+       if (ret_hooks >= 0)
+               ip_vs_unregister_hooks(ipvs, u->af);
        if (svc != NULL) {
                ip_vs_unbind_scheduler(svc, sched);
                ip_vs_service_free(svc);
@@ -1500,9 +1513,15 @@ static void __ip_vs_del_service(struct ip_vs_service *svc, bool cleanup)
        struct ip_vs_pe *old_pe;
        struct netns_ipvs *ipvs = svc->ipvs;
 
-       /* Count only IPv4 services for old get/setsockopt interface */
-       if (svc->af == AF_INET)
+       if (svc->af == AF_INET) {
                ipvs->num_services--;
+               if (!ipvs->num_services)
+                       ip_vs_unregister_hooks(ipvs, svc->af);
+       } else if (svc->af == AF_INET6) {
+               ipvs->num_services6--;
+               if (!ipvs->num_services6)
+                       ip_vs_unregister_hooks(ipvs, svc->af);
+       }
 
        ip_vs_stop_estimator(svc->ipvs, &svc->stats);