@@ -11,10 +11,20 @@ import argparse
1111import os
1212import string
1313import sys
14+ import subprocess
15+ import json
16+ import struct
17+ import fcntl
1418
1519import yaml
1620
1721
22+ def get_sudo_uid () -> int :
23+ uid = os .environ ["SUDO_UID" ]
24+ if uid is not None :
25+ return int (uid )
26+
27+
1828def get_denylist ():
1929 denylist_file = "/etc/labgrid/helpers.yaml"
2030 try :
@@ -32,6 +42,66 @@ def get_denylist():
3242
3343 return denylist
3444
45+ def open_tap (name , device = "/dev/net/tun" ):
46+ TUNSETIFF = 0x400454CA
47+ IFF_NO_PI = 0x1000
48+ O_RDWR = 0x2
49+ IFF_TAP = 0x0002
50+
51+ flags = IFF_TAP | IFF_NO_PI
52+ name = name .encode ()
53+ ifr_name = name + b"\x00 " * (16 - len (name ))
54+ ifr = struct .pack ("16sH22s" , ifr_name , flags , b"\x00 " * 22 )
55+
56+ fd = os .open (device , O_RDWR )
57+ fcntl .ioctl (fd , TUNSETIFF , ifr )
58+ return fd
59+
60+
61+ def handle_ns_macvtap (options ):
62+ # Use macvtap in bridge mode. This is more consistent than using hairpin
63+ # mode and relying on a switch to relay packets back into the interface.
64+ output = subprocess .check_output (
65+ ["ip" , "-j" , "-echo" , "link" , "add" , "link" , options .ifname , "type" , "macvtap" , "mode" , "bridge" ]
66+ )
67+ output = json .loads (output )
68+ assert len (output ) == 1
69+ ifindex_new = output [0 ]["ifindex" ]
70+ ifname_new = output [0 ]["ifname" ]
71+ assert ifname_new .startswith ("macvtap" )
72+ try :
73+ # Set the flag that tells the kernel to allow multicast to flow through
74+ # the macvtap. This is required to be done on the exporter side for the
75+ # client to receive multicast groups that the exporter is not listening
76+ # to. Note that the client still needs to do multicast group membership
77+ # to tell intermediate routers about the groups, this just prevents the
78+ # kernel from filtering them out before they are sent to the tap.
79+ subprocess .check_call (["ip" , "link" , "set" , "allmulticast" , "on" , "dev" , ifname_new ])
80+
81+ if options .mac_address :
82+ subprocess .check_call (
83+ ["ip" , "link" , "set" , "address" , options .mac_address , "dev" , ifname_new ])
84+ subprocess .check_call (
85+ ["ip" , "link" , "set" , "dev" , ifname_new , "name" , "macvtap0" , "up" , "netns" , str (options .pid ), "index" , str (ifindex_new )]
86+ )
87+ except :
88+ subprocess .check_call (
89+ ["ip" , "link" , "del" , ifname_new ]
90+ )
91+ raise
92+ tap_name = f"/dev/tap{ ifindex_new } "
93+ uid = get_sudo_uid ()
94+ os .chown (
95+ path = tap_name ,
96+ uid = uid ,
97+ gid = - 1 ,
98+ follow_symlinks = False ,
99+ )
100+
101+ with os .fdopen (open_tap (ifname_new , device = tap_name )) as macvtap_fd :
102+ os .set_inheritable (macvtap_fd .fileno (), True )
103+ os .execlp ("labgrid-tap-fwd" , "labgrid-tap-fwd" , str (macvtap_fd .fileno ()))
104+
35105
36106def main (program , options ):
37107 if not options .ifname :
@@ -46,7 +116,7 @@ def main(program, options):
46116 if options .ifname in denylist :
47117 raise ValueError (f"Interface name '{ options .ifname } ' is denied in denylist." )
48118
49- programs = ["tcpreplay" , "tcpdump" , "ip" , "ethtool" ]
119+ programs = ["tcpreplay" , "tcpdump" , "ip" , "ethtool" , "ns-macvtap" ]
50120 if program not in programs :
51121 raise ValueError (f"Invalid program { program } called with wrapper, valid programs are: { programs } " )
52122
@@ -112,6 +182,10 @@ def main(program, options):
112182 args .append (options .ifname )
113183 args .extend (options .ethtool_pause_args )
114184
185+ elif program == "ns-macvtap" :
186+ handle_ns_macvtap (options )
187+ return
188+
115189 try :
116190 os .execvp (args [0 ], args )
117191 except FileNotFoundError as e :
@@ -165,6 +239,12 @@ if __name__ == "__main__":
165239 "ethtool_pause_args" , metavar = "ARG" , nargs = argparse .REMAINDER , help = "ethtool --pause args"
166240 )
167241
242+ # ns-macvtap
243+ ns_mactap_parser = subparsers .add_parser ("ns-macvtap" )
244+ ns_mactap_parser .add_argument ("ifname" , type = str , help = "interface name" )
245+ ns_mactap_parser .add_argument ("pid" , type = int , help = "pid of namespace agent" )
246+ ns_mactap_parser .add_argument ("--mac-address" , type = str , metavar = "ADDRESS" , help = "Set interface MAC address to ADDRESS" )
247+
168248 args = parser .parse_args ()
169249 try :
170250 main (args .program , args )
0 commit comments