Reassembles IRC traffic into sessions and exposes summaries.
| 142 | |
| 143 | |
| 144 | class IRCDPIParser: |
| 145 | """Reassembles IRC traffic into sessions and exposes summaries.""" |
| 146 | |
| 147 | # Default IRC ports we attentively listen on. |
| 148 | DEFAULT_PORTS = frozenset({6667, 6668, 6669, 6697, 7000, 7001}) |
| 149 | |
| 150 | MAX_SESSIONS = 1000 |
| 151 | MAX_BUFFER_PER_FLOW = 16 * 1024 # drop oversized garbage rather than grow forever |
| 152 | |
| 153 | def __init__( |
| 154 | self, |
| 155 | interface: str = 'any', |
| 156 | ports: Optional[frozenset] = None, |
| 157 | on_session_event: Optional[Callable[[IRCSession, IRCMessage], None]] = None, |
| 158 | tshark_path: Optional[str] = None, |
| 159 | ) -> None: |
| 160 | self.interface = interface |
| 161 | self.ports = ports if ports is not None else self.DEFAULT_PORTS |
| 162 | self._on_event = on_session_event |
| 163 | self._tshark = tshark_path or shutil.which('tshark') |
| 164 | |
| 165 | self._sessions: Dict[Tuple[str, str, int], IRCSession] = {} |
| 166 | # Per-flow line buffer keyed by (src, dst, sport, dport) |
| 167 | self._buffers: Dict[Tuple[str, str, int, int], bytearray] = {} |
| 168 | self._lock = threading.Lock() |
| 169 | |
| 170 | self._proc: Optional[subprocess.Popen] = None |
| 171 | self._thread: Optional[threading.Thread] = None |
| 172 | self._running = False |
| 173 | |
| 174 | # ------------------------------------------------------------------ # |
| 175 | # Lifecycle |
| 176 | # ------------------------------------------------------------------ # |
| 177 | |
| 178 | def is_available(self) -> bool: |
| 179 | return bool(self._tshark) |
| 180 | |
| 181 | def start(self) -> bool: |
| 182 | if not self.is_available(): |
| 183 | logger.warning("tshark not found; IRC DPI disabled.") |
| 184 | return False |
| 185 | if self._running: |
| 186 | return True |
| 187 | self._running = True |
| 188 | self._thread = threading.Thread(target=self._capture_loop, |
| 189 | name="IRCDPI", daemon=True) |
| 190 | self._thread.start() |
| 191 | return True |
| 192 | |
| 193 | def stop(self) -> None: |
| 194 | self._running = False |
| 195 | proc = self._proc |
| 196 | if proc and proc.poll() is None: |
| 197 | try: |
| 198 | proc.terminate() |
| 199 | proc.wait(timeout=3) |
| 200 | except Exception: |
| 201 | try: |
no outgoing calls