lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1470876798-4024-6-git-send-email-anaravaram@google.com>
Date:	Wed, 10 Aug 2016 17:53:18 -0700
From:	Anoop Naravaram <anaravaram@...gle.com>
To:	corbet@....net, tj@...nel.org, lizefan@...wei.com,
	hannes@...xchg.org, davem@...emloft.net, kuznet@....inr.ac.ru,
	jmorris@...ei.org, yoshfuji@...ux-ipv6.org, kaber@...sh.net,
	linux-doc@...r.kernel.org, cgroups@...r.kernel.org,
	netdev@...r.kernel.org
Cc:	edumazet@...gle.com, maheshb@...gle.com, weiwan@...gle.com,
	tom@...bertland.com, Anoop Naravaram <anaravaram@...gle.com>
Subject: [PATCH 5/5] net: add test for net cgroup

Created a file scripts/cgroup/net_cgroup_test.py that tests the
functionality of the net cgroup as described in previous commit logs.

Signed-off-by: Anoop Naravaram <anaravaram@...gle.com>
---
 scripts/cgroup/net_cgroup_test.py | 359 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 359 insertions(+)
 create mode 100755 scripts/cgroup/net_cgroup_test.py

diff --git a/scripts/cgroup/net_cgroup_test.py b/scripts/cgroup/net_cgroup_test.py
new file mode 100755
index 0000000..604f662
--- /dev/null
+++ b/scripts/cgroup/net_cgroup_test.py
@@ -0,0 +1,359 @@
+#!/usr/grte/v4/bin/python2.7
+import unittest
+import os
+import socket
+import shutil
+import multiprocessing
+
+cgroup_net_root = '/dev/cgroup/net'
+
+def create_cgroup(name):
+    '''
+    Creates a cgroup with the given name. The name should also include the names
+    of all ancestors separated by slashes, such as 'a/b/c'. Returns a path to
+    the directory of the newly created cgroup.
+    '''
+    cgroup_dir = os.path.join(cgroup_net_root, name)
+    while True:
+        try:
+            os.mkdir(cgroup_dir)
+            break
+        except OSError as e:
+            # remove it if it already exists, then try to create again
+            # there will be errors when rmtree tries to remove the cgroup files,
+            # but these errors should be ignored because we only care about
+            # rmdir'ing the directories, which will automatically get rid of the
+            # files inside them
+            shutil.rmtree(cgroup_dir, ignore_errors=True)
+
+    return cgroup_dir
+
+
+def parse_ranges(ranges_str):
+    '''
+    Converts a range string like "100-200,300-400" into a set of 2-tuples like
+    {(100,200),(300,400)}.
+    '''
+    return set(tuple(int(l) for l in r.strip().split('-'))
+               for r in ranges_str.split(','))
+
+def acquire_udp_ports(cgroup_dir, e2, n, addr, numfailq):
+    '''
+    Waits for the event e1, attempts to acquire n udp ports connected to addr,
+    and then puts the number of failures on the queue and waits for e2. Then,
+    all sockets are closed. (Intended to be called as a subprocess.)
+
+    While waiting for e1, the parent process can set this process's cgroup.
+    While waiting for e2, the parent process can read the udp statistics while
+    this process is still alive.
+    '''
+
+    with open(os.path.join(cgroup_dir, 'tasks'), 'w') as f:
+        # add proc1 to cgroup a/b
+        f.write(str(os.getpid()))
+
+    socketset = set()
+    numfail = 0
+    for _ in xrange(n):
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        try:
+            s.connect(addr)
+        except socket.error as e:
+            numfail += 1
+        socketset.add(s)
+
+    if numfailq is not None:
+        numfailq.put(numfail)
+
+    if e2 is not None:
+        e2.wait()
+
+    for s in socketset:
+        s.close()
+
+
+class NetCgroupTest(unittest.TestCase):
+
+    def test_port_range_check(self):
+        '''
+        Test that the kernel is correctly checking that a port is in the
+        relevant range when a process in a net cgroup is trying to bind a socket
+        to it, or trying to listen on a socket bound to it.
+        '''
+
+        # create a new cgroup a
+        cgroup_a_dir = create_cgroup('a')
+
+        # add current process to cgroup a
+        with open(os.path.join(cgroup_a_dir, 'tasks'), 'w') as f:
+            f.write(str(os.getpid()))
+
+        # set bind and listen range of cgroup a
+        with open(os.path.join(cgroup_a_dir, 'net.bind_port_ranges'), 'w') as f:
+            f.write('300-400,500')
+        with open(os.path.join(cgroup_a_dir, 'net.listen_port_ranges'), 'w') as f:
+            f.write('350-400')
+
+        # try binding and listening on various ports, and check if they succeed
+        # or fail appropriately
+        s = socket.socket()
+        try:
+            s.bind(('0.0.0.0', 350)) # should bind and listen successfully
+            try:
+                s.listen(5)
+            except socket.error:
+                self.fail('unexpectedly failed to listen')
+        except socket.error:
+            self.fail('unexpectedly failed to bind')
+        s.close()
+
+        s = socket.socket()
+        try:
+            s.bind(('0.0.0.0', 370)) # should bind and listen successfully
+            try:
+                s.listen(5)
+            except socket.error:
+                self.fail('unexpectedly failed to listen')
+        except socket.error:
+            self.fail('unexpectedly failed to bind')
+        s.close()
+
+        s = socket.socket()
+        try:
+            s.bind(('0.0.0.0', 320)) # should bind successfully but fail to listen
+            with self.assertRaises(socket.error):
+                s.listen(5)
+        except socket.error:
+            self.fail('unexpectedly failed to bind')
+        s.close()
+
+        s = socket.socket()
+        try:
+            s.bind(('0.0.0.0', 500)) # should bind successfully but fail to listen
+            with self.assertRaises(socket.error):
+                s.listen(5)
+        except socket.error:
+            self.fail('unexpectedly failed to bind')
+        s.close()
+
+        s = socket.socket()
+        with self.assertRaises(socket.error):
+            s.bind(('0.0.0.0', 200)) # should fail to bind
+        s.close()
+
+        s = socket.socket()
+        with self.assertRaises(socket.error):
+            s.bind(('0.0.0.0', 401)) # should fail to bind
+        s.close()
+
+        # remove current process from cgroup a (by adding it to root)
+        with open(os.path.join(cgroup_net_root, 'tasks'), 'w') as f:
+            f.write(str(os.getpid()))
+
+
+    def test_range_inheritance(self):
+        '''
+        Test that the kernel copies the ranges from parent when a net cgroup is
+        created.
+        '''
+        cgroup_a_dir = create_cgroup('a')
+
+        # set ranges of parent
+        with open(os.path.join(cgroup_a_dir, 'net.bind_port_ranges'), 'w') as f:
+            f.write('100-200,300-400,500')
+        with open(os.path.join(cgroup_a_dir, 'net.listen_port_ranges'), 'w') as f:
+            f.write('150,300')
+
+        cgroup_b_dir = create_cgroup('a/b')
+
+        # check that bind range is the same in both a and a/b
+        with open(os.path.join(cgroup_a_dir, 'net.bind_port_ranges')) as fa, \
+             open(os.path.join(cgroup_b_dir, 'net.bind_port_ranges')) as fb:
+            ranges_a_str = fa.read()
+            ranges_b_str = fb.read()
+            ranges_a = parse_ranges(ranges_a_str)
+            ranges_b = parse_ranges(ranges_b_str)
+            self.assertEqual(ranges_a, ranges_b)
+
+        # check that listen range is the same in both a and a/b
+        with open(os.path.join(cgroup_a_dir, 'net.listen_port_ranges')) as fa, \
+             open(os.path.join(cgroup_b_dir, 'net.listen_port_ranges')) as fb:
+            ranges_a_str = fa.read()
+            ranges_b_str = fb.read()
+            ranges_a = parse_ranges(ranges_a_str)
+            ranges_b = parse_ranges(ranges_b_str)
+            self.assertEqual(ranges_a, ranges_b)
+
+    def test_enforce_subset(self):
+        '''
+        Test that the kernel enforces the rule that a cgroup cannot have a port
+        within its range that isn't in its parent's range.
+        '''
+        cgroup_a_dir = create_cgroup('a')
+
+        # set ranges of parent
+        with open(os.path.join(cgroup_a_dir, 'net.bind_port_ranges'), 'w') as f:
+            f.write('100-200,300-400,500')
+        with open(os.path.join(cgroup_a_dir, 'net.listen_port_ranges'), 'w') as f:
+            f.write('150,300')
+
+        cgroup_b_dir = create_cgroup('a/b')
+
+        # try to set a/b ranges to various things
+
+        with open(os.path.join(cgroup_b_dir, 'net.bind_port_ranges'), 'w') as f:
+            try:
+                f.write('130-160,500') # should succeed
+            except IOError as e:
+                self.fail('unexpectedly failed to set ranges')
+        with open(os.path.join(cgroup_b_dir, 'net.listen_port_ranges'), 'w') as f:
+            try:
+                f.write('150') # should succeed
+            except IOError as e:
+                self.fail('unexpectedly failed to set ranges')
+
+        with open(os.path.join(cgroup_b_dir, 'net.bind_port_ranges'), 'w') as f:
+            with self.assertRaises(IOError):
+                f.write('200-300,350,360-370') # should fail
+        with open(os.path.join(cgroup_b_dir, 'net.listen_port_ranges'), 'w') as f:
+            with self.assertRaises(IOError):
+                f.write('200-300') # should fail
+
+        with open(os.path.join(cgroup_b_dir, 'net.bind_port_ranges'), 'w') as f:
+            with self.assertRaises(IOError):
+                f.write('210,220,230-240') # should fail
+        with open(os.path.join(cgroup_b_dir, 'net.listen_port_ranges'), 'w') as f:
+            with self.assertRaises(IOError):
+                f.write('210,220,230-240') # should fail
+
+    def test_udp_usage(self):
+        '''
+        Tests if the kernel counts udp usage in a hierarchical manner.
+        '''
+
+        # create a new cgroups a, a/b, a/c
+        cgroup_a_dir = create_cgroup('a')
+        cgroup_b_dir = create_cgroup('a/b')
+        cgroup_c_dir = create_cgroup('a/c')
+
+        # event for synchronizing subprocesses
+        e3 = multiprocessing.Event()
+
+        # create a server socket so that subprocesses can connect to it
+        addr = ('0.0.0.0', 3000)
+        serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        serversocket.bind(addr)
+
+        numfailq1 = multiprocessing.Queue()
+        numfailq2 = multiprocessing.Queue()
+
+        # create 2 subprocesses, one going into a/b, other going into a/c
+        proc1 = multiprocessing.Process(name='proc1', target=acquire_udp_ports,
+                                        args=(cgroup_b_dir, e3, 4, addr, numfailq1))
+        proc2 = multiprocessing.Process(name='proc2', target=acquire_udp_ports,
+                                        args=(cgroup_c_dir, e3, 3, addr, numfailq2))
+
+        # proc1 will acquire 4 ports in cgroup a/b
+        proc1.start()
+        # make sure none failed
+        self.assertEqual(numfailq1.get(), 0)
+
+        # proc2 will acquire 3 ports in cgroup a/c
+        proc2.start()
+        # make sure none failed
+        self.assertEqual(numfailq2.get(), 0)
+
+        # check if the usage count is correct
+        with open(os.path.join(cgroup_a_dir, 'net.udp_usage')) as f:
+            # a/net.udp_usage should be 7, because it counts its children's too
+            self.assertEqual(int(f.read()), 7)
+        with open(os.path.join(cgroup_b_dir, 'net.udp_usage')) as f:
+            self.assertEqual(int(f.read()), 4)
+        with open(os.path.join(cgroup_c_dir, 'net.udp_usage')) as f:
+            self.assertEqual(int(f.read()), 3)
+
+        # signal the subprocesses that they can close their sockets now
+        e3.set()
+        serversocket.close()
+
+    def test_udp_limit(self):
+        '''
+        Test if the kernel only allows processes to use a limited number of udp
+        ports.
+        '''
+        # create a new cgroup a
+        cgroup_a_dir = create_cgroup('a')
+
+        # event for synchronizing subprocess
+        e2 = multiprocessing.Event()
+
+        # create a server socket so that subprocesses can connect to it
+        addr = ('0.0.0.0', 3000)
+        serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        serversocket.bind(addr)
+
+        # queue for getting fail count
+        numfailq = multiprocessing.Queue()
+
+        # create and start a subprocess
+        proc = multiprocessing.Process(name='proc', target=acquire_udp_ports,
+                                       args=(cgroup_a_dir, e2, 8, addr, numfailq))
+
+        # set the udp limit to 5
+        with open(os.path.join(cgroup_a_dir, 'net.udp_limit'), 'w') as f:
+            f.write('5')
+
+        # proc will attempt to acquire 8 udp port (only 5 will be successful)
+        proc.start()
+        # make sure that there were indeed 3 failures
+        self.assertEqual(numfailq.get(), 3)
+
+        # check if the usage count and fail count in the files is correct
+        with open(os.path.join(cgroup_a_dir, 'net.udp_usage')) as f:
+            self.assertEqual(int(f.read()), 5)
+        with open(os.path.join(cgroup_a_dir, 'net.udp_failcnt')) as f:
+            self.assertEqual(int(f.read()), 3)
+
+        # signal the subprocess that it can close its sockets now
+        e2.set()
+        serversocket.close()
+
+    def test_dscp_range_check(self):
+        # create a new cgroup a
+        cgroup_a_dir = create_cgroup('a')
+
+        # add current process to cgroup a
+        with open(os.path.join(cgroup_a_dir, 'tasks'), 'w') as f:
+            f.write(str(os.getpid()))
+
+        # set dscp range of cgroup a
+        with open(os.path.join(cgroup_a_dir, 'net.dscp_ranges'), 'w') as f:
+            f.write('20-40')
+
+        # try setting the IP_TOS option to various things
+        s = socket.socket()
+        try:
+            # dscp = 30 (in range)
+            s.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 30 << 2 | 1)
+        except socket.error:
+            self.fail('unexpectedly failed to setsockopt')
+        s.close()
+
+        s = socket.socket()
+        with self.assertRaises(socket.error):
+            # dscp = 50 (out of range)
+            s.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 50 << 2 | 2)
+        s.close()
+
+        s = socket.socket()
+        with self.assertRaises(socket.error):
+            # dscp = 10 (out of range)
+            s.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 10 << 2 | 3)
+        s.close()
+
+        # remove current process from cgroup a (by adding it to root)
+        with open(os.path.join(cgroup_net_root, 'tasks'), 'w') as f:
+            f.write(str(os.getpid()))
+
+if __name__ == '__main__':
+    unittest.main()
-- 
2.8.0.rc3.226.g39d4020

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ