justniffer

Justniffer Just A Network TCP Packet Sniffer. Justniffer is a network protocol analyzer that captures network traffic and produces logs in a customized way, can emulate web server log files, track response times and extract all "intercepted" files from the HTTP traffic

View on GitHub

Extending Justniffer

HOME MAN

Justniffer can be extended using external scripts in three different ways:

Each method offers flexibility depending on the level of customization and control required.


🔹 Extending with a Bash Script

Justniffer allows the execution of external scripts using the -e option, which is triggered for every log entry. Instead of being printed, the log is passed to the script via stdin, and the script is responsible for processing the input and printing the output.

It’s called a log entry, not a log line, because a log entry can span multiple lines.

Usage Example:

Run Justniffer with a Bash script for log processing:

sudo justniffer -i any -l '%request' -e ./test.sh

Example Script (test.sh)

#!/bin/bash
# sudo justniffer  -e ./test.sh -l dest.ip:%dest.ip:%dest.port%newline%request  -i any  

while read inputline
do 
    text=`echo "$inputline" | grep -i -E  host\|dest\.ip`
    if [ "$text" != "" ]; then
        echo $text;
    fi;
done

This method is ideal for quick inline processing of log entries.


🔹 Extending with a Python Function

You can process logs using a Python function, where each log entry is passed as a byte string to a specified function (using byte strings instead of regular strings allows the function to handle binary content as well)

Usage Example:

Run Justniffer with a Python script:

sudo justniffer -i any -P ./simple-example.py

Example Python Function (simple-example.py)

sudo justniffer  -P simple-example.py  -i any  \
-l "%source.ip:%source.port %dest.ip:%dest.port%newline%request.header%newline%response.header"
import re

HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']

def app(log: bytes):
    print('-' * 190)
    is_http = False
    for idx, line in enumerate(log.decode('utf-8', errors='ignore').splitlines()):
        if idx == 0:
            print(line)
        if idx == 1:
            for method in HTTP_METHODS:
                if line.startswith(method):
                    is_http = True
                    break
        if  is_http:
            print(line)

This method is useful for basic log manipulation and text processing. It is more optimized than the Bash script example because it does not spawn a new process for each log entry. Instead, it directly calls the function, with the Python module being imported only once.

In the provided example, the function first prints the source IP, source port, destination IP, and destination port. Then, it prints the request headers and response headers, but only for HTTP requests

By default, the function “app” is called. However, if needed, you can specify a different function using the format -P script.py:other_function.


🔹 Extending with Python Handlers

For structured event-driven handling, implement a Python class that reacts to different network events.

Interface Definition (ExchangeBase)

Endpoint = tuple[str, int]
Conn = tuple[Endpoint, Endpoint]

class ExchangeBase:
    # called when a connection's syn packet is sent
    def on_opening(self, conn: Conn, time: float) -> None:
        pass

    # called when the connection is established
    def on_open(self, conn: Conn, time: float) -> None:
        pass

    # called when a request fragment is received, applying to every tcp packet 
    # which has already been defragmented, ordered, and deduplicated
    def on_request(self, conn: Conn, content: bytes, time: float) -> None:
        pass

    # called when a response fragment is received, applying to every tcp packet 
    # which has already been defragmented, ordered, and deduplicated
    def on_response(self, conn: Conn, content: bytes, time: float) -> None:
        pass

    # called when the connection is closed, if triggered before on_open
    # it means the connection was refused or filtered
    def on_close(self, conn: Conn, time: float, source_ip: str, source_port: int) -> None:
        pass

    # called when the sniffer is interrupted in the middle of a connection
    def on_interrupted(self) -> None:
        pass

    # called when the connection times out
    def on_timed_out(self, conn: Conn, time: float) -> None:
        pass

    # called to get the result to be logged, if none is returned no log will be generated
    def result(self, time: float | None) -> str | None:
        pass

The result method is typically called after a request/response transaction (the exhange) has completed. It serves as the final step in processing, allowing the system to log or return the result.

Object Life Cycle:

Here’s a general flow of the object’s lifecycle based on your sequence:

This lifecycle ensures a structured sequence of events where each interaction is captured at the right stage.

Usage Example:

sudo PYTHONPATH=. justniffer -i any  -l "%python(simpletest)" -N

The -N option prevents an automatic newline from being added at the end of each log entry. The Python handler has full control over whether to log or not.

Example Python Handler (simpletest.py)



METHODS = [m.encode() for m in ('GET', 'POST', 'PUT', 'PATCH', 
                                'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT')]

class Exchange:
    _response: bytes| None
    def __init__(self) -> None:
        self._request = b''
        self._response = None

    def on_request(self, conn, content: bytes, time: float) -> None:
        self._request+=content

    def on_response(self, conn, content: bytes, time: float) -> None:
        if self._response is None: 
            self._response = content 

    def result(self, time:float | None) -> str | None:
        for m in METHODS:
            if self._request.startswith(m):
                line = self._request.decode(errors='ignore').split('\r')[0]
                if self._response is not None:
                    line += ' | ' + (str(self._response.decode(errors='ignore').splitlines()[0]))
                print(line)
        return None
    

app = Exchange

This example will log the first line of the request and the first line of the response, but only if the protocol is HTTP


This approach is suited for advanced traffic monitoring and structured event handling.