Previously, nfstest was unable to process NFS header messages split across multiple TCP segments, resulting in missed NFS operations and numerous test failures. Such as Rocky Linux 9.6. For example: 4 0.000816 192.168.122.198 → 192.168.122.199 NFS 110 V4 NULL Call 5 0.001073 192.168.122.199 → 192.168.122.198 TCP 66 2049 → 775 [ACK] Seq=1 Ack=45 Win=65152 Len=0 TSval=3032720633 TSecr=443583529 6 0.001155 192.168.122.199 → 192.168.122.198 TCP 70 2049 → 775 [PSH, ACK] Seq=1 Ack=45 Win=65152 Len=4 TSval=3032720634 TSecr=443583529 [TCP segment of a reassembled PDU] 7 0.001155 192.168.122.199 → 192.168.122.198 NFS 90 V4 NULL Reply (Call In 4) This patch introduces functionality to reassemble TCP segments, allowing nfstest to accurately decode complete NFS messages, similar to Wireshark's "TCP Segment of a Reassembled PDU. Signed-off-by: Chen Hanxiao <chenhx.fnst@xxxxxxxxxxx> --- v2: fix nfstest_alloc TCP DATA segment reassemble packet/application/rpc.py | 28 ++++++++++++++++++++++++++++ packet/pktt.py | 5 +++++ packet/transport/tcp.py | 5 +++++ packet/unpack.py | 6 ++++++ 4 files changed, 44 insertions(+) diff --git a/packet/application/rpc.py b/packet/application/rpc.py index 718a300..7bea9f9 100644 --- a/packet/application/rpc.py +++ b/packet/application/rpc.py @@ -193,9 +193,35 @@ class RPC(GSS): pktt = self._pktt unpack = pktt.unpack init_size = unpack.size() + ip = pktt.pkt.ip + tcp = pktt.pkt.tcp + streamid = "%s:%d-%s:%d" % (ip.src, tcp.src_port, ip.dst, tcp.dst_port) + new_size = 0 if self._proto == 6: # TCP packet save_data = '' + #if a split header coming and streamid not in pktt._tcp_pdu_map: + if tcp.flags.PSH and init_size < 16: + if streamid not in pktt._tcp_pdu_map: + pktt._tcp_pdu_map[streamid] = unpack.getbytes() + pktt._tcp_pdu_pkt[streamid] = pktt.pkt + pktt._tcp_pdu_size[streamid] = unpack.size() + return + elif tcp.flags.PSH: + # TCP PDU reassemble + if streamid in pktt._tcp_pdu_map: + pktt._tcp_pdu_map[streamid] += unpack.getbytes() + pktt._tcp_pdu_pkt[streamid] = pktt.pkt + pktt._tcp_pdu_size[streamid] += unpack.size() + + pktt.unpack.replace_data(pktt._tcp_pdu_map[streamid]) + unpack = pktt.unpack + pktt.pkt = pktt._tcp_pdu_pkt[streamid] + new_size = pktt._tcp_pdu_size[streamid] - 4 + + del pktt._tcp_pdu_map[streamid] + del pktt._tcp_pdu_pkt[streamid] + while True: # Decode fragment header psize = unpack.unpack_uint() @@ -211,6 +237,8 @@ class RPC(GSS): # Concatenate RPC fragments unpack.insert(save_data) break + if size < new_size: + size = new_size self.fragment_hdr = Header(size, last_fragment) elif self._proto == 17: # UDP packet diff --git a/packet/pktt.py b/packet/pktt.py index b9420f9..5e6a7ba 100644 --- a/packet/pktt.py +++ b/packet/pktt.py @@ -368,6 +368,11 @@ class Pktt(BaseObj): # IPv4 fragments used in reassembly self._ipv4_fragments = {} + # IPv4 PDU fragments + self._tcp_pdu_map = {} + self._tcp_pdu_pkt = {} + self._tcp_pdu_size = {} + # RDMA reassembly object self._rdma_info = RDMAinfo() diff --git a/packet/transport/tcp.py b/packet/transport/tcp.py index e7fadf4..a5385c1 100644 --- a/packet/transport/tcp.py +++ b/packet/transport/tcp.py @@ -389,6 +389,11 @@ class TCP(BaseObj): # Get RPC header rpc = RPC(pktt, proto=6) + ip = pktt.pkt.ip + tcp = pktt.pkt.tcp + streamid = "%s:%d-%s:%d" % (ip.src, tcp.src_port, ip.dst, tcp.dst_port) + if streamid in pktt._tcp_pdu_size: + ldata = pktt._tcp_pdu_size[streamid] - 4; else: ldata = size - 4 diff --git a/packet/unpack.py b/packet/unpack.py index 223cf60..603102e 100644 --- a/packet/unpack.py +++ b/packet/unpack.py @@ -194,6 +194,12 @@ class Unpack(object): self._state.append([sid, self._offset]) return sid + def replace_data(self, data): + """replace current working buffer""" + self._data = data + self._offset = 0 + self._state = [] + def restore_state(self, sid): """Restore state given by the state id""" max = len(self._state) -- 2.47.1