memcg: accounting for objects allocated for new netdevice
authorVasily Averin <vvs@openvz.org>
Mon, 2 May 2022 12:15:51 +0000 (15:15 +0300)
committerJakub Kicinski <kuba@kernel.org>
Thu, 5 May 2022 02:16:46 +0000 (19:16 -0700)
Creating a new netdevice allocates at least ~50Kb of memory for various
kernel objects, but only ~5Kb of them are accounted to memcg. As a result,
creating an unlimited number of netdevice inside a memcg-limited container
does not fall within memcg restrictions, consumes a significant part
of the host's memory, can cause global OOM and lead to random kills of
host processes.

The main consumers of non-accounted memory are:
 ~10Kb   80+ kernfs nodes
 ~6Kb    ipv6_add_dev() allocations
  6Kb    __register_sysctl_table() allocations
  4Kb    neigh_sysctl_register() allocations
  4Kb    __devinet_sysctl_register() allocations
  4Kb    __addrconf_sysctl_register() allocations

Accounting of these objects allows to increase the share of memcg-related
memory up to 60-70% (~38Kb accounted vs ~54Kb total for dummy netdevice
on typical VM with default Fedora 35 kernel) and this should be enough
to somehow protect the host from misuse inside container.

Other related objects are quite small and may not be taken into account
to minimize the expected performance degradation.

It should be separately mentonied ~300 bytes of percpu allocation
of struct ipstats_mib in snmp6_alloc_dev(), on huge multi-cpu nodes
it can become the main consumer of memory.

This patch does not enables kernfs accounting as it affects
other parts of the kernel and should be discussed separately.
However, even without kernfs, this patch significantly improves the
current situation and allows to take into account more than half
of all netdevice allocations.

Signed-off-by: Vasily Averin <vvs@openvz.org>
Acked-by: Luis Chamberlain <mcgrof@kernel.org>
Link: https://lore.kernel.org/r/354a0a5f-9ec3-a25c-3215-304eab2157bc@openvz.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
fs/proc/proc_sysctl.c
net/core/neighbour.c
net/ipv4/devinet.c
net/ipv6/addrconf.c

index 5851c2a92c0d1c88cd50a01e8169efebbc181fc7..c29f39c01a9af1a41d7384344869fec25fb3e1aa 100644 (file)
@@ -1333,7 +1333,7 @@ struct ctl_table_header *__register_sysctl_table(
                nr_entries++;
 
        header = kzalloc(sizeof(struct ctl_table_header) +
-                        sizeof(struct ctl_node)*nr_entries, GFP_KERNEL);
+                        sizeof(struct ctl_node)*nr_entries, GFP_KERNEL_ACCOUNT);
        if (!header)
                return NULL;
 
index f64ebd050f6c4bd18ad6b977d27cfaa5090b31f6..47b6c1f0fdbb075d0413ebcfdb6ddf031ff91628 100644 (file)
@@ -3728,7 +3728,7 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
        char neigh_path[ sizeof("net//neigh/") + IFNAMSIZ + IFNAMSIZ ];
        char *p_name;
 
-       t = kmemdup(&neigh_sysctl_template, sizeof(*t), GFP_KERNEL);
+       t = kmemdup(&neigh_sysctl_template, sizeof(*t), GFP_KERNEL_ACCOUNT);
        if (!t)
                goto err;
 
index 53a6b14dc50a5bd6a2d940b4ed20943826c71b2b..89141ba5e9ee5c88238f53b3baeae3645888b593 100644 (file)
@@ -2573,7 +2573,7 @@ static int __devinet_sysctl_register(struct net *net, char *dev_name,
        struct devinet_sysctl_table *t;
        char path[sizeof("net/ipv4/conf/") + IFNAMSIZ];
 
-       t = kmemdup(&devinet_sysctl, sizeof(*t), GFP_KERNEL);
+       t = kmemdup(&devinet_sysctl, sizeof(*t), GFP_KERNEL_ACCOUNT);
        if (!t)
                goto out;
 
index 7dee662d70190345105f42cc84d042e51f1c1bff..cde242dca5302c2e2ac2da30f3a22e153b7d2689 100644 (file)
@@ -335,7 +335,7 @@ static int snmp6_alloc_dev(struct inet6_dev *idev)
 {
        int i;
 
-       idev->stats.ipv6 = alloc_percpu(struct ipstats_mib);
+       idev->stats.ipv6 = alloc_percpu_gfp(struct ipstats_mib, GFP_KERNEL_ACCOUNT);
        if (!idev->stats.ipv6)
                goto err_ip;
 
@@ -351,7 +351,7 @@ static int snmp6_alloc_dev(struct inet6_dev *idev)
        if (!idev->stats.icmpv6dev)
                goto err_icmp;
        idev->stats.icmpv6msgdev = kzalloc(sizeof(struct icmpv6msg_mib_device),
-                                          GFP_KERNEL);
+                                          GFP_KERNEL_ACCOUNT);
        if (!idev->stats.icmpv6msgdev)
                goto err_icmpmsg;
 
@@ -375,7 +375,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
        if (dev->mtu < IPV6_MIN_MTU && dev != blackhole_netdev)
                return ERR_PTR(-EINVAL);
 
-       ndev = kzalloc(sizeof(struct inet6_dev), GFP_KERNEL);
+       ndev = kzalloc(sizeof(*ndev), GFP_KERNEL_ACCOUNT);
        if (!ndev)
                return ERR_PTR(err);
 
@@ -7060,7 +7060,7 @@ static int __addrconf_sysctl_register(struct net *net, char *dev_name,
        struct ctl_table *table;
        char path[sizeof("net/ipv6/conf/") + IFNAMSIZ];
 
-       table = kmemdup(addrconf_sysctl, sizeof(addrconf_sysctl), GFP_KERNEL);
+       table = kmemdup(addrconf_sysctl, sizeof(addrconf_sysctl), GFP_KERNEL_ACCOUNT);
        if (!table)
                goto out;