Skip to content

New parser: conntrack #646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
gaby opened this issue Feb 27, 2025 · 3 comments
Open

New parser: conntrack #646

gaby opened this issue Feb 27, 2025 · 3 comments

Comments

@gaby
Copy link

gaby commented Feb 27, 2025

Currently jc has support for several networking related CLI's, but it doesn't have support for conntrack. I can't find many conntrack parsers online, most of them haven't been updated in +5 years.

Package name: conntrack

Example Output: from running conntrack -E / conntrack -L and connecting/disconnecting the VM to the internet:

    [NEW] unknown  2 600 src=192.168.136.128 dst=224.0.0.22 [UNREPLIED] src=224.0.0.22 dst=192.168.136.128
    [NEW] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=36037 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.136.128 sport=53 dport=36037
 [UPDATE] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=36037 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=36037
    [NEW] tcp      6 120 SYN_SENT src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 [UNREPLIED] src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784
 [UPDATE] tcp      6 60 SYN_RECV src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784
 [UPDATE] tcp      6 432000 ESTABLISHED src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784 [ASSURED]
 [UPDATE] tcp      6 120 FIN_WAIT src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784 [ASSURED]
 [UPDATE] tcp      6 30 LAST_ACK src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784 [ASSURED]
 [UPDATE] tcp      6 120 TIME_WAIT src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784 [ASSURED]
    [NEW] udp      17 30 src=192.168.136.128 dst=224.0.0.251 sport=5353 dport=5353 [UNREPLIED] src=224.0.0.251 dst=192.168.136.128 sport=5353 dport=5353
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=56122 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=56122
    [NEW] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=45365 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.136.128 sport=53 dport=45365
    [NEW] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=48400 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.136.128 sport=53 dport=48400
 [UPDATE] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=45365 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=45365
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=56122 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=56122
 [UPDATE] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=48400 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=48400
    [NEW] tcp      6 120 SYN_SENT src=192.168.136.128 dst=34.117.59.81 sport=33192 dport=443 [UNREPLIED] src=34.117.59.81 dst=192.168.136.128 sport=443 dport=33192
    [NEW] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=42007 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.136.128 sport=53 dport=42007
 [UPDATE] tcp      6 60 SYN_RECV src=192.168.136.128 dst=34.117.59.81 sport=33192 dport=443 src=34.117.59.81 dst=192.168.136.128 sport=443 dport=33192
 [UPDATE] tcp      6 432000 ESTABLISHED src=192.168.136.128 dst=34.117.59.81 sport=33192 dport=443 src=34.117.59.81 dst=192.168.136.128 sport=443 dport=33192 [ASSURED]
 [UPDATE] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=42007 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=42007
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=51449 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=51449
    [NEW] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=42284 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.136.128 sport=53 dport=42284
    [NEW] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=59470 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.136.128 sport=53 dport=59470
 [UPDATE] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=59470 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=59470
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=51449 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=51449
 [UPDATE] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=42284 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=42284
    [NEW] tcp      6 120 SYN_SENT src=192.168.136.128 dst=185.125.188.55 sport=57236 dport=443 [UNREPLIED] src=185.125.188.55 dst=192.168.136.128 sport=443 dport=57236
 [UPDATE] tcp      6 60 SYN_RECV src=192.168.136.128 dst=185.125.188.55 sport=57236 dport=443 src=185.125.188.55 dst=192.168.136.128 sport=443 dport=57236
 [UPDATE] tcp      6 432000 ESTABLISHED src=192.168.136.128 dst=185.125.188.55 sport=57236 dport=443 src=185.125.188.55 dst=192.168.136.128 sport=443 dport=57236 [ASSURED]
    [NEW] tcp      6 120 SYN_SENT src=192.168.136.128 dst=91.189.91.96 sport=49740 dport=80 [UNREPLIED] src=91.189.91.96 dst=192.168.136.128 sport=80 dport=49740
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=49510 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=49510
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=49510 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=49510
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=44514 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=44514
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=44514 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=44514
[DESTROY] udp      17 src=192.168.136.128 dst=8.8.8.8 sport=42284 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=42284
[DESTROY] udp      17 src=127.0.0.1 dst=127.0.0.53 sport=44514 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=44514
[DESTROY] udp      17 src=127.0.0.1 dst=127.0.0.53 sport=51449 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=51449
[DESTROY] udp      17 src=192.168.136.128 dst=8.8.8.8 sport=42007 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=42007
[DESTROY] udp      17 src=192.168.136.128 dst=8.8.8.8 sport=59470 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=59470
[DESTROY] udp      17 src=127.0.0.1 dst=127.0.0.53 sport=49510 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=49510
[DESTROY] udp      17 src=127.0.0.1 dst=127.0.0.53 sport=56122 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=56122
[DESTROY] udp      17 src=192.168.136.128 dst=8.8.8.8 sport=45365 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=45365
[DESTROY] udp      17 src=192.168.136.128 dst=224.0.0.251 sport=5353 dport=5353 [UNREPLIED] src=224.0.0.251 dst=192.168.136.128 sport=5353 dport=5353
[DESTROY] udp      17 src=192.168.136.128 dst=8.8.8.8 sport=36037 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=36037
[DESTROY] udp      17 src=192.168.136.128 dst=8.8.8.8 sport=48400 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=48400
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=52269 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=52269
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=26218 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=26218
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=52269 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=52269
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=26218 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=26218
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=10945 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=10945
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=6420 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=6420
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=10945 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=10945
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=6420 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=6420
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=34417 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=34417
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=34417 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=34417
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=56346 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=56346
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=56346 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=56346
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=33056 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=33056
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=33056 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=33056
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=36275 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=36275
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=36275 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=36275
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=55493 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55493
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=8434 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=8434
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=55493 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55493
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=8434 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=8434
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=16802 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=16802
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=16802 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=16802
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=23501 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=23501
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=23501 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=23501
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=56507 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=56507
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=56507 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=56507
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=49527 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=49527
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=49527 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=49527
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=51778 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=51778
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=51778 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=51778
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=50664 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=50664
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=50664 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=50664
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=55780 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55780
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=55780 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55780
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=33568 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=33568
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=33568 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=33568
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=52673 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=52673
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=52673 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=52673
    [NEW] udp      17 29 src=127.0.0.1 dst=127.0.0.53 sport=36742 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=36742
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=36742 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=36742
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=60767 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=60767
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=60767 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=60767
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=36386 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=36386
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=36386 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=36386
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=60340 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=60340
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=60340 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=60340
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=43271 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=43271
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=43271 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=43271
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=41919 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=41919
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=41919 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=41919
    [NEW] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=55376 dport=53 [UNREPLIED] src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55376
 [UPDATE] udp      17 30 src=127.0.0.1 dst=127.0.0.53 sport=55376 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55376
[DESTROY] tcp      6 LAST_ACK src=192.168.136.128 dst=185.125.188.55 sport=57236 dport=443 src=185.125.188.55 dst=192.168.136.128 sport=443 dport=57236 [ASSURED]
@gaby
Copy link
Author

gaby commented Feb 27, 2025

This is a draft-implementation created using OpenAI - o1 pro model.

#!/usr/bin/env python3

import re

def parse_conntrack_line(line: str) -> dict:
    """
    Parse a single line of conntrack output and return a dictionary
    with keys:
       - status   (e.g. "NEW", "UPDATE", "DESTROY")
       - protocol (e.g. "udp", "tcp", "unknown")
       - proto_num (numeric protocol, e.g. 17 for UDP, 6 for TCP)
       - timeout  (integer, if present; else None)
       - state    (TCP state like SYN_SENT, ESTABLISHED, etc. if present; else None)
       - origin   (dict of key-value pairs for the original direction, e.g. src=..., dst=..., sport=..., dport=...)
       - reply    (dict of key-value pairs for the reply direction, similarly)
       - flags    (list of bracketed flags seen, e.g. ["UNREPLIED", "ASSURED"])
    Returns an empty dictionary {} if the line is not parseable.
    """

    # Strip and skip blank lines
    line = line.strip()
    if not line:
        return {}

    # Split tokens by whitespace
    tokens = line.split()

    # The first token should be something like "[NEW]" or "[UPDATE]" or "[DESTROY]"
    # If it does not look like a bracket, skip/return empty.
    if not tokens[0].startswith("[") or not tokens[0].endswith("]"):
        return {}

    status = tokens[0].strip("[]")
    # Next token is the textual protocol: "tcp", "udp", "unknown", etc.
    if len(tokens) < 2:
        return {}

    protocol = tokens[1]

    # Third token is typically the numeric protocol (e.g. "17", "6", or "2")
    # Bail out if we don't have enough tokens
    if len(tokens) < 3:
        return {}

    proto_num_str = tokens[2]
    if not proto_num_str.isdigit():
        return {}

    proto_num = int(proto_num_str)

    # Fourth token may be the timeout (if it's numeric) or might be a TCP state (e.g. SYN_SENT).
    idx = 3
    timeout = None
    state = None

    # Check if the current token is a digit => it is the timeout
    if idx < len(tokens) and tokens[idx].isdigit():
        timeout = int(tokens[idx])
        idx += 1

    # After that, we *might* have a TCP state (like SYN_SENT, SYN_RECV, ESTABLISHED, etc.)
    # or we might already be at key/value pairs: src=..., dst=..., etc.
    if idx < len(tokens) and not tokens[idx].startswith("src="):
        # Assume it's a TCP/conntrack state
        # (For UDP lines, there is often no such extra state token.)
        possible_state = tokens[idx]
        # Heuristic: if it's bracketed like "[UNREPLIED]" we treat that as a flag, not a state
        # but typically you see bracketed tokens separate from these fields.
        if not possible_state.startswith("["):
            state = possible_state
            idx += 1

    # Now we expect pairs (like src=..., dst=..., sport=..., dport=...)
    # possibly interspersed with bracketed flags such as [UNREPLIED], [ASSURED].
    # We also expect to see two "directions": "origin" and "reply".

    def parse_keyvalue_pairs(tokens, start_idx):
        """
        Parse key=value pairs from tokens starting at start_idx
        until we hit a bracket token (e.g. [UNREPLIED]) or run out of tokens
        or find a token that doesn't look like key=value.
        Returns (dict_of_parsed_pairs, new_index)
        """
        pairs = {}
        i = start_idx
        while i < len(tokens):
            # If we see a bracket, stop (those are flags or other markers)
            if tokens[i].startswith("["):
                break
            # If it's "key=value", parse it
            if '=' in tokens[i]:
                key, value = tokens[i].split('=', 1)
                pairs[key] = value
                i += 1
            else:
                # Something else that doesn't match key=value, break out
                break
        return pairs, i

    flags = []

    # Parse origin part
    origin, idx = parse_keyvalue_pairs(tokens, idx)

    # Collect bracketed flags (if any)
    while idx < len(tokens) and tokens[idx].startswith("["):
        flags.append(tokens[idx].strip("[]"))
        idx += 1

    # Parse reply part
    reply, idx = parse_keyvalue_pairs(tokens, idx)

    # Collect any trailing bracketed flags
    while idx < len(tokens) and tokens[idx].startswith("["):
        flags.append(tokens[idx].strip("[]"))
        idx += 1

    # Build the final parsed structure
    parsed = {
        "status": status,
        "protocol": protocol,
        "proto_num": proto_num,
        "timeout": timeout,
        "state": state,
        "origin": origin,
        "reply": reply,
        "flags": flags
    }
    return parsed


def parse_conntrack_output(conntrack_text: str) -> list:
    """
    Given the full text output from `conntrack -L`,
    split it into lines and parse each line into a dictionary structure.
    Return a list of the parsed results (one dict per valid line).
    """
    results = []
    for line in conntrack_text.splitlines():
        line = line.strip()
        if not line:
            continue
        parsed = parse_conntrack_line(line)
        if parsed:
            results.append(parsed)
    return results


if __name__ == "__main__":
    # Example usage:
    example_output = r"""
    [NEW] unknown  2 600 src=192.168.136.128 dst=224.0.0.22 [UNREPLIED] src=224.0.0.22 dst=192.168.136.128
    [NEW] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=36037 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.136.128 sport=53 dport=36037
 [UPDATE] udp      17 30 src=192.168.136.128 dst=8.8.8.8 sport=36037 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=36037
    [NEW] tcp      6 120 SYN_SENT src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 [UNREPLIED] src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784
 [UPDATE] tcp      6 60 SYN_RECV src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784
 [UPDATE] tcp      6 432000 ESTABLISHED src=192.168.136.128 dst=91.189.91.48 sport=38784 dport=80 src=91.189.91.48 dst=192.168.136.128 sport=80 dport=38784 [ASSURED]
 [DESTROY] udp      17 src=192.168.136.128 dst=8.8.8.8 sport=42284 dport=53 src=8.8.8.8 dst=192.168.136.128 sport=53 dport=42284
    """

    parsed_lines = parse_conntrack_output(example_output)
    for entry in parsed_lines:
        print(entry)

@kellyjonbrazil
Copy link
Owner

Thank you for the parser suggestion and the reference code! I'll start working on the next release soon and we can probably get this in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants
@gaby @kellyjonbrazil and others