def main(argv=sys.argv): global program_name program_name = os.path.basename(sys.argv[0]) try: opts, args = getopt.getopt(argv[1:], "i:sptn") except getopt.GetoptError: usage() device = None doselect = False dopoll = False mechanism = None dotimeout = False dononblock = False for opt, optarg in opts: if opt == '-i': device = optarg.encode("utf-8") elif opt == '-s': doselect = True mechanism = "select() and pcap_dispatch()" elif opt == '-p': dopoll = True mechanism = "poll() and pcap_dispatch()" elif opt == '-t': dotimeout = True elif opt == '-n': dononblock = True else: usage() expression = args if doselect and dopoll: print("selpolltest: choose select (-s) or poll (-p), but not both", file=sys.stderr) return 1 if dotimeout and not doselect and not dopoll: print("selpolltest: timeout (-t) requires select (-s) or poll (-p)", file=sys.stderr) return 1 ebuf = ct.create_string_buffer(pcap.PCAP_ERRBUF_SIZE) if device is None: devlist = ct.POINTER(pcap.pcap_if_t)() if pcap.findalldevs(ct.byref(devlist), ebuf) == -1: error("{!s}", ebuf.value.decode("utf-8", "ignore")) if not devlist: error("no interfaces available for capture") device = devlist[0].name pcap.freealldevs(devlist) ebuf.value = b"" pd = pcap.open_live(device, 65535, 0, 1000, ebuf) if not pd: error("{!s}", ebuf.value.decode("utf-8", "ignore")) elif ebuf.value: warning("{!s}", ebuf.value.decode("utf-8", "ignore")) localnet = pcap.bpf_u_int32() netmask = pcap.bpf_u_int32() if pcap.lookupnet(device, ct.byref(localnet), ct.byref(netmask), ebuf) < 0: localnet = pcap.bpf_u_int32(0) netmask = pcap.bpf_u_int32(0) warning("{!s}", ebuf.value.decode("utf-8", "ignore")) fcode = pcap.bpf_program() cmdbuf = " ".join(expression).encode("utf-8") if pcap.compile(pd, ct.byref(fcode), cmdbuf, 1, netmask) < 0: error("{!s}", pcap.geterr(pd).decode("utf-8", "ignore")) if pcap.setfilter(pd, ct.byref(fcode)) < 0: error("{!s}", pcap.geterr(pd).decode("utf-8", "ignore")) if doselect or dopoll: # We need either an FD on which to do select()/poll() # or, if there isn't one, a timeout to use in select()/ # poll(). try: selectable_fd = pcap.get_selectable_fd(pd) except AttributeError: error("pcap.get_selectable_fd is not available on this platform") if selectable_fd == -1: print("Listening on {!s}, using {}, with a timeout".format( device.decode("utf-8"), mechanism)) try: required_timeout = pcap.get_required_select_timeout(pd) except AttributeError: error("pcap.get_required_select_timeout is not available " "on this platform") if not required_timeout: error("select()/poll() isn't supported on {!s}, " "even with a timeout", device.decode("utf-8")) required_timeout = required_timeout[0] # As we won't be notified by select() or poll() # that a read can be done, we'll have to periodically # try reading from the device every time the required # timeout expires, and we don't want those attempts # to block if nothing has arrived in that interval, # so we want to force non-blocking mode. dononblock = True else: print("Listening on {!s}, using {}".format( device.decode("utf-8"), mechanism)) required_timeout = None else: print("Listening on {!s}, using pcap_dispatch()".format( device.decode("utf-8"))) if dononblock: if pcap.setnonblock(pd, 1, ebuf) == -1: error("pcap.setnonblock failed: {!s}", ebuf.value.decode("utf-8", "ignore")) status = 0 if doselect: while True: try: if dotimeout: seltimeout = (0 + (required_timeout.tv_usec if required_timeout is not None and required_timeout.tv_usec < 1000 else 1000) / 1000000.0) rfds, wfds, efds = select.select([selectable_fd], [], [selectable_fd], seltimeout) elif required_timeout is not None: seltimeout = (required_timeout.tv_sec + required_timeout.tv_usec / 1000000.0) rfds, wfds, efds = select.select([selectable_fd], [], [selectable_fd], seltimeout) else: rfds, wfds, efds = select.select([selectable_fd], [], [selectable_fd]) except select.error as exc: print("Select returns error ({})".format(exc.args[1])) else: if selectable_fd == -1: if status != 0: print("Select returned a descriptor") else: print("Select timed out: " if not rfds and not wfds and not efds else "Select returned a descriptor: ", end="") print("readable, " if selectable_fd in rfds else "not readable, ", end="") print("exceptional condition" if selectable_fd in efds else "no exceptional condition", end="") print() packet_count = ct.c_int(0) status = pcap.dispatch(pd, -1, countme, ct.cast(ct.pointer(packet_count), ct.POINTER(ct.c_ubyte))) if status < 0: break # Don't report this if we're using a # required timeout and we got no packets, # because that could be a very short timeout, # and we don't want to spam the user with # a ton of "no packets" reports. if (status != 0 or packet_count.value != 0 or required_timeout is not None): print("{:d} packets seen, {:d} packets counted after " "select returns".format(status, packet_count.value)) elif dopoll: while True: poller = select.poll() poller.register(selectable_fd, select.POLLIN) if dotimeout: polltimeout = 1 elif (required_timeout is not None and required_timeout.tv_usec >= 1000): polltimeout = required_timeout.tv_usec // 1000 else: polltimeout = None try: events = poller.poll(polltimeout) except select.error as exc: print("Poll returns error ({})".format(exc.args[1])) else: if selectable_fd == -1: if status != 0: print("Poll returned a descriptor") else: if not events: print("Poll timed out") else: event = events[0][1] print("Poll returned a descriptor: ", end="") print("readable, " if event & select.POLLIN else "not readable, ", end="") print("exceptional condition, " if event & select.POLLERR else "no exceptional condition, ", end="") print("disconnect, " if event & select.POLLHUP else "no disconnect, ", end="") print("invalid" if event & select.POLLNVAL else "not invalid", end="") print() packet_count = ct.c_int(0) status = pcap.dispatch(pd, -1, countme, ct.cast(ct.pointer(packet_count), ct.POINTER(ct.c_ubyte))) if status < 0: break # Don't report this if we're using a # required timeout and we got no packets, # because that could be a very short timeout, # and we don't want to spam the user with # a ton of "no packets" reports. if (status != 0 or packet_count.value != 0 or required_timeout is not None): print("{:d} packets seen, {:d} packets counted after " "poll returns".format(status, packet_count.value)) else: while True: packet_count = ct.c_int(0) status = pcap.dispatch(pd, -1, countme, ct.cast(ct.pointer(packet_count), ct.POINTER(ct.c_ubyte))) if status < 0: break print("{:d} packets seen, {:d} packets counted after " "pcap.dispatch returns".format(status, packet_count.value)) if status == -2: # We got interrupted, so perhaps we didn't manage to finish a # line we were printing. Print an extra newline, just in case. print() sys.stdout.flush() if status == -1: # Error. Report it. print("{}: pcap.loop: {!s}".format(program_name, pcap.geterr(pd).decode("utf-8", "ignore")), file=sys.stderr) pcap.freecode(ct.byref(fcode)) pcap.close(pd) return 1 if status == -1 else 0
def _thread_worker(self, name: str, pd: object, decoder: LinkLayerPacket.Decoder) -> None: import libpcap as pcap assert isinstance(pd, ctypes.POINTER(pcap.pcap_t)) try: _logger.debug("%r: Worker thread for %r is started: %s", self, name, threading.current_thread()) # noinspection PyTypeChecker @pcap.pcap_handler # type: ignore def proxy(_: object, header: ctypes.Structure, packet: typing.Any) -> None: # Parse the header, extract the timestamp and the packet length. header = header.contents ts_ns = (header.ts.tv_sec * 1_000_000 + header.ts.tv_usec) * 1000 ts = Timestamp(system_ns=ts_ns, monotonic_ns=time.monotonic_ns()) length, real_length = header.caplen, header.len _logger.debug("%r: CAPTURED PACKET ts=%s dev=%r len=%d bytes", self, ts, name, length) if real_length != length: # In theory, this should never occur because we use a huge capture buffer. # On Windows, however, when using Npcap v0.96, the captured length is (always?) reported to be # 32 bytes shorter than the real length, despite the fact that the packet is not truncated. _logger.debug( "%r: Length mismatch in a packet captured from %r: real %r bytes, captured %r bytes", self, name, real_length, length, ) # Create a copy of the payload. This is required per the libpcap API contract -- it says that the # memory is invalidated upon return from the callback. packet = memoryview(ctypes.cast(packet, ctypes.POINTER(ctypes.c_ubyte * length))[0]).tobytes() llp = decoder(memoryview(packet)) if llp is None: if _logger.isEnabledFor(logging.INFO): _logger.info( "%r: Link-layer packet of %d bytes captured from %r at %s could not be parsed. " "The header is: %s", self, len(packet), name, ts, packet[:32].hex(), ) else: self._callback(LinkLayerCapture(timestamp=ts, packet=llp, device_name=name)) packets_per_batch = 100 while self._keep_going: err = pcap.dispatch(pd, packets_per_batch, proxy, ctypes.POINTER(ctypes.c_ubyte)()) if err < 0: # Negative values represent errors, otherwise it's the number of packets processed. if self._keep_going: _logger.critical( "%r: Worker thread for %r has failed with error %s; %s", self, name, err, pcap.geterr(pd).decode(), ) else: _logger.debug( "%r: Error %r in worker thread for %r ignored because it is commanded to stop", self, err, name, ) break except Exception as ex: _logger.exception("%r: Unhandled exception in worker thread for %r; stopping: %r", self, name, ex) finally: # BEWARE: pcap_close() is not idempotent! Second close causes a heap corruption. *sigh* pcap.close(pd) _logger.debug("%r: Worker thread for %r is being terminated", self, name)
def main(argv): global program_name program_name = os.path.basename(sys.argv[0]) try: opts, args = getopt.getopt(argv[1:], "i:mnt:") except getopt.GetoptError: usage() device = None immediate = False nonblock = 0 timeout = 1000 for opt, optarg in opts: if opt == '-i': device = optarg.encode("utf-8") elif opt == '-m': immediate = True elif opt == '-n': nonblock = 1 elif opt == '-t': try: timeout = int(optarg) except: error('Timeout value "{}" is not a number', optarg) if timeout < 0: error("Timeout value {:d} is negative", timeout) if timeout > INT_MAX: error("Timeout value {:d} is too large (> {:d})", timeout, INT_MAX) else: usage() expression = args ebuf = ct.create_string_buffer(pcap.PCAP_ERRBUF_SIZE) if device is None: devlist = ct.POINTER(pcap.pcap_if_t)() if pcap.findalldevs(ct.byref(devlist), ebuf) == -1: error("{!s}", ebuf.value.decode("utf-8", "ignore")) if not devlist: error("no interfaces available for capture") device = devlist[0].name pcap.freealldevs(devlist) ebuf.value = b"" pd = pcap.create(device, ebuf) if not pd: error("{!s}", ebuf.value.decode("utf-8", "ignore")) status = pcap.set_snaplen(pd, 65535) if status != 0: error("{!s}: pcap.set_snaplen failed: {!s}", device.decode("utf-8"), statustostr(status).decode("utf-8", "ignore")); if immediate: try: status = pcap.set_immediate_mode(pd, 1) except AttributeError: error("pcap.set_immediate_mode is not available on this platform") if status != 0: error("{!s}: pcap.set_immediate_mode failed: {!s}", device.decode("utf-8"), statustostr(status).decode("utf-8", "ignore")); status = pcap.set_timeout(pd, timeout) if status != 0: error("{!s}: pcap.set_timeout failed: {!s}", device.decode("utf-8"), statustostr(status).decode("utf-8", "ignore")); status = pcap.activate(pd) if status < 0: # pcap.activate() failed. error("{!s}: {!s}\n({!s})", device.decode("utf-8"), statustostr(status).decode("utf-8", "ignore"), pcap.geterr(pd).decode("utf-8", "ignore")) elif status > 0: # pcap.activate() succeeded, but it's warning us # of a problem it had. warning("{!s}: {!s}\n({!s})", device.decode("utf-8"), statustostr(status).decode("utf-8", "ignore"), pcap.geterr(pd).decode("utf-8", "ignore")) localnet = pcap.bpf_u_int32() netmask = pcap.bpf_u_int32() if pcap.lookupnet(device, ct.byref(localnet), ct.byref(netmask), ebuf) < 0: localnet = pcap.bpf_u_int32(0) netmask = pcap.bpf_u_int32(0) warning("{!s}", ebuf.value.decode("utf-8", "ignore")) fcode = pcap.bpf_program() cmdbuf = " ".join(expression).encode("utf-8") if pcap.compile(pd, ct.byref(fcode), cmdbuf, 1, netmask) < 0: error("{!s}", pcap.geterr(pd).decode("utf-8", "ignore")) if pcap.setfilter(pd, ct.byref(fcode)) < 0: error("{!s}", pcap.geterr(pd).decode("utf-8", "ignore")) if pcap.setnonblock(pd, nonblock, ebuf) == -1: error("pcap.setnonblock failed: {!s}", ebuf.value.decode("utf-8", "ignore")) print("Listening on {!s}".format(device.decode("utf-8"))) while True: packet_count = ct.c_int(0) status = pcap.dispatch(pd, -1, countme, ct.cast(ct.pointer(packet_count), ct.POINTER(ct.c_ubyte))) if status < 0: break if status != 0: print("{:d} packets seen, {:d} packets counted after " "pcap.dispatch returns".format(status, packet_count.value)) if status == -2: # We got interrupted, so perhaps we didn't manage to finish a # line we were printing. Print an extra newline, just in case. print() sys.stdout.flush() if status == -1: # Error. Report it. print("{}: pcap.loop: {!s}".format(program_name, pcap.geterr(pd).decode("utf-8", "ignore")), file=sys.stderr) pcap.freecode(ct.byref(fcode)) pcap.close(pd) return 1 if status == -1 else 0