Check for suspicious traffic patterns
(self, src_ip: str, dst_ip: str,
src_port: int, dst_port: int, protocol: str)
| 1054 | 20: 'ftp-data', 21: 'ftp', 22: 'ssh', 23: 'telnet', 25: 'smtp', |
| 1055 | 53: 'dns', 67: 'dhcp-server', 68: 'dhcp-client', 80: 'http', |
| 1056 | 110: 'pop3', 123: 'ntp', 135: 'msrpc', 137: 'netbios-ns', |
| 1057 | 138: 'netbios-dgm', 139: 'netbios-ssn', 143: 'imap', 161: 'snmp', |
| 1058 | 162: 'snmptrap', 389: 'ldap', 443: 'https', 445: 'smb', |
| 1059 | 465: 'smtps', 514: 'syslog', 587: 'submission', 636: 'ldaps', |
| 1060 | 993: 'imaps', 995: 'pop3s', 1433: 'mssql', 1521: 'oracle', |
| 1061 | 2375: 'docker', 2376: 'docker-tls', 2379: 'etcd', 2380: 'etcd-peer', |
| 1062 | 3306: 'mysql', 3389: 'rdp', 5432: 'postgresql', 5601: 'kibana', |
| 1063 | 5672: 'amqp', 5900: 'vnc', 5901: 'vnc', 5984: 'couchdb', |
| 1064 | 5985: 'winrm', 5986: 'winrm-ssl', 6379: 'redis', 7474: 'neo4j', |
| 1065 | 7687: 'neo4j-bolt', 8000: 'http-alt', 8008: 'http-alt', |
| 1066 | 8080: 'http-alt', 8086: 'influxdb', 8443: 'https-alt', |
| 1067 | 8888: 'http-alt', 9000: 'grafana', 9042: 'cassandra', |
| 1068 | 9090: 'prometheus', 9091: 'prometheus-push', 9092: 'kafka', |
| 1069 | 9200: 'elasticsearch', 9300: 'elasticsearch-transport', |
| 1070 | 11211: 'memcached', 15672: 'rabbitmq-mgmt', 27017: 'mongodb', |
| 1071 | 27018: 'mongodb-shard', |
| 1072 | } |
| 1073 | |
| 1074 | @staticmethod |
| 1075 | def _ip_to_pseudo_mac(ip: str) -> str: |
| 1076 | parts = ip.split('.') |
| 1077 | if len(parts) != 4: |
| 1078 | return '' |
| 1079 | try: |
| 1080 | return '00:00:' + ':'.join(f'{int(p):02x}' for p in parts) |
| 1081 | except ValueError: |
| 1082 | return '' |
| 1083 | |
| 1084 | def _passive_sync_to_db(self): |
| 1085 | """Flush passively-observed LAN hosts into the hosts DB. |
| 1086 | |
| 1087 | For each LAN IP that meets the discovery threshold (real MAC seen via |
| 1088 | ARP, or >= PASSIVE_MIN_PACKETS observed), upsert into `hosts`. Merges |
| 1089 | listening ports with any existing port list — never replaces. |
| 1090 | """ |
| 1091 | db = getattr(self.shared_data, 'db', None) if self.shared_data else None |
| 1092 | if not db or not hasattr(db, 'upsert_host'): |
| 1093 | return |
| 1094 | if not self._lan_networks: |
| 1095 | return |
| 1096 | |
| 1097 | with self._lock: |
| 1098 | snapshot: List[Tuple[str, str, set]] = [] |
| 1099 | for ip, stats in self.host_stats.items(): |
| 1100 | if not self._is_lan_ip(ip): |
| 1101 | continue |
| 1102 | if ip in self._local_ips: |
| 1103 | continue |
| 1104 | mac = self._mac_by_ip.get(ip) or stats.mac or '' |
| 1105 | if not mac and stats.total_packets < self.PASSIVE_MIN_PACKETS: |
| 1106 | continue |
| 1107 | listening = set(self._listening_ports.get(ip, set())) |
| 1108 | snapshot.append((ip, mac, listening)) |
| 1109 | |
| 1110 | for ip, mac, listening in snapshot: |
| 1111 | try: |
| 1112 | self._upsert_passive_host(db, ip, mac, listening) |
| 1113 | except Exception as exc: |