summaryrefslogtreecommitdiff
path: root/crafted-pkt/replay-chunks.py
blob: 99907daa88a1a42af608bed23dd514847b2f6d4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python
#
# Replay a communication, splitting data at a certain chunk sizes
# (defaults to 2). This can be used to test reassembly for example.
#
# Copyright (C) 2014 Peter Wu <peter@lekensteyn.nl>

# Usage (assuming a capture file with TCP stream 0 at loopback interface lo)
#     dumpcap -i lo -w split.pcapng
#     ./replay-chunks.py old-capture.pcapng
# Run ./replay-chunks.py --help for details

import socket
import sys
import os
from argparse import ArgumentParser
from subprocess import PIPE
from subprocess import Popen as _Popen
if hasattr(_Popen, '__exit__'):
    Popen = _Popen
else:
    class PopenClosable(_Popen):
        def __enter__(self):
            return self
        def __exit__(self, exc_type, exc_value, traceback):
            self.stdout.close()
    Popen = PopenClosable
try:
    from subprocess import DEVNULL
except ImportError:
    # Python < 3.3 compatibility
    DEVNULL = open(os.devnull, 'w')

state = 'init'

def _is_marker(line):
    return all(c == '=' for c in line.strip())

def _dumpbytes(data):
    return ''.join([
        chr(x) if x >= 0x20 and x < 0x7f else '.'
            for x in data
    ])

class FollowParser(object):
    def __init__(self, chunk_size=2):
        self.state = self.state_find_begin
        self.addr = None
        self.sock_client = None
        self.sock_server = None
        self.chunk_size = chunk_size

    def add_data(self, data, is_reply):
        sock = self.sock_server if is_reply else self.sock_client
        othersock = self.sock_client if is_reply else self.sock_server
        for i in range(0, len(data), self.chunk_size):
            sock.sendall(data[i:i+self.chunk_size])
            othersock.recv(self.chunk_size)
        print('{}: {}'.format('S->C' if is_reply else 'C->S', _dumpbytes(data)))

    def state_find_begin(self, line):
        if _is_marker(line):
            self.state = self.state_find_node_1

    def state_find_node_1(self, line):
        if line.startswith('Node 1: '):
            host, port = line.split(' ')[-1].rsplit(':', 1)
            self.addr = (host, int(port))
            self.state = self.state_find_data
            self.open_sockets()

    def state_find_data(self, line):
        if _is_marker(line):
            self.state = self.state_done
            return
        is_reply = line.startswith('\t')
        data = bytearray.fromhex(line.strip())
        self.add_data(data, is_reply)

    def state_done(self, line):
        pass

    def open_sockets(self):
        svr = socket.socket()
        try:
            svr.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            svr.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            try:
                svr.bind(self.addr)
            except socket.error as e:
                host, port = self.addr
                print('bind({0}:{1}) failure: {2}'.format(host, port, e))
                self.addr = ('127.9.0.1', port)
                print('Falling back to {}:{}'.format(*self.addr))
                svr.bind(self.addr)
            svr.listen(1)
            self.sock_client = socket.socket()
            self.sock_client.connect(self.addr)
            self.sock_server, remote_addr = svr.accept()
        finally:
            svr.close()

    def feed_data(self, line):
        old_state = self.state
        self.state(line)
        # Return old state and new state
        return old_state, self.state

    def close(self):
        if self.sock_server:
            self.sock_server.close()
            self.sock_server = None
        if self.sock_client:
            self.sock_client.close()
            self.sock_client = None

def main(tshark_output, chunk_size):
    parser = FollowParser(chunk_size=chunk_size)
    try:
        for line in tshark_output:
            old_state, new_state = parser.feed_data(line)
            if new_state != old_state and old_state == parser.state_find_node_1:
                print('Found server node: {}:{}'.format(*parser.addr))
    finally:
        parser.close()

parser = ArgumentParser(description='Replay TCP capture')
parser.add_argument('-s', '--chunk-size', type=int, default=2,
                    help='Maximum size of each chunk (default %(default)d)')
parser.add_argument('file', help='Any capture format recognized by tshark')
if __name__ == '__main__':
    _args = parser.parse_args()
    _cmd = [
        'tshark',
        '-r',
        _args.file,
        '-q',
        '-z', 'follow,tcp,raw,0'
    ]
    with Popen(_cmd, stdin=DEVNULL, stdout=PIPE, universal_newlines=True) as p:
        main(p.stdout, chunk_size=_args.chunk_size)