@@ -40,10 +40,11 @@ class PassiveNetworkDiscovery(discovery.DiscoveryController):
4040
4141 family = "ipv4"
4242
43- def __init__ (self , state , publisher , * , interface = None ):
43+ def __init__ (self , state , publisher , * , interface = None , subnet_filter = None ):
4444
4545 self .queue = queue .SimpleQueue ()
46- self .interface = None
46+ self .interface = interface
47+ self .subnet_filter = subnet_filter
4748 self .addresses_seen = set ()
4849 self .device_records = set ()
4950 self .devices_records_published = set ()
@@ -90,7 +91,6 @@ def start_discovery(self):
9091 self .devices_records_published .clear ()
9192 self .device_records .clear ()
9293 self .addresses_seen .clear ()
93-
9494 # Turn-on the queue worker to consume sniffed packets
9595 self .queue_thread = threading .Thread (
9696 target = self .queue_worker , args = [], daemon = True
@@ -103,8 +103,34 @@ def start_discovery(self):
103103 )
104104 self .service_thread .start ()
105105
106+ if subnet := self .subnet_filter :
107+ try :
108+ iface = ipaddress .ip_interface (subnet )
109+ network = iface .network
110+ bpf_filter = f"ip and src net { network } and (dst net { network } or broadcast or multicast)"
111+ # Exclude network and broadcast addresses from being discovered as source
112+ bpf_filter += f" and not src host { network .network_address } "
113+ if network .broadcast_address :
114+ bpf_filter += f" and not src host { network .broadcast_address } "
115+ # Assume first host is gateway and exclude it
116+ if first_host := next (network .hosts (), None ):
117+ bpf_filter += f" and not src host { first_host } "
118+ # Exclude our own IP if provided in the subnet filter
119+ if iface .ip != network .network_address :
120+ bpf_filter += f" and not src host { iface .ip } "
121+ except ValueError :
122+ logging .warning ("Invalid subnet filter: %s" , subnet )
123+ bpf_filter = f"ip and src net { subnet } and (dst net { subnet } or broadcast or multicast)"
124+ else :
125+ bpf_filter = PRIVATE_IP_BPF_FILTER
126+ logging .info ("Using BPF filter: %s" , bpf_filter )
127+
106128 self .sniffer = scapy .sendrecv .AsyncSniffer (
107- prn = self .queue .put , store = False , iface = self .interface , started_callback = self .scapy_is_go , filter = PRIVATE_IP_BPF_FILTER
129+ prn = self .queue .put ,
130+ store = False ,
131+ iface = self .interface ,
132+ started_callback = self .scapy_is_go ,
133+ filter = bpf_filter ,
108134 )
109135
110136 self .sniffer .start ()
@@ -175,8 +201,10 @@ def queue_worker(self):
175201 if scapy .layers .inet .IP in item :
176202 self .ip_packets_seen += 1
177203
178- # A packet "sees" two devices - the source and the destination
179- for x in ["src" , "dst" ]:
204+ # A packet "sees" two devices - the source and the destination.
205+ # However, we only care about the source, as the destination may
206+ # not be online (e.g. if we are pinging it).
207+ for x in ["src" ]:
180208
181209 if x == "src" :
182210 if scapy .layers .inet .UDP in item and (
0 commit comments