def proxy_linkinfo(data, nl): marshal = MarshalRtnl() inbox = marshal.parse(data) data = b'' for msg in inbox: if msg['event'] == 'NLMSG_ERROR': data += msg.data continue # Sysfs operations can require root permissions, # but the script can be run under a normal user # Bug-Url: https://github.com/svinota/pyroute2/issues/113 try: compat_fix_attrs(msg, nl) except OSError: # We can safely ignore here any OSError. # In the worst case, we just return what we have got # from the kernel via netlink pass msg.reset() msg.encode() data += msg.data return {'verdict': 'forward', 'data': data}
class IPRoute(object): def __init__(self, *argv, **kwarg): self._ifc = Ifconfig() self._arp = ARP() self._route = Route() self.marshal = MarshalRtnl() send_ns = Namespace(self, { 'addr_pool': AddrPool(0x10000, 0x1ffff), 'monitor': False }) self._sproxy = NetlinkProxy(policy='return', nl=send_ns) self._mon_th = None self._rtm = None self._pfdr, self._pfdw = os.pipe() # notify external poll/select self._ctlr, self._ctlw = os.pipe() # notify monitoring thread self._outq = queue.Queue() self._system_lock = threading.Lock() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def close(self): with self._system_lock: if self._mon_th is not None: os.write(self._ctlw, b'\0') self._mon_th.join() self._rtm.close() for ep in (self._pfdr, self._pfdw, self._ctlr, self._ctlw): try: os.close(ep) except OSError: pass def bind(self, *argv, **kwarg): with self._system_lock: if self._mon_th is not None: return self._mon_th = threading.Thread(target=self._monitor_thread, name='PF_ROUTE monitoring') self._mon_th.setDaemon(True) self._mon_th.start() def _monitor_thread(self): # Monitoring thread to convert arriving PF_ROUTE data into # the netlink format, enqueue it and notify poll/select. self._rtm = RTMSocket(output='netlink') inputs = [self._rtm.fileno(), self._ctlr] outputs = [] while True: try: events, _, _ = select.select(inputs, outputs, inputs) except: continue for fd in events: if fd == self._ctlr: # Main thread <-> monitor thread protocol is # pretty simple: discard the data and terminate # the monitor thread. os.read(self._ctlr, 1) return else: # Read the data from the socket and queue it msg = self._rtm.get() if msg is not None: msg.encode() self._outq.put(msg.data) # Notify external poll/select os.write(self._pfdw, b'\0') def fileno(self): # Every time when some new data arrives, one should write # into self._pfdw one byte to kick possible poll/select. # # Resp. recv() discards one byte from self._pfdr each call. return self._pfdr def get(self): data = self.recv() return self.marshal.parse(data) def recv(self, bufsize=None): os.read(self._pfdr, 1) return self._outq.get() def getsockopt(self, *argv, **kwarg): return 1024 * 1024 def sendto_gate(self, msg, addr): # # handle incoming netlink requests # # sendto_gate() receives single RTNL messages as objects # cmd = msg['header']['type'] flags = msg['header']['flags'] seq = msg['header']['sequence_number'] # work only on dump requests for now if flags != NLM_F_REQUEST | NLM_F_DUMP: return # if cmd == RTM_GETLINK: rtype = RTM_NEWLINK ret = self.get_links() elif cmd == RTM_GETADDR: rtype = RTM_NEWADDR ret = self.get_addr() elif cmd == RTM_GETROUTE: rtype = RTM_NEWROUTE ret = self.get_routes() elif cmd == RTM_GETNEIGH: rtype = RTM_NEWNEIGH ret = self.get_neighbours() # # set response type and finalize the message for r in ret: r['header']['type'] = rtype r['header']['flags'] = NLM_F_MULTI r['header']['sequence_number'] = seq # r = type(msg)() r['header']['type'] = NLMSG_DONE r['header']['sequence_number'] = seq ret.append(r) data = b'' for r in ret: r.encode() data += r.data self._outq.put(data) os.write(self._pfdw, b'\0') def get_links(self, *argv, **kwarg): ret = [] data = self._ifc.run() parsed = self._ifc.parse(data) for name, spec in parsed['links'].items(): msg = ifinfmsg().load(spec) del msg['value'] ret.append(msg) return ret def get_addr(self, *argv, **kwarg): ret = [] data = self._ifc.run() parsed = self._ifc.parse(data) for name, specs in parsed['addrs'].items(): for spec in specs: msg = ifaddrmsg().load(spec) del msg['value'] ret.append(msg) return ret def get_neighbours(self, *argv, **kwarg): ifc = self._ifc.parse(self._ifc.run()) arp = self._arp.parse(self._arp.run()) ret = [] for spec in arp: spec['ifindex'] = ifc['links'][spec['ifname']]['index'] msg = ndmsg().load(spec) del msg['value'] ret.append(msg) return ret def get_routes(self, *argv, **kwarg): ifc = self._ifc.parse(self._ifc.run()) rta = self._route.parse(self._route.run()) ret = [] for spec in rta: idx = ifc['links'][spec['ifname']]['index'] spec['attrs'].append(['RTA_OIF', idx]) msg = rtmsg().load(spec) del msg['value'] ret.append(msg) return ret
class IPRSocketMixin(object): def __init__(self, *argv, **kwarg): if 'family' in kwarg: kwarg.pop('family') super(IPRSocketMixin, self).__init__(NETLINK_ROUTE, *argv[1:], **kwarg) self.marshal = MarshalRtnl() self._s_channel = None if sys.platform.startswith('linux'): self._gate = self._gate_linux self.sendto_gate = self._gate_linux send_ns = Namespace(self, {'addr_pool': AddrPool(0x10000, 0x1ffff), 'monitor': False}) self._sproxy = NetlinkProxy(policy='return', nl=send_ns) self._sproxy.pmap = {rtnl.RTM_NEWLINK: proxy_newlink, rtnl.RTM_SETLINK: proxy_setlink} if config.kernel < [3, 3, 0]: self._recv_ns = Namespace(self, {'addr_pool': AddrPool(0x20000, 0x2ffff), 'monitor': False}) self._sproxy.pmap[rtnl.RTM_DELLINK] = proxy_dellink # inject proxy hooks into recv() and... self.__recv = self._recv self._recv = self._p_recv # ... recv_into() self._recv_ft = self.recv_ft self.recv_ft = self._p_recv_ft def bind(self, groups=rtnl.RTMGRP_DEFAULTS, **kwarg): super(IPRSocketMixin, self).bind(groups, **kwarg) def _gate_linux(self, msg, addr): msg.reset() msg.encode() ret = self._sproxy.handle(msg) if ret is not None: if ret['verdict'] == 'forward': return self._sendto(ret['data'], addr) elif ret['verdict'] in ('return', 'error'): if self._s_channel is not None: return self._s_channel.send(ret['data']) else: msgs = self.marshal.parse(ret['data']) for msg in msgs: seq = msg['header']['sequence_number'] if seq in self.backlog: self.backlog[seq].append(msg) else: self.backlog[seq] = [msg] return len(ret['data']) else: ValueError('Incorrect verdict') return self._sendto(msg.data, addr) def _p_recv_ft(self, bufsize, flags=0): data = self._recv_ft(bufsize, flags) ret = proxy_linkinfo(data, self._recv_ns) if ret is not None: if ret['verdict'] in ('forward', 'error'): return ret['data'] else: ValueError('Incorrect verdict') return data def _p_recv(self, bufsize, flags=0): data = self.__recv(bufsize, flags) ret = proxy_linkinfo(data, self._recv_ns) if ret is not None: if ret['verdict'] in ('forward', 'error'): return ret['data'] else: ValueError('Incorrect verdict') return data
class IPRoute(object): def __init__(self, *argv, **kwarg): if 'ssh' in kwarg: self._ssh = ['ssh', kwarg.pop('ssh')] else: self._ssh = [] async_qsize = kwarg.get('async_qsize') self._ifc = Ifconfig(cmd=self._ssh + ['ifconfig', '-a']) self._arp = ARP(cmd=self._ssh + ['arp', '-an']) self._route = Route(cmd=self._ssh + ['netstat', '-rn']) self.marshal = MarshalRtnl() send_ns = Namespace(self, {'addr_pool': AddrPool(0x10000, 0x1ffff), 'monitor': False}) self._sproxy = NetlinkProxy(policy='return', nl=send_ns) self._mon_th = None self._rtm = None self._brd_socket = None self._pfdr, self._pfdw = os.pipe() # notify external poll/select self._ctlr, self._ctlw = os.pipe() # notify monitoring thread self._outq = queue.Queue(maxsize=async_qsize or config.async_qsize) self._system_lock = threading.Lock() self.closed = threading.Event() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def clone(self): return self def close(self, code=errno.ECONNRESET): with self._system_lock: if self.closed.is_set(): return if self._mon_th is not None: os.write(self._ctlw, b'\0') self._mon_th.join() self._rtm.close() if code > 0: self._outq.put(struct.pack('IHHQIQQ', 28, 2, 0, 0, code, 0, 0)) os.write(self._pfdw, b'\0') for ep in (self._pfdr, self._pfdw, self._ctlr, self._ctlw): try: os.close(ep) except OSError: pass self.closed.set() def bind(self, *argv, **kwarg): with self._system_lock: if self._mon_th is not None: return if self._ssh: return self._mon_th = threading.Thread(target=self._monitor_thread, name='PF_ROUTE monitoring') self._mon_th.setDaemon(True) self._mon_th.start() def _monitor_thread(self): # Monitoring thread to convert arriving PF_ROUTE data into # the netlink format, enqueue it and notify poll/select. self._rtm = RTMSocket(output='netlink') inputs = [self._rtm.fileno(), self._ctlr] outputs = [] while True: try: events, _, _ = select.select(inputs, outputs, inputs) except: continue for fd in events: if fd == self._ctlr: # Main thread <-> monitor thread protocol is # pretty simple: discard the data and terminate # the monitor thread. os.read(self._ctlr, 1) return else: # Read the data from the socket and queue it msg = self._rtm.get() if msg is not None: msg.encode() self._outq.put(msg.data) # Notify external poll/select os.write(self._pfdw, b'\0') def fileno(self): # Every time when some new data arrives, one should write # into self._pfdw one byte to kick possible poll/select. # # Resp. recv() discards one byte from self._pfdr each call. return self._pfdr def get(self): data = self.recv() return self.marshal.parse(data) def recv(self, bufsize=None): os.read(self._pfdr, 1) return self._outq.get() def getsockopt(self, *argv, **kwarg): return 1024 * 1024 def sendto_gate(self, msg, addr): # # handle incoming netlink requests # # sendto_gate() receives single RTNL messages as objects # cmd = msg['header']['type'] flags = msg['header']['flags'] seq = msg['header']['sequence_number'] # work only on dump requests for now if flags != NLM_F_REQUEST | NLM_F_DUMP: return # if cmd == RTM_GETLINK: rtype = RTM_NEWLINK ret = self.get_links() elif cmd == RTM_GETADDR: rtype = RTM_NEWADDR ret = self.get_addr() elif cmd == RTM_GETROUTE: rtype = RTM_NEWROUTE ret = self.get_routes() elif cmd == RTM_GETNEIGH: rtype = RTM_NEWNEIGH ret = self.get_neighbours() # # set response type and finalize the message for r in ret: r['header']['type'] = rtype r['header']['flags'] = NLM_F_MULTI r['header']['sequence_number'] = seq # r = type(msg)() r['header']['type'] = NLMSG_DONE r['header']['sequence_number'] = seq ret.append(r) data = b'' for r in ret: r.encode() data += r.data self._outq.put(data) os.write(self._pfdw, b'\0') def get_links(self, *argv, **kwarg): ret = [] data = self._ifc.run() parsed = self._ifc.parse(data) for name, spec in parsed['links'].items(): msg = ifinfmsg().load(spec) msg['header']['type'] = RTM_NEWLINK del msg['value'] flags = msg['flags'] new_flags = 0 for value, name in IFF_VALUES.items(): if value & flags and name in IFF_NAMES: new_flags |= IFF_NAMES[name] msg['flags'] = new_flags ret.append(msg) return ret def get_addr(self, *argv, **kwarg): ret = [] data = self._ifc.run() parsed = self._ifc.parse(data) for name, specs in parsed['addrs'].items(): for spec in specs: msg = ifaddrmsg().load(spec) msg['header']['type'] = RTM_NEWADDR del msg['value'] ret.append(msg) return ret def get_neighbours(self, *argv, **kwarg): ifc = self._ifc.parse(self._ifc.run()) arp = self._arp.parse(self._arp.run()) ret = [] for spec in arp: if spec['ifname'] not in ifc['links']: continue spec['ifindex'] = ifc['links'][spec['ifname']]['index'] msg = ndmsg().load(spec) msg['header']['type'] = RTM_NEWNEIGH del msg['value'] ret.append(msg) return ret def get_routes(self, *argv, **kwarg): ifc = self._ifc.parse(self._ifc.run()) rta = self._route.parse(self._route.run()) ret = [] for spec in rta: if spec['ifname'] not in ifc['links']: continue idx = ifc['links'][spec['ifname']]['index'] spec['attrs'].append(['RTA_OIF', idx]) msg = rtmsg().load(spec) msg['header']['type'] = RTM_NEWROUTE del msg['value'] ret.append(msg) return ret
def main(args): """Parse arguments, read the pcap and parse nl packets with pyroute2""" psr = ArgumentParser(description=__doc__.splitlines()[0]) psr.add_argument("pcap", type=FileType("rb"), help="The pcap file to read, or - for stdin") psr.add_argument("-p", "--pprint", help="use pprint() for pretty-printing messages instead " "of the builtin tree-like display", default=False, action="store_true") psr.add_argument("-l", "--log-level", choices=LOG_LEVELS, default="info", help="Log level. 'info' (the default) prints a header " "for each packet, 'debug' prints information about " "skipped packets, 'warn' only prints packet or " "message decoding errors.") psr.add_argument("filter", nargs="*", help="Only display messages of this type. " "Can be specified multiple times.") args = psr.parse_args(args) for i in args.filter: if i not in MSG_TYPES: psr.error("Invalid filter '%s' (choose from %s)" % (i, MSG_TYPES)) LOG.setLevel(args.log_level.upper()) # Open the pcap file (read its header) try: pcap_file = NLPcap(args.pcap) except PcapError as pce: psr.error("%r: %s" % (args.pcap.name, pce)) # Use the built in marshal for decoding marshal = MarshalRtnl() # The function that will be used for printing print_func = pprint if args.pprint else nl_pprint # Loop over the pcap file for packet in pcap_file: # Parse the packet content and display all contained messages try: messages = marshal.parse(packet) except StructError: LOG.warn("[packet %d] could not parse %r", pcap_file.pkt_count, packet) continue for msg_num, msg in enumerate(messages, start=1): msg_type = MSG_MAP.get(msg["header"]["type"], "unknown type") if args.filter and msg_type not in args.filter: continue LOG.info("[packet %d] message %d (%s)", pcap_file.pkt_count, msg_num, msg_type) print_func(msg) return 0