def on(self, target, component): if not commonl.prctl_cap_get_effective() & 1 << 12: # If we don't have network setting privilege, # don't even go there # CAP_NET_ADMIN is 12 (from /usr/include/linux/prctl.h. # # Fail here (upon use) instead of during server startup, # because maybe we don't really care about it and we have # a default configuration with the thargets that would # need this. raise RuntimeError("daemon lacks CAP_NET_ADMIN: unable to" " add networking capabilities ") if_name, ic_name = self._component_validate(target, component) if not commonl.if_present(f"b{ic_name}"): target.log.info(f"{ic_name}: assuming network off since netif " f"b{ic_name} is not present") return commonl.if_remove_maybe(if_name) # ensure no leftovers subprocess.check_call(["ip", "tuntap", "add", if_name, "mode", "tap"], stderr=subprocess.STDOUT) subprocess.check_call( ["ip", "link", "set", if_name, "master", "b" + ic_name], stderr=subprocess.STDOUT) # promisc on: needed so we can wireshark in subprocess.check_call( ["ip", "link", "set", if_name, "promisc", "on", "up"], stderr=subprocess.STDOUT) # We don't assign IP addresses here -- we leave it for the # client; if we do, for example QEMU won't work as it is just # used to associate the interface target.fsdb.set(component, if_name)
def _qemu_preexec_nw(self): # This is called by subprocess.Popen after spawning to run # qemu from tt_qemu.power_on_do() right before spawning Qemu # for us. # # See doc block on top of this class and on :class:`vlan_pci` # for why file descriptor 0. # # We will find out which is the index of the TAP device # assigned to this, created on _image_power_on_pre and open # file desctriptor 0 to it, then leave it open for Qemu to # tap into it. count = 0 for ic_name, ic_kws in list(self.tags.get('interconnects', {}).items()): if not 'ipv4_addr' in ic_kws and not 'ipv6_addr' in ic_kws: continue if count > 0: raise NotImplementedError( "QEMU Networking cannot implement " "multiple networks at this time " "(when trying to connec to %s)" % ic_name) kws = dict(ic_kws) kws.update(self.kws) kws['ic_name'] = ic_name if not commonl.if_present("_b%s" % ic_name): self.log.warning("network %s powered off? networking " "disabled" % ic_name) # If the network is not powered up, skip it # FIXME: replace with it calling vlan_pci.something() # that brings up the basic interface (_bICNAME) so # that once we power the network, it works continue tapindex = commonl.if_index("t%(ic_name)s%(id)s" % kws) assign = 0 # Need to wait for udev to reconfigure this for us to have # access; this is done by a udev rule installed # (/usr/lib/udev/rules.d/80-ttbd.rules) tapdevname = "/dev/tap%d" % tapindex count = 1 top = 4 while not os.access(tapdevname, os.R_OK | os.W_OK): if count >= top: msg = "%s: timed out waiting for udev to set " \ "permissions in /usr/lib/udev/rules.d/80-ttbd.rules" \ % tapdevname self.log.error(msg) raise RuntimeError(msg) time.sleep(0.25) count += 1 fd = os.open("/dev/tap%d" % tapindex, os.O_RDWR, 0) if fd != assign: # there, reassign it to fd @assign os.dup2(fd, assign) os.close(fd) # leave fd assign open for QEMU! count += 1
def _qemu_preexec_nw(self): # This is called by subprocess.Popen after spawning to run # qemu from tt_qemu.power_on_do() right before spawning Qemu # for us. # # See doc block on top of this class and on :class:`vlan_pci` # for why file descriptor 0. # # We will find out which is the index of the TAP device # assigned to this, created on _image_power_on_pre and open # file desctriptor 0 to it, then leave it open for Qemu to # tap into it. count = 0 for ic_name, ic_kws in self.tags.get('interconnects', {}).iteritems(): if not 'ipv4_addr' in ic_kws and not 'ipv6_addr' in ic_kws: continue if count > 0: raise NotImplementedError( "QEMU Networking cannot implement " "multiple networks at this time " "(when trying to connec to %s)" % ic_name) kws = dict(ic_kws) kws.update(self.kws) kws['ic_name'] = ic_name if not commonl.if_present("_b%s" % ic_name): self.log.warning("network %s powered off? networking " "disabled" % ic_name) # If the network is not powered up, skip it # FIXME: replace with it calling vlan_pci.something() # that brings up the basic interface (_bICNAME) so # that once we power the network, it works continue tapindex = commonl.if_index("t%(ic_name)s%(id)s" % kws) assign = 0 # Need to wait for udev to reconfigure this for us to have # access; this is done by a udev rule installed # (/usr/lib/udev/rules.d/80-ttbd.rules) tapdevname = "/dev/tap%d" % tapindex count = 1 top = 4 while not os.access(tapdevname, os.R_OK | os.W_OK): if count >= top: msg = "%s: timed out waiting for udev to set " \ "permissions in /usr/lib/udev/rules.d/80-ttbd.rules" \ % tapdevname self.log.error(msg) raise RuntimeError(msg) time.sleep(0.25) count += 1 fd = os.open("/dev/tap%d" % tapindex, os.O_RDWR, 0) if fd != assign: # there, reassign it to fd @assign os.dup2(fd, assign) os.close(fd) # leave fd assign open for QEMU! count += 1
def on(self, target, component): if_name, ic_name = self._component_validate(target, component) kws = dict( id=target.id, ic_name=ic_name, if_name=if_name, ) ic_data = target.tags['interconnects'][ic_name] try: kws['ipv4_addr'] = ic_data['ipv4_addr'] kws['ipv4_prefix_len'] = ic_data['ipv4_prefix_len'] kws['ipv6_addr'] = ic_data['ipv6_addr'] kws['ipv6_prefix_len'] = ic_data['ipv6_prefix_len'] kws['mac_addr'] = ic_data['mac_addr'] except KeyError as e: raise ValueError( "%s: can't create TAP interface:" " target '%s' declares connection to interconnect '%s'" " but is missing field '%s'" % (component, target.id, ic_name, str(e))) if not commonl.if_present("_b%(ic_name)s" % kws): target.log.info( "%(ic_name)s: assuming network off since netif " "_b%(ic_name)s is not present", kws) return commonl.if_remove_maybe(if_name) # ensure no leftovers subprocess.check_call( "ip link add " " link _b%(ic_name)s" # created by the interconnect " name %(if_name)s" " address %(mac_addr)s" " up" " type macvtap mode bridge" % kws, shell=True, stderr=subprocess.STDOUT) subprocess.check_call( "ip link set %(if_name)s" " promisc on " # needed so we can wireshark in " up" % kws, shell=True, stderr=subprocess.STDOUT) # We don't assign IP addresses here -- we leave it for the # client; if we do, for example QEMU won't work as it is just # used to associate the interface target.fsdb.set(component, if_name)
def _power_on_pre_nw(self): # We need the __init__ part doing it earlier because remember, # these might be running different processes, and the basic # self.qemu_cmdline array has to be initialized so we can # find the actual binary being used. kws = dict(self.kws) # Get fresh values for these keys for key in list(self.fsdb.keys()): if key.startswith("qemu-"): kws[key] = self.fsdb.get(key) # Setup network stuff, create virtual tap interfaces for ic_name, ic_kws in list(self.tags.get('interconnects', {}).items()): if 'ipv4_addr' in ic_kws or 'ipv6_addr' in ic_kws: _kws = dict(kws) _kws.update(ic_kws) _kws['ic_name'] = ic_name # QEMU device ident only allows a-zA-Z0-9-, starting # with letter _kws['ident'] = "id" + self._r_ident.sub("", _kws['mac_addr']) # CAP_NET_ADMIN is 12 (from /usr/include/linux/prctl.h if not commonl.prctl_cap_get_effective() & 1 << 12: # If we don't have network setting privilege, # don't even go there self.log.warning("daemon lacks CAP_NET_ADMIN: unable to " "add networking capabilities ") continue if not commonl.if_present("_b%s" % ic_name): self.log.warning("network %s powered off? networking " "disabled" % ic_name) # If the network is not powered up, skip it # FIXME: replace with it calling vlan_pci.something() # that brings up the basic interface (_bICNAME) so # that once we power the network, it works continue commonl.if_remove_maybe("t%s%s" % (ic_name, self.id)) subprocess.check_call( "ip link add " " link _b%(ic_name)s " " name t%(ic_name)s%(id)s" " address %(mac_addr)s" " up" " type macvtap mode bridge; " "ip link set t%(ic_name)s%(id)s" " promisc on " " up" % _kws, shell = True) # Add to the command line # note model=virtio WORKS with QEMU's BIOS OVMW_CODE.fd self.qemu_cmdline_append += \ " -net nic,id=%(ident)s,model=virtio,macaddr=%(mac_addr)s" \ " -net tap,fd=0,name=%(ident)s" % _kws # Add a nat/host interface? if ic_name == 'nat_host': raise RuntimeError("NAT configuration is currently broken") # we hardcode the MAC address of the NAT ethernet # interface, which we'll also add in the configuration # for systemd-network in /etc/systemd/network for it # to do DHCP. There is only one of those interfaces # per virtual host, so it will never conflict with # anything and we don't use the form of addressing in # any MAC we generate. mac_addr = "02:01:01:01:01:01" self.qemu_cmdline_append += \ " -net nic,name=nat_host,model=virtio,macaddr=%s" \ " -net user,id=nat_host,net=192.168.200.0/24,dhcpstart=192.168.200.10 " \ % mac_addr # If no network interfaces we added, remove the default # networking QEMU does if "-net" not in self.qemu_cmdline \ and "-net" not in self.qemu_cmdline_append: self.qemu_cmdline_append += "-net none "
def _power_on_pre_nw(self): # We need the __init__ part doing it earlier because remember, # these might be running different processes, and the basic # self.qemu_cmdline array has to be initialized so we can # find the actual binary being used. kws = dict(self.kws) # Get fresh values for these keys for key in self.fsdb.keys(): if key.startswith("qemu-"): kws[key] = self.fsdb.get(key) # Setup network stuff, create virtual tap interfaces for ic_name, ic_kws in self.tags.get('interconnects', {}).iteritems(): if 'ipv4_addr' in ic_kws or 'ipv6_addr' in ic_kws: _kws = dict(kws) _kws.update(ic_kws) _kws['ic_name'] = ic_name # QEMU device ident only allows a-zA-Z0-9-, starting # with letter _kws['ident'] = "id" + self._r_ident.sub("", _kws['mac_addr']) # CAP_NET_ADMIN is 12 (from /usr/include/linux/prctl.h if not commonl.prctl_cap_get_effective() & 1 << 12: # If we don't have network setting privilege, # don't even go there self.log.warning("daemon lacks CAP_NET_ADMIN: unable to " "add networking capabilities ") continue if not commonl.if_present("_b%s" % ic_name): self.log.warning("network %s powered off? networking " "disabled" % ic_name) # If the network is not powered up, skip it # FIXME: replace with it calling vlan_pci.something() # that brings up the basic interface (_bICNAME) so # that once we power the network, it works continue commonl.if_remove_maybe("t%s%s" % (ic_name, self.id)) subprocess.check_call( "ip link add " " link _b%(ic_name)s " " name t%(ic_name)s%(id)s" " address %(mac_addr)s" " up" " type macvtap mode bridge; " "ip link set t%(ic_name)s%(id)s" " promisc on " " up" % _kws, shell = True) # Add to the command line # note model=virtio WORKS with QEMU's BIOS OVMW_CODE.fd self.qemu_cmdline_append += \ " -net nic,id=%(ident)s,model=virtio,macaddr=%(mac_addr)s" \ " -net tap,fd=0,name=%(ident)s" % _kws # Add a nat/host interface? if ic_name == 'nat_host': raise RuntimeError("NAT configuration is currently broken") # we hardcode the MAC address of the NAT ethernet # interface, which we'll also add in the configuration # for systemd-network in /etc/systemd/network for it # to do DHCP. There is only one of those interfaces # per virtual host, so it will never conflict with # anything and we don't use the form of addressing in # any MAC we generate. mac_addr = "02:01:01:01:01:01" self.qemu_cmdline_append += \ " -net nic,name=nat_host,model=virtio,macaddr=%s" \ " -net user,id=nat_host,net=192.168.200.0/24,dhcpstart=192.168.200.10 " \ % mac_addr # If no network interfaces we added, remove the default # networking QEMU does if "-net" not in self.qemu_cmdline \ and "-net" not in self.qemu_cmdline_append: self.qemu_cmdline_append += "-net none "
def on(self, target, _component): # Bring up the lower network interface; lower is called # whatever (if it is a physical device) or _bNAME; bring it # up, make it promiscuous mode = self._get_mode(target) if mode == 'vlan': # our lower is a physical device, our upper is a device # which till tag for eth vlan %(vlan) ifname = commonl.if_find_by_mac(target.tags['mac_addr'], physical=True) if not commonl.if_present("b%(id)s" % target.kws): # Do create the new interface only if not already # created, otherwise daemons that are already running # will stop operating # This function might be being called to restablish a # half baked operating state. kws = dict(target.kws) kws['ifname'] = ifname subprocess.check_call( "/usr/sbin/ip link add" " link %(ifname)s name b%(id)s" " type vlan id %(vlan)s" #" protocol VLAN_PROTO" #" reorder_hdr on|off" #" gvrp on|off mvrp on|off loose_binding on|off" % kws, shell=True) subprocess.check_call( # bring lower up "/usr/sbin/ip link set dev %s up promisc on" % ifname, shell=True) elif mode == 'physical': ifname = commonl.if_find_by_mac(target.tags['mac_addr']) subprocess.check_call( # bring lower up "/usr/sbin/ip link set dev %s up promisc on" % ifname, shell=True) self._if_rename(target) elif mode == 'virtual': # We create a bridge, to serve as lower if not commonl.if_present("b%(id)s" % target.kws): # Do create the new interface only if not already # created, otherwise daemons that are already running # will stop operating # This function might be being called to restablish a # half baced operating state. commonl.if_remove_maybe("b%(id)s" % target.kws) subprocess.check_call("/usr/sbin/ip link add" " name b%(id)s" " type bridge" % target.kws, shell=True) subprocess.check_call( # bring lower up "/usr/sbin/ip link set" " dev b%(id)s" " up promisc on" % target.kws, shell=True) else: raise AssertionError("Unknown mode %s" % mode) # Configure the IP addresses for the top interface subprocess.check_call( # clean up existing address "/usr/sbin/ip add flush dev b%(id)s " % target.kws, shell=True) subprocess.check_call( # add IPv6 # if this fails, check Network Manager hasn't disabled ipv6 # sysctl -a | grep disable_ipv6 must show all to 0 "/usr/sbin/ip addr add" " %(ipv6_addr)s/%(ipv6_prefix_len)s dev b%(id)s " % target.kws, shell=True) subprocess.check_call( # add IPv4 "/usr/sbin/ip addr add" " %(ipv4_addr)s/%(ipv4_prefix_len)d" " dev b%(id)s" % target.kws, shell=True) # Bring up the top interface, which sets up ther outing subprocess.check_call( "/usr/sbin/ip link set dev b%(id)s up promisc on" % target.kws, shell=True) target.fsdb.set('power_state', 'on') # Start tcpdump on the network? # # The value of the tcpdump property, if not None, is the # filename we'll capture to. tcpdump = target.fsdb.get('tcpdump') if tcpdump: assert not os.path.sep in tcpdump \ and tcpdump != "" \ and tcpdump != os.path.pardir \ and tcpdump != os.path.curdir, \ "Bad filename for TCP dump capture '%s' specified as " \ " value to property *tcpdump*: must not include" % tcpdump # per ttbd:make_ticket(), colon splits the real username # from the ticket owner = target.owner_get().split(":")[0] assert owner, "BUG? target not owned on power on?" capfile = os.path.join(target.files_path, owner, tcpdump) # Because it is in the user's area, # we assume the user knows what he is doing to overwrite it, # so we'll remove any first commonl.rm_f(capfile) pidfile = os.path.join(target.state_dir, "tcpdump.pid") logfile = os.path.join(target.state_dir, "tcpdump.log") cmdline = [ "/usr/sbin/tcpdump", "-U", "-i", "b%(id)s" % target.kws, "-w", capfile ] try: logf = open(logfile, "a") target.log.info("Starting tcpdump with: %s", " ".join(cmdline)) p = subprocess.Popen(cmdline, shell=False, cwd=target.state_dir, close_fds=True, stdout=logf, stderr=subprocess.STDOUT) except OSError as e: raise RuntimeError("tcpdump failed to start: %s" % e) ttbl.daemon_pid_add(p.pid) # FIXME: race condition if it died? with open(pidfile, "w") as pidfilef: pidfilef.write("%d" % p.pid) pid = commonl.process_started( # Verify it started pidfile, "/usr/sbin/tcpdump", verification_f=os.path.exists, verification_f_args=(capfile, ), timeout=20, tag="tcpdump", log=target.log) if pid == None: raise RuntimeError("tcpdump failed to start after 5s")