Re: [gentoo-portage-dev] [PATCH] Use RTNETLINK to configure the loopback interface
On Tue, Aug 27, 2019 at 7:11 PM Zac Medico wrote: > > On 8/27/19 9:24 AM, Alec Warner wrote: > > unit tests? > > > > -A > > Maybe something like this: > > diff --git a/lib/portage/tests/process/test_unshare_net.py > b/lib/portage/tests/process/test_unshare_net.py > new file mode 100644 > index 0..2d3eebad0 > --- /dev/null > +++ b/lib/portage/tests/process/test_unshare_net.py > @@ -0,0 +1,30 @@ > +# Copyright 2019 Gentoo Authors > +# Distributed under the terms of the GNU General Public License v2 > + > +import os > +import platform > + > +import portage.process > +from portage.const import BASH_BINARY > +from portage.tests import TestCase > + > +UNSHARE_NET_TEST_SCRIPT = """ > +ping -c 1 -W 1 127.0.0.1 || exit 1 > +[[ -n ${IPV6} ]] || exit 0 > +ping -c 1 -W 1 ::1 || exit 1 > +""" > + > +class UnshareNetTestCase(TestCase): > + > + def testUnshareNet(self): > + > + if os.geteuid() != 0: > + self.skipTest('not root') > + if platform.system() != 'Linux': > + self.skipTest('not Linux') > + if portage.process.find_binary('ping') is None: > + self.skipTest('ping not found') > + > + env = os.environ.copy() > + env['IPV6'] = '1' if portage.process._has_ipv6() else '' > + self.assertEqual(portage.process.spawn([BASH_BINARY, '-c', > UNSHARE_NET_TEST_SCRIPT], unshare_net=True, env=env), 0) Thanks, that's helpful. I have made a couple tweaks to the original patch that I will send along shortly.
Re: [gentoo-portage-dev] [PATCH] Use RTNETLINK to configure the loopback interface
On 8/27/19 9:24 AM, Alec Warner wrote: > unit tests? > > -A Maybe something like this: diff --git a/lib/portage/tests/process/test_unshare_net.py b/lib/portage/tests/process/test_unshare_net.py new file mode 100644 index 0..2d3eebad0 --- /dev/null +++ b/lib/portage/tests/process/test_unshare_net.py @@ -0,0 +1,30 @@ +# Copyright 2019 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +import os +import platform + +import portage.process +from portage.const import BASH_BINARY +from portage.tests import TestCase + +UNSHARE_NET_TEST_SCRIPT = """ +ping -c 1 -W 1 127.0.0.1 || exit 1 +[[ -n ${IPV6} ]] || exit 0 +ping -c 1 -W 1 ::1 || exit 1 +""" + +class UnshareNetTestCase(TestCase): + + def testUnshareNet(self): + + if os.geteuid() != 0: + self.skipTest('not root') + if platform.system() != 'Linux': + self.skipTest('not Linux') + if portage.process.find_binary('ping') is None: + self.skipTest('ping not found') + + env = os.environ.copy() + env['IPV6'] = '1' if portage.process._has_ipv6() else '' + self.assertEqual(portage.process.spawn([BASH_BINARY, '-c', UNSHARE_NET_TEST_SCRIPT], unshare_net=True, env=env), 0) -- Thanks, Zac signature.asc Description: OpenPGP digital signature
Re: [gentoo-portage-dev] [PATCH] Use RTNETLINK to configure the loopback interface
unit tests? -A On Mon, Aug 26, 2019 at 8:03 PM Mike Gilbert wrote: > This eliminates the dependency on iproute2 on Linux. > > Signed-off-by: Mike Gilbert > --- > lib/portage/process.py | 25 -- > lib/portage/util/netlink.py | 91 + > 2 files changed, 100 insertions(+), 16 deletions(-) > create mode 100644 lib/portage/util/netlink.py > > diff --git a/lib/portage/process.py b/lib/portage/process.py > index 2a2cbd972..bb4462c7f 100644 > --- a/lib/portage/process.py > +++ b/lib/portage/process.py > @@ -10,7 +10,6 @@ import multiprocessing > import platform > import signal > import socket > -import struct > import subprocess > import sys > import traceback > @@ -489,17 +488,6 @@ def _configure_loopback_interface(): > Configure the loopback interface. > """ > > - IFF_UP = 0x1 > - ifreq = struct.pack('16sh', b'lo', IFF_UP) > - SIOCSIFFLAGS = 0x8914 > - > - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) > - try: > - fcntl.ioctl(sock, SIOCSIFFLAGS, ifreq) > - except IOError as e: > - writemsg("Unable to enable loopback interface: %s\n" % > e.strerror, noiselevel=-1) > - sock.close() > - > # We add some additional addresses to work around odd behavior in > glibc's > # getaddrinfo() implementation when the AI_ADDRCONFIG flag is set. > # > @@ -514,12 +502,17 @@ def _configure_loopback_interface(): > # Bug: https://bugs.gentoo.org/690758 > # Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=12377#c13 > > + from portage.util.netlink import RtNetlink > + > try: > - subprocess.call(['ip', 'address', 'add', '10.0.0.1/8', > 'dev', 'lo']) > - if _has_ipv6(): > - subprocess.call(['ip', 'address', 'add', > 'fd00::1/8', 'dev', 'lo']) > + with RtNetlink() as rtnl: > + ifindex = rtnl.get_link_ifindex(b'lo') > + rtnl.set_link_up(ifindex) > + rtnl.add_address(ifindex, socket.AF_INET, > '10.0.0.1', 8) > + if _has_ipv6(): > + rtnl.add_address(ifindex, socket.AF_INET6, > 'fd::1', 8) > except EnvironmentError as e: > - writemsg("Error calling 'ip': %s\n" % e.strerror, > noiselevel=-1) > + writemsg("Unable to configure loopback interface: %s\n" % > e.strerror, noiselevel=-1) > > def _exec(binary, mycommand, opt_name, fd_pipes, > env, gid, groups, uid, umask, cwd, > diff --git a/lib/portage/util/netlink.py b/lib/portage/util/netlink.py > new file mode 100644 > index 0..5b18b8f95 > --- /dev/null > +++ b/lib/portage/util/netlink.py > @@ -0,0 +1,91 @@ > +# Copyright 2019 Gentoo Authors > +# Distributed under the terms of the GNU General Public License v2 > + > +from io import BytesIO > +from os import strerror > +from struct import Struct > + > +from socket import ( > + AF_NETLINK, AF_UNSPEC, > + MSG_PEEK, MSG_TRUNC, > + NETLINK_ROUTE, > + SOCK_DGRAM, > + inet_pton, socket, > +) > + > +IFA_LOCAL = 2 > +IFF_UP = 0x1 > +IFLA_IFNAME = 3 > +NLMSG_ERROR = 2 > +RTM_NEWLINK = 16 > +RTM_GETLINK = 18 > +RTM_NEWADDR = 20 > +NLM_F_REQUEST = 0x1 > +NLM_F_ACK = 0x4 > +NLM_F_EXCL = 0x200 > +NLM_F_CREATE = 0x400 > + > +nlmsghdr = Struct('=IHHII') > +nlmsgerr = Struct('i') > +rtattr = Struct('HH') > +ifinfomsg = Struct('BHiII') > +ifaddrmsg = Struct('i') > + > +def create_nlmsg(nlmsg_type, nlmsg_flags, nlmsg_seq, nlmsg_pid, data): > + nlmsg_len = nlmsghdr.size + len(data) > + return nlmsghdr.pack(nlmsg_len, nlmsg_type, nlmsg_flags, > nlmsg_seq, nlmsg_pid) + data > + > +def create_rtattr(rta_type, data): > + rta_len = rtattr.size + len(data) > + return rtattr.pack(rta_len, rta_type) + data > + > +def parse_message(msg): > + buf = BytesIO(msg) > + hdr = nlmsghdr.unpack(buf.read(nlmsghdr.size)) > + if hdr[1] == NLMSG_ERROR: > + err = nlmsgerr.unpack(buf.read(nlmsgerr.size)) > + error = -err[0] > + if error != 0: > + raise OSError(error, strerror(error)) > + elif hdr[1] == RTM_NEWLINK: > + return ifinfomsg.unpack(buf.read(ifinfomsg.size)) > + > +class RtNetlink: > + def __init__(self): > + self.sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE) > + self.addr = (0, 0) > + self.sock.bind(self.addr) > + > + def __enter__(self): > + return self > + > + def __exit__(self, exc_type, exc_value, traceback): > + self.sock.close() > + > + def send_message(self, msg): > + self.sock.sendto(msg, self.addr) > + # Messages are variable length, but 128 is enough for the > the ones we care about. > + resp = self.sock.recv(128) > +
[gentoo-portage-dev] [PATCH] Use RTNETLINK to configure the loopback interface
This eliminates the dependency on iproute2 on Linux. Signed-off-by: Mike Gilbert --- lib/portage/process.py | 25 -- lib/portage/util/netlink.py | 91 + 2 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 lib/portage/util/netlink.py diff --git a/lib/portage/process.py b/lib/portage/process.py index 2a2cbd972..bb4462c7f 100644 --- a/lib/portage/process.py +++ b/lib/portage/process.py @@ -10,7 +10,6 @@ import multiprocessing import platform import signal import socket -import struct import subprocess import sys import traceback @@ -489,17 +488,6 @@ def _configure_loopback_interface(): Configure the loopback interface. """ - IFF_UP = 0x1 - ifreq = struct.pack('16sh', b'lo', IFF_UP) - SIOCSIFFLAGS = 0x8914 - - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) - try: - fcntl.ioctl(sock, SIOCSIFFLAGS, ifreq) - except IOError as e: - writemsg("Unable to enable loopback interface: %s\n" % e.strerror, noiselevel=-1) - sock.close() - # We add some additional addresses to work around odd behavior in glibc's # getaddrinfo() implementation when the AI_ADDRCONFIG flag is set. # @@ -514,12 +502,17 @@ def _configure_loopback_interface(): # Bug: https://bugs.gentoo.org/690758 # Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=12377#c13 + from portage.util.netlink import RtNetlink + try: - subprocess.call(['ip', 'address', 'add', '10.0.0.1/8', 'dev', 'lo']) - if _has_ipv6(): - subprocess.call(['ip', 'address', 'add', 'fd00::1/8', 'dev', 'lo']) + with RtNetlink() as rtnl: + ifindex = rtnl.get_link_ifindex(b'lo') + rtnl.set_link_up(ifindex) + rtnl.add_address(ifindex, socket.AF_INET, '10.0.0.1', 8) + if _has_ipv6(): + rtnl.add_address(ifindex, socket.AF_INET6, 'fd::1', 8) except EnvironmentError as e: - writemsg("Error calling 'ip': %s\n" % e.strerror, noiselevel=-1) + writemsg("Unable to configure loopback interface: %s\n" % e.strerror, noiselevel=-1) def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask, cwd, diff --git a/lib/portage/util/netlink.py b/lib/portage/util/netlink.py new file mode 100644 index 0..5b18b8f95 --- /dev/null +++ b/lib/portage/util/netlink.py @@ -0,0 +1,91 @@ +# Copyright 2019 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +from io import BytesIO +from os import strerror +from struct import Struct + +from socket import ( + AF_NETLINK, AF_UNSPEC, + MSG_PEEK, MSG_TRUNC, + NETLINK_ROUTE, + SOCK_DGRAM, + inet_pton, socket, +) + +IFA_LOCAL = 2 +IFF_UP = 0x1 +IFLA_IFNAME = 3 +NLMSG_ERROR = 2 +RTM_NEWLINK = 16 +RTM_GETLINK = 18 +RTM_NEWADDR = 20 +NLM_F_REQUEST = 0x1 +NLM_F_ACK = 0x4 +NLM_F_EXCL = 0x200 +NLM_F_CREATE = 0x400 + +nlmsghdr = Struct('=IHHII') +nlmsgerr = Struct('i') +rtattr = Struct('HH') +ifinfomsg = Struct('BHiII') +ifaddrmsg = Struct('i') + +def create_nlmsg(nlmsg_type, nlmsg_flags, nlmsg_seq, nlmsg_pid, data): + nlmsg_len = nlmsghdr.size + len(data) + return nlmsghdr.pack(nlmsg_len, nlmsg_type, nlmsg_flags, nlmsg_seq, nlmsg_pid) + data + +def create_rtattr(rta_type, data): + rta_len = rtattr.size + len(data) + return rtattr.pack(rta_len, rta_type) + data + +def parse_message(msg): + buf = BytesIO(msg) + hdr = nlmsghdr.unpack(buf.read(nlmsghdr.size)) + if hdr[1] == NLMSG_ERROR: + err = nlmsgerr.unpack(buf.read(nlmsgerr.size)) + error = -err[0] + if error != 0: + raise OSError(error, strerror(error)) + elif hdr[1] == RTM_NEWLINK: + return ifinfomsg.unpack(buf.read(ifinfomsg.size)) + +class RtNetlink: + def __init__(self): + self.sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE) + self.addr = (0, 0) + self.sock.bind(self.addr) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.sock.close() + + def send_message(self, msg): + self.sock.sendto(msg, self.addr) + # Messages are variable length, but 128 is enough for the the ones we care about. + resp = self.sock.recv(128) + return parse_message(resp) + + def get_link_ifindex(self, ifname): + body = ifinfomsg.pack(AF_UNSPEC, 0, 0, 0, 0) + body += create_rtattr(IFLA_IFNAME, ifname) + flags = NLM_F_REQUEST + msg = create_nlmsg(RTM_GETLINK, flags, 1, 0, body) + resp = self.send_message(msg)