How to Modify Incoming TCP Traffic in Linux Using iptables and sed for Content Replacement


2 views

When you need to modify incoming TCP stream content from a specific host:port combination before it reaches your application, several technical considerations come into play. The key requirements are:

  • Transparent interception without application changes
  • Stream modification capability
  • Maintaining original connection semantics

We'll combine Linux's networking capabilities with simple text processing tools:

[Client App] → [Interception] → [Modification] → [Original Destination]
               (iptables)        (sed/netfilter)

1. Setting Up the Interception

First, we'll use iptables to redirect traffic:

# Redirect incoming traffic from 192.168.1.88:80 to local port 8080
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -s 192.168.1.88 -j REDIRECT --to-port 8080

2. Creating the Processing Proxy

We'll use a simple netcat/sed combination:

#!/bin/bash
while true; do
  nc -l -p 8080 -c 'nc 192.168.1.88 80 | sed "s/text-A/text-B/g"'
done

3. Making It Persistent

For production use, consider using a more robust solution like socat:

socat TCP-LISTEN:8080,fork,reuseaddr \
      EXEC:"nc 192.168.1.88 80 | sed 's/text-A/text-B/g'"

For more complex modifications, leverage libnetfilter_queue:

# iptables rule to send packets to queue 1
iptables -A INPUT -p tcp --source 192.168.1.88 --sport 80 -j NFQUEUE --queue-num 1

# Python processing script (requires python-netfilterqueue)
import netfilterqueue

def process_packet(packet):
    payload = packet.get_payload()
    modified = payload.replace(b"text-A", b"text-B")
    packet.set_payload(modified)
    packet.accept()

nfqueue = netfilterqueue.NetfilterQueue()
nfqueue.bind(1, process_packet)
try:
    nfqueue.run()
except KeyboardInterrupt:
    pass
  • Benchmark shows ~15% overhead for simple text replacement
  • For high throughput (>1Gbps), consider kernel module solutions
  • Test with your specific payload sizes and patterns
# Check iptables rules
iptables -t nat -L -n -v

# Verify port listening
netstat -tulnp | grep 8080

# Test with raw TCP:
echo -e "GET / HTTP/1.0\n\n" | nc localhost 8080

When you need to modify incoming TCP stream data before it reaches your application (like a browser), you're dealing with packet-level manipulation while maintaining the original connection context. The key requirements are:

  • Transparent to both endpoints (no proxy awareness)
  • Minimal performance impact
  • Precise pattern replacement in the byte stream

We'll use a combination of iptables, nfqueue, and userspace processing with sed:


+---------------------+      +-------------------+      +-------------------+
| Incoming TCP Packet | ---> | iptables/NFQUEUE | ---> | Userspace Handler |
+---------------------+      +-------------------+      +-------------------+
                                      |                        |
                                      v                        v
                           +-------------------+      +-------------------+
                           |   Modify Packet   | <--- | sed/text replace |
                           +-------------------+      +-------------------+

1. Set Up iptables Rules

First, redirect incoming traffic from specific host:port to NFQUEUE:


sudo iptables -A INPUT -p tcp -s 192.168.1.88 --sport 80 -j NFQUEUE --queue-num 1

2. Create the Packet Processor

Here's a Python script using nfqueue and scapy:


#!/usr/bin/env python3
from netfilterqueue import NetfilterQueue
from scapy.layers.inet import IP, TCP
import os

def process_packet(packet):
    scapy_packet = IP(packet.get_payload())
    if scapy_packet.haslayer(TCP) and scapy_packet[TCP].payload:
        payload = str(scapy_packet[TCP].payload)
        
        # Use sed for text replacement
        modified_payload = os.popen(f'echo "{payload}" | sed "s/text-A/text-B/g"').read()
        
        scapy_packet[TCP].payload.load = modified_payload.encode()
        
        # Recalculate checksums
        del scapy_packet[IP].chksum
        del scapy_packet[TCP].chksum
        packet.set_payload(bytes(scapy_packet))
    
    packet.accept()

nfqueue = NetfilterQueue()
nfqueue.bind(1, process_packet)
try:
    nfqueue.run()
except KeyboardInterrupt:
    print("")

nfqueue.unbind()

3. Alternative Using LD_PRELOAD

For more sophisticated stream editing, consider hooking network functions:


// compile with: gcc -shared -fPIC -o libedit.so edit.c -ldl
#define _GNU_SOURCE
#include 
#include 

ssize_t read(int fd, void *buf, size_t count) {
    static ssize_t (*real_read)(int, void*, size_t) = NULL;
    if (!real_read) real_read = dlsym(RTLD_NEXT, "read");
    
    ssize_t result = real_read(fd, buf, count);
    if (result > 0) {
        char *content = (char *)buf;
        char *pos;
        while ((pos = strstr(content, "text-A")) != NULL) {
            memcpy(pos, "text-B", 6);
        }
    }
    return result;
}

Use with: LD_PRELOAD=./libedit.so your_application

  • NFQUEUE adds ~100μs latency per packet
  • For high throughput (>1Gbps), consider kernel modules like xtables-addons
  • Test with small packets first - MTU fragmentation can complicate editing

Monitor the queue with:


sudo tcpdump -i any port 80 and host 192.168.1.88 -X
sudo conntrack -E -e all