Re: [gentoo-portage-dev] [PATCH] Use RTNETLINK to configure the loopback interface

2019-08-28 Thread Mike Gilbert
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

2019-08-27 Thread Zac Medico
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

2019-08-27 Thread Alec Warner
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

2019-08-26 Thread Mike Gilbert
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)