def add(self, kind, ifname, reuse=False, **kwarg): ''' Create new network interface ''' with self.ipdb.exclusive: # check for existing interface if ifname in self: if (self[ifname]['ipdb_scope'] == 'shadow') or reuse: device = self[ifname] kwarg['kind'] = kind device.load_dict(kwarg) if self[ifname]['ipdb_scope'] == 'shadow': with device._direct_state: device['ipdb_scope'] = 'create' else: raise CreateException("interface %s exists" % ifname) else: device = self[ifname] = Interface(ipdb=self.ipdb, mode='snapshot') device.update(kwarg) if isinstance(kwarg.get('link', None), Interface): device['link'] = kwarg['link']['index'] if isinstance(kwarg.get('vxlan_link', None), Interface): device['vxlan_link'] = kwarg['vxlan_link']['index'] device['kind'] = kind device['index'] = kwarg.get('index', 0) device['ifname'] = ifname device['ipdb_scope'] = 'create' device._mode = self.ipdb.mode device.begin() return device
def commit(self, tid=None, transaction=None, rollback=False, newif=False): ''' Commit transaction. In the case of exception all changes applied during commit will be reverted. ''' error = None added = None removed = None drop = True if tid: transaction = self._transactions[tid] else: if transaction: drop = False else: transaction = self.last() if transaction.partial: transaction.errors = [] wd = None with self._write_lock: # if the interface does not exist, create it first ;) if self['ipdb_scope'] != 'system': request = IPLinkRequest(self.filter('common')) # create watchdog wd = self.ipdb.watchdog(ifname=self['ifname']) newif = True try: # 8<---------------------------------------------------- # ACHTUNG: hack for old platforms if request.get('address', None) == '00:00:00:00:00:00': del request['address'] del request['broadcast'] # 8<---------------------------------------------------- try: self.nl.link('add', **request) except NetlinkError as x: # File exists if x.code == errno.EEXIST: # A bit special case, could be one of two cases: # # 1. A race condition between two different IPDB # processes # 2. An attempt to create dummy0, gre0, bond0 when # the corrseponding module is not loaded. Being # loaded, the module creates a default interface # by itself, causing the request to fail # # The exception in that case can cause the DB # inconsistence, since there can be queued not only # the interface creation, but also IP address # changes etc. # # So we ignore this particular exception and try to # continue, as it is created by us. pass # Operation not supported elif x.code == errno.EOPNOTSUPP and \ request.get('index', 0) != 0: # ACHTUNG: hack for old platforms request = IPLinkRequest({ 'ifname': self['ifname'], 'kind': self['kind'], 'index': 0 }) self.nl.link('add', **request) else: raise except Exception as e: # on failure, invalidate the interface and detach it # from the parent # 1. drop the IPRoute() link self.nl = None # 2. clean up ipdb self.detach() # 3. invalidate the interface with self._direct_state: for i in tuple(self.keys()): del self[i] # 4. the rest self._mode = 'invalid' self._exception = e self._tb = traceback.format_exc() # raise the exception if transaction.partial: transaction.errors.append(e) raise PartialCommitException() else: raise if wd is not None: wd.wait() if self['index'] == 0: # Only the interface creation time issue on # old or compat platforms. The interface index # may be not known yet, but we can not continue # without it. It will be updated anyway, but # it is better to force the lookup. ix = self.nl.link_lookup(ifname=self['ifname']) if ix: self['index'] = ix[0] else: if transaction.partial: transaction.errors.append(CreateException()) raise PartialCommitException() else: raise CreateException() # now we have our index and IP set and all other stuff snapshot = self.pick() # resolve all delayed ports def resolve_ports(transaction, ports, callback, self, drop): def error(x): return KeyError('can not resolve port %s' % x) for port in tuple(ports): ifindex = self._resolve_port(port) if ifindex is None: if transaction.partial: transaction.errors.append(error(port)) else: if drop: self.drop(transaction) raise error(port) else: ports.remove(port) callback(ifindex, direct=True) resolve_ports(transaction, transaction._delay_add_port, transaction.add_port, self, drop) resolve_ports(transaction, transaction._delay_del_port, transaction.del_port, self, drop) try: removed = snapshot - transaction added = transaction - snapshot run = transaction._run nl = transaction.nl # 8<--------------------------------------------- # Port vlans self['vlans'].set_target(transaction['vlans']) for i in removed['vlans']: # remove vlan from the port run(nl.link, 'vlan-del', index=self['index'], vlan_info=self['vlans'][i]) for i in added['vlans']: # add vlan to the port run(nl.link, 'vlan-add', index=self['index'], vlan_info=transaction['vlans'][i]) if (not transaction.partial) and \ (removed['vlans'] or added['vlans']): self['vlans'].target.wait(SYNC_TIMEOUT) if not self['vlans'].target.is_set(): raise CommitException('vlans target is not set') # 8<--------------------------------------------- # Ports self['ports'].set_target(transaction['ports']) for i in removed['ports']: # detach port if i in self.ipdb.interfaces: port = self.ipdb.interfaces[i] port.set_target('master', None) port.mirror_target('master', 'link') run(nl.link, 'set', index=port['index'], master=0) else: transaction.errors.append(KeyError(i)) for i in added['ports']: # attach port if i in self.ipdb.interfaces: port = self.ipdb.interfaces[i] port.set_target('master', self['index']) port.mirror_target('master', 'link') run(nl.link, 'set', index=port['index'], master=self['index']) else: transaction.errors.append(KeyError(i)) if (not transaction.partial) and \ (removed['ports'] or added['ports']): for link in self.nl.get_links(*(removed['ports'] | added['ports'])): self.ipdb.device_put(link) self['ports'].target.wait(SYNC_TIMEOUT) if not self['ports'].target.is_set(): raise CommitException('ports target is not set') # wait for proper targets on ports for i in list(added['ports']) + list(removed['ports']): port = self.ipdb.interfaces[i] target = port._local_targets['master'] target.wait(SYNC_TIMEOUT) del port._local_targets['master'] del port._local_targets['link'] if not target.is_set(): raise CommitException('master target failed') if i in added['ports']: if port.if_master != self['index']: raise CommitException('master set failed') else: if port.if_master == self['index']: raise CommitException('master unset failed') # 8<--------------------------------------------- # Interface changes request = IPLinkRequest() for key in added: if (key in self._xfields['common']) and \ (key != 'kind'): request[key] = added[key] request['index'] = self['index'] # apply changes only if there is something to apply if any([ request[item] is not None for item in request if item != 'index' ]): if request.get('address', None) == '00:00:00:00:00:00': request.pop('address') request.pop('broadcast', None) run(nl.link, 'set', **request) # hardcoded pause -- if the interface was moved # across network namespaces if not transaction.partial and 'net_ns_fd' in request: while True: # wait until the interface will disappear # from the main network namespace try: for link in self.nl.get_links(self['index']): self.ipdb.device_put(link) except NetlinkError as e: if e.code == errno.ENODEV: break raise except Exception: raise time.sleep(0.1) # 8<--------------------------------------------- # IP address changes # # There is one corner case: if the interface didn't # exist before commit(), the transaction may not # contain automatic IPv6 addresses. # # So fetch here possible addresses and use it to # extend the transaction target = self._commit_real_ip().union(set(transaction['ipaddr'])) self['ipaddr'].set_target(target) # The promote_secondaries sysctl causes the kernel # to add secondary addresses back after the primary # address is removed. # # The library can not tell this from the result of # an external program. # # One simple way to work that around is to remove # secondaries first. rip = sorted(removed['ipaddr'], key=lambda x: self['ipaddr'][x]['flags'], reverse=True) # 8<-------------------------------------- for i in rip: # Ignore link-local IPv6 addresses if i[0][:4] == 'fe80' and i[1] == 64: continue # When you remove a primary IP addr, all subnetwork # can be removed. In this case you will fail, but # it is OK, no need to roll back try: self.ipdb.update_addr( run(nl.addr, 'delete', self['index'], i[0], i[1]), 'remove') except NetlinkError as x: # bypass only errno 99, 'Cannot assign address' if x.code != errno.EADDRNOTAVAIL: raise except socket.error as x: # bypass illegal IP requests if isinstance(x.args[0], basestring) and \ x.args[0].startswith('illegal IP'): continue raise # 8<-------------------------------------- target = added['ipaddr'] for i in range(3): # just to be sure self._commit_add_ip(target, transaction) if transaction.partial: break real = self._commit_real_ip() if real >= set(transaction['ipaddr']): break else: target = set(transaction['ipaddr']) - real else: raise CommitException('ipaddr setup error', i) # 8<-------------------------------------- if (not transaction.partial) and \ (removed['ipaddr'] or added['ipaddr']): # 8<-------------------------------------- # bond and bridge interfaces do not send # IPv6 address updates, when are down # # beside of that, bridge interfaces are # down by default, so they never send # address updates from beginning # # so if we need, force address load # # FIXME: probably, we should handle other # types as well if self['kind'] in ('bond', 'bridge', 'veth'): self.ipdb.update_addr(self.nl.get_addr(), 'add') # 8<-------------------------------------- self['ipaddr'].target.wait(SYNC_TIMEOUT) if not self['ipaddr'].target.is_set(): raise CommitException('ipaddr target is not set') # 8<--------------------------------------------- # Iterate callback chain for ch in self._commit_hooks: # An exception will rollback the transaction ch(self.dump(), snapshot.dump(), transaction.dump()) # 8<--------------------------------------------- # reload interface to hit targets if not transaction.partial: if transaction._targets: try: self.reload() except NetlinkError as e: if e.code == errno.ENODEV: # No such device if ('net_ns_fd' in added) or \ ('net_ns_pid' in added): # it means, that the device was moved # to another netns; just give up if drop: self.drop(transaction) return self # wait for targets transaction._wait_all_targets() # 8<--------------------------------------------- # Interface removal if (added.get('ipdb_scope') in ('shadow', 'remove')) or\ ((added.get('ipdb_scope') == 'create') and rollback): wd = self.ipdb.watchdog(action='RTM_DELLINK', ifname=self['ifname']) if added.get('ipdb_scope') in ('shadow', 'create'): self.set_item('ipdb_scope', 'locked') self.nl.link('delete', **self) wd.wait() if added.get('ipdb_scope') == 'shadow': self.set_item('ipdb_scope', 'shadow') if added['ipdb_scope'] == 'create': self.load_dict(transaction) if drop: self.drop(transaction) return self # 8<--------------------------------------------- except Exception as e: error = e # something went wrong: roll the transaction back if not rollback: try: self.commit(transaction=snapshot, rollback=True, newif=newif) except Exception as i_e: error = RuntimeError() error.cause = i_e else: # reload all the database -- it can take a long time, # but it is required since we have no idea, what is # the result of the failure links = self.nl.get_links() for link in links: self.ipdb.device_put(link, skip_slaves=True) for link in links: self.ipdb.update_slaves(link) links = self.nl.get_vlans() for link in links: self.ipdb.update_dev(link) self.ipdb.update_addr(self.nl.get_addr()) error.traceback = traceback.format_exc() for key in ('ipaddr', 'ports', 'vlans'): self[key].clear_target() # raise partial commit exceptions if transaction.partial and transaction.errors: error = PartialCommitException('partial commit error') # if it is not a rollback turn if drop and not rollback: # drop last transaction in any case self.drop(transaction) # raise exception for failed transaction if error is not None: error.transaction = transaction raise error time.sleep(config.commit_barrier) # drop all collected errors, if any self.errors = [] return self
def commit(self, tid=None, transaction=None, commit_phase=1, commit_mask=0xff, newif=False): ''' Commit transaction. In the case of exception all changes applied during commit will be reverted. ''' if not commit_phase & commit_mask: return self def invalidate(): # on failure, invalidate the interface and detach it # from the parent # 0. obtain lock on IPDB, to avoid deadlocks # ... all the DB updates will wait with self.ipdb.exclusive: # 1. drop the IPRoute() link self.nl = None # 2. clean up ipdb self.detach() # 3. invalidate the interface with self._direct_state: for i in tuple(self.keys()): del self[i] # 4. the rest self._mode = 'invalid' error = None added = None removed = None drop = True init = None if tid: transaction = self.global_tx[tid] else: if transaction: drop = False else: transaction = self.current_tx if transaction.partial: transaction.errors = [] with self._write_lock: # if the interface does not exist, create it first ;) if self['ipdb_scope'] != 'system': newif = True self.set_target('ipdb_scope', 'system') try: # 8<---------------------------------------------------- # ACHTUNG: hack for old platforms if self['address'] == '00:00:00:00:00:00': with self._direct_state: self['address'] = None self['broadcast'] = None # 8<---------------------------------------------------- init = self.pick() try: self.nl.link('add', **self) except NetlinkError as x: # File exists if x.code == errno.EEXIST: # A bit special case, could be one of two cases: # # 1. A race condition between two different IPDB # processes # 2. An attempt to create dummy0, gre0, bond0 when # the corrseponding module is not loaded. Being # loaded, the module creates a default interface # by itself, causing the request to fail # # The exception in that case can cause the DB # inconsistence, since there can be queued not only # the interface creation, but also IP address # changes etc. # # So we ignore this particular exception and try to # continue, as it is created by us. pass else: raise except Exception as e: if transaction.partial: transaction.errors.append(e) raise PartialCommitException() else: # If link('add', ...) raises an exception, no netlink # broadcast will be sent, and the object is unmodified. # After the exception forwarding, the object is ready # to repeat the commit() call. raise if transaction['ipdb_scope'] == 'create' and commit_phase > 1: if self['index']: wd = self.ipdb.watchdog(action='RTM_DELLINK', ifname=self['ifname']) with self._direct_state: self['ipdb_scope'] = 'locked' self.nl.link('delete', index=self['index']) wd.wait() self.load_dict(transaction) return self elif newif: # Here we come only if a new interface is created # if commit_phase == 1 and not self.wait_target('ipdb_scope'): invalidate() raise CreateException() # Re-populate transaction.ipaddr to have a proper IP target # # The reason behind the code is that a new interface in the # "up" state will have automatic IPv6 addresses, that aren't # reflected in the transaction. This may cause a false IP # target mismatch and a commit failure. # # To avoid that, collect automatic addresses to the # transaction manually, since it is not yet properly linked. # for addr in self.ipdb.ipaddr[self['index']]: transaction['ipaddr'].add(addr) # now we have our index and IP set and all other stuff snapshot = self.pick() # resolve all delayed ports def resolve_ports(transaction, ports, callback, self, drop): def error(x): return KeyError('can not resolve port %s' % x) for port in tuple(ports): ifindex = self._resolve_port(port) if ifindex is None: if transaction.partial: transaction.errors.append(error(port)) else: if drop: self.drop(transaction.uid) raise error(port) else: ports.remove(port) with transaction._direct_state: # ???? callback(ifindex) resolve_ports(transaction, transaction._delay_add_port, transaction.add_port, self, drop) resolve_ports(transaction, transaction._delay_del_port, transaction.del_port, self, drop) try: removed, added = snapshot // transaction run = transaction._run nl = transaction.nl # 8<--------------------------------------------- # Port vlans if removed['vlans'] or added['vlans']: if added['vlans']: transaction['vlans'].add(1) self['vlans'].set_target(transaction['vlans']) for i in removed['vlans']: if i != 1: # remove vlan from the port run(nl.vlan_filter, 'del', index=self['index'], vlan_info=self['vlans'][i]) for i in added['vlans']: if i != 1: # add vlan to the port run(nl.vlan_filter, 'add', index=self['index'], vlan_info=transaction['vlans'][i]) self['vlans'].target.wait(SYNC_TIMEOUT) if not self['vlans'].target.is_set(): raise CommitException('vlans target is not set') # 8<--------------------------------------------- # Ports if removed['ports'] or added['ports']: self['ports'].set_target(transaction['ports']) for i in removed['ports']: # detach port if i in self.ipdb.interfaces: (self.ipdb.interfaces[i] .set_target('master', None) .mirror_target('master', 'link')) run(nl.link, 'set', index=i, master=0) else: transaction.errors.append(KeyError(i)) for i in added['ports']: # attach port if i in self.ipdb.interfaces: (self.ipdb.interfaces[i] .set_target('master', self['index']) .mirror_target('master', 'link')) run(nl.link, 'set', index=i, master=self['index']) else: transaction.errors.append(KeyError(i)) self['ports'].target.wait(SYNC_TIMEOUT) if not self['ports'].target.is_set(): raise CommitException('ports target is not set') # wait for proper targets on ports for i in list(added['ports']) + list(removed['ports']): port = self.ipdb.interfaces[i] target = port._local_targets['master'] target.wait(SYNC_TIMEOUT) with port._write_lock: del port._local_targets['master'] del port._local_targets['link'] if not target.is_set(): raise CommitException('master target failed') if i in added['ports']: if port.if_master != self['index']: raise CommitException('master set failed') else: if port.if_master == self['index']: raise CommitException('master unset failed') # 8<--------------------------------------------- # Interface changes request = IPLinkRequest() for key in added: if (key == 'net_ns_fd') or \ (key not in self._virtual_fields) and \ (key != 'kind'): request[key] = added[key] # apply changes only if there is something to apply if any([request[item] is not None for item in request]): request['index'] = self['index'] request['kind'] = self['kind'] if request.get('address', None) == '00:00:00:00:00:00': request.pop('address') request.pop('broadcast', None) if tuple(filter(lambda x: x[:3] == 'br_', request)): request['family'] = socket.AF_BRIDGE run(nl.link, (RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK), **request) else: run(nl.link, 'set', **request) # hardcoded pause -- if the interface was moved # across network namespaces if 'net_ns_fd' in request: while True: # wait until the interface will disappear # from the main network namespace try: for link in self.nl.get_links(self['index']): self.ipdb.interfaces._new(link) except NetlinkError as e: if e.code == errno.ENODEV: break raise except Exception: raise time.sleep(0.1) if not transaction.partial: transaction.wait_all_targets() # 8<--------------------------------------------- # IP address changes for _ in range(3): ip2add = transaction['ipaddr'] - self['ipaddr'] ip2remove = self['ipaddr'] - transaction['ipaddr'] if not ip2add and not ip2remove: break self['ipaddr'].set_target(transaction['ipaddr']) ### # Remove # # The promote_secondaries sysctl causes the kernel # to add secondary addresses back after the primary # address is removed. # # The library can not tell this from the result of # an external program. # # One simple way to work that around is to remove # secondaries first. rip = sorted(ip2remove, key=lambda x: self['ipaddr'][x]['flags'], reverse=True) # 8<-------------------------------------- for i in rip: # Ignore link-local IPv6 addresses if i[0][:4] == 'fe80' and i[1] == 64: continue # When you remove a primary IP addr, all the # subnetwork can be removed. In this case you # will fail, but it is OK, no need to roll back try: run(nl.addr, 'delete', self['index'], i[0], i[1]) except NetlinkError as x: # bypass only errno 99, # 'Cannot assign address' if x.code != errno.EADDRNOTAVAIL: raise except socket.error as x: # bypass illegal IP requests if isinstance(x.args[0], basestring) and \ x.args[0].startswith('illegal IP'): continue raise ### # Add addresses # 8<-------------------------------------- for i in ip2add: # Ignore link-local IPv6 addresses if i[0][:4] == 'fe80' and i[1] == 64: continue # Try to fetch additional address attributes try: kwarg = dict([k for k in transaction['ipaddr'][i].items() if k[0] in ('broadcast', 'anycast', 'scope')]) except KeyError: kwarg = None # feed the address to the OS run(nl.addr, 'add', self['index'], i[0], i[1], **kwarg if kwarg else {}) # 8<-------------------------------------- # bond and bridge interfaces do not send # IPv6 address updates, when are down # # beside of that, bridge interfaces are # down by default, so they never send # address updates from beginning # # so if we need, force address load # # FIXME: probably, we should handle other # types as well if self['kind'] in ('bond', 'bridge', 'veth'): for addr in self.nl.get_addr(family=socket.AF_INET6): self.ipdb.ipaddr._new(addr) # 8<-------------------------------------- self['ipaddr'].target.wait(SYNC_TIMEOUT) if self['ipaddr'].target.is_set(): break else: raise CommitException('ipaddr target is not set') # 8<--------------------------------------------- # Iterate callback chain for ch in self._commit_hooks: # An exception will rollback the transaction ch(self.dump(), snapshot.dump(), transaction.dump()) # 8<--------------------------------------------- # Interface removal if (added.get('ipdb_scope') in ('shadow', 'remove')): wd = self.ipdb.watchdog(action='RTM_DELLINK', ifname=self['ifname']) if added.get('ipdb_scope') in ('shadow', 'create'): with self._direct_state: self['ipdb_scope'] = 'locked' self.nl.link('delete', index=self['index']) wd.wait() if added.get('ipdb_scope') == 'shadow': with self._direct_state: self['ipdb_scope'] = 'shadow' if added['ipdb_scope'] == 'create': self.load_dict(transaction) if drop: self.drop(transaction.uid) return self # 8<--------------------------------------------- except Exception as e: error = e # something went wrong: roll the transaction back if commit_phase == 1: if newif: drop = False try: self.commit(transaction=init if newif else snapshot, commit_phase=2, commit_mask=commit_mask, newif=newif) except Exception as i_e: error = RuntimeError() error.cause = i_e else: # reload all the database -- it can take a long time, # but it is required since we have no idea, what is # the result of the failure links = self.nl.get_links() for link in links: self.ipdb.interfaces._new(link) links = self.nl.get_vlans() for link in links: self.ipdb.interfaces._new(link) for addr in self.nl.get_addr(): self.ipdb.ipaddr._new(addr) error.traceback = traceback.format_exc() for key in ('ipaddr', 'ports', 'vlans'): self[key].clear_target() # raise partial commit exceptions if transaction.partial and transaction.errors: error = PartialCommitException('partial commit error') # if it is not a rollback turn if drop and commit_phase == 1: # drop last transaction in any case self.drop(transaction.uid) # raise exception for failed transaction if error is not None: error.transaction = transaction raise error time.sleep(config.commit_barrier) # drop all collected errors, if any self.errors = [] return self
def create(self, kind, ifname, reuse=False, **kwarg): ''' Create an interface. Arguments 'kind' and 'ifname' are required. - kind — interface type, can be of: - bridge - bond - vlan - tun - dummy - veth - macvlan - macvtap - gre - team - ifname — interface name - reuse — if such interface exists, return it anyway Different interface kinds can require different arguments for creation. ► **veth** To properly create `veth` interface, one should specify `peer` also, since `veth` interfaces are created in pairs:: with ip.create(ifname='v1p0', kind='veth', peer='v1p1') as i: i.add_ip('10.0.0.1/24') i.add_ip('10.0.0.2/24') The code above creates two interfaces, `v1p0` and `v1p1`, and adds two addresses to `v1p0`. ► **macvlan** Macvlan interfaces act like VLANs within OS. The macvlan driver provides an ability to add several MAC addresses on one interface, where every MAC address is reflected with a virtual interface in the system. In some setups macvlan interfaces can replace bridge interfaces, providing more simple and at the same time high-performance solution:: ip.create(ifname='mvlan0', kind='macvlan', link=ip.interfaces.em1, macvlan_mode='private').commit() Several macvlan modes are available: 'private', 'vepa', 'bridge', 'passthru'. Ususally the default is 'vepa'. ► **macvtap** Almost the same as macvlan, but creates also a character tap device:: ip.create(ifname='mvtap0', kind='macvtap', link=ip.interfaces.em1, macvtap_mode='vepa').commit() Will create a device file `"/dev/tap%s" % ip.interfaces.mvtap0.index` ► **gre** Create GRE tunnel:: with ip.create(ifname='grex', kind='gre', gre_local='172.16.0.1', gre_remote='172.16.0.101', gre_ttl=16) as i: i.add_ip('192.168.0.1/24') i.up() The keyed GRE requires explicit iflags/oflags specification:: ip.create(ifname='grex', kind='gre', gre_local='172.16.0.1', gre_remote='172.16.0.101', gre_ttl=16, gre_ikey=10, gre_okey=10, gre_iflags=32, gre_oflags=32).commit() ► **vlan** VLAN interfaces require additional parameters, `vlan_id` and `link`, where `link` is a master interface to create VLAN on:: ip.create(ifname='v100', kind='vlan', link=ip.interfaces.eth0, vlan_id=100) ip.create(ifname='v100', kind='vlan', link=1, vlan_id=100) The `link` parameter should be either integer, interface id, or an interface object. VLAN id must be integer. ► **vxlan** VXLAN interfaces are like VLAN ones, but require a bit more parameters:: ip.create(ifname='vx101', kind='vxlan', vxlan_link=ip.interfaces.eth0, vxlan_id=101, vxlan_group='239.1.1.1', vxlan_ttl=16) All possible vxlan parameters are listed in the module `pyroute2.netlink.rtnl.ifinfmsg:... vxlan_data`. ► **tuntap** Possible `tuntap` keywords: - `mode` — "tun" or "tap" - `uid` — integer - `gid` — integer - `ifr` — dict of tuntap flags (see tuntapmsg.py) ''' with self.exclusive: # check for existing interface if ifname in self.interfaces: if (self.interfaces[ifname]['ipdb_scope'] == 'shadow') \ or reuse: device = self.interfaces[ifname] kwarg['kind'] = kind device.load_dict(kwarg) device.set_item('ipdb_scope', 'create') else: raise CreateException("interface %s exists" % ifname) else: device = \ self.interfaces[ifname] = \ self.iclass(ipdb=self, mode='snapshot') device.update(kwarg) if isinstance(kwarg.get('link', None), Interface): device['link'] = kwarg['link']['index'] if isinstance(kwarg.get('vxlan_link', None), Interface): device['vxlan_link'] = kwarg['vxlan_link']['index'] device['kind'] = kind device['index'] = kwarg.get('index', 0) device['ifname'] = ifname device['ipdb_scope'] = 'create' device._mode = self.mode tid = device.begin() # # All the device methods are handled via `transactional.update()` # except of the very creation. # # Commit the changes in the 'direct' mode, since this call is not # decorated. if self.mode == 'direct': device.commit(tid) return device