Of course! In Python, handling TCP options is an advanced but powerful feature, primarily used for low-level network programming, security research, and protocol testing. The standard socket library in Python does not provide direct, easy access to TCP options. You need to use more powerful, system-level libraries.
Here’s a comprehensive guide covering:
- Why you'd want to use TCP options
- The primary Python library:
scapy - A more complex but native approach:
socket.SO_TIMESTAMPING - The "gold standard" for C extensions:
python-linux-network
Why Use TCP Options?
TCP options are a key part of the TCP header, allowing for extensions and negotiated features between the client and server. Common options include:
- MSS (Maximum Segment Size): Negotiates the largest amount of data that can be sent in a single TCP segment.
- Window Scale: Allows for the use of larger TCP receive windows, improving performance over high-latency networks.
- SACK Permitted (Selective Acknowledgment): Allows a receiver to acknowledge non-contiguous blocks of data, improving recovery from lost packets.
- Timestamps: Used for the PAWS (Protection Against Wrapped Sequences) mechanism and RTT (Round-Trip Time) measurement.
- NOP (No-Operation): Used for padding to align option fields.
- Custom/EOL (End of Option List): Used to mark the end of the options list.
The Easiest & Most Powerful Method: scapy
scapy is a fantastic Python library that allows you to create, send, sniff, and dissect network packets. It gives you complete control over the TCP header, including the options field.
Installation:
pip install scapy
How it Works:
You build a packet layer by layer. For TCP options, you pass a list of tuples to the options parameter of the TCP layer. Each tuple is (option_name, option_value).
Example 1: Sending a TCP SYN Packet with Custom Options
Let's create a TCP SYN packet with an MSS of 1460, a Window Scale option, and a Timestamp.
from scapy.all import IP, TCP, send
# Define the TCP options
# Format: (option_name, option_value)
# For simple flags like 'WScale', the value is the shift count.
# For 'MSS', the value is the Maximum Segment Size in bytes.
# For 'Timestamp', the value is a tuple (tsval, tsecr).
# For 'NOP', the value is just the string 'NOP'.
tcp_options = [
('MSS', 1460),
('WScale', 7),
('NOP', None), # Padding
('NOP', None), # Padding
('SAckOK', None), # SACK Permitted flag
('Timestamp', (12345678, 0)) # (tsval, tsecr)
]
# Build the IP layer
ip_layer = IP(dst="8.8.8.8")
# Build the TCP layer with the custom options
# flags='S' sets the SYN flag
tcp_layer = TCP(
sport=12345,
dport=80,
seq=1000,
window=8192,
options=tcp_options,
flags='S' # SYN packet
)
# Stack the layers to create the final packet
packet = ip_layer / tcp_layer
# Print a summary of the packet to see the options
print(packet.show())
# Send the packet (you might need root/admin privileges)
# send(packet, verbose=0)
print(f"Packet built and ready to send: {packet.summary()}")
Output of packet.show():
You will see a detailed breakdown, including the Options section listing exactly what you built.
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 60
id= 1
flags=
frag= 0L
ttl= 64
proto= tcp
chksum= 0x2c5a
src= 192.168.1.10 # Your source IP
dst= 8.8.8.8
\options\
###[ TCP ]###
sport= 12345
dport= http
seq= 1000
ack= 0
dataofs= 10 # This shows the header is longer due to options
reserved= 0L
flags= S
window= 8192
chksum= 0x7a1e
urgptr= 0
options= [('MSS', 1460), ('WScale', 7), ('NOP', None), ('NOP', None), ('SAckOK', None), ('Timestamp', (12345678, 0))]
\options\
###[ Raw ]###
load= ''
Example 2: Sniffing and Parsing TCP Options
Now, let's sniff a TCP packet and inspect its options.
from scapy.all import sniff, TCP
def process_packet(packet):
# Check if the packet has an IP and TCP layer
if packet.haslayer(TCP):
tcp_layer = packet.getlayer(TCP)
print(f"\n--- TCP Packet from {packet[IP].src}:{tcp_layer.sport} to {packet[IP].dst}:{tcp_layer.dport} ---")
# Check if options exist and are not empty
if tcp_layer.options:
print("TCP Options Found:")
for option in tcp_layer.options:
# option is a tuple (option_name, option_value)
print(f" - {option[0]}: {option[1]}")
else:
print("No TCP options in this packet.")
# Sniff 5 TCP packets and call process_packet for each
# You might need root/admin privileges to run this
# sniff(filter="tcp", prn=process_packet, count=5)
print("Sniffer ready. Run with root/admin to capture live packets.")
Native Socket Approach: SO_TIMESTAMPING
For a more "native" Python approach without scapy, you can use the socket module with special options. However, this is much more limited. You can't set arbitrary TCP options like MSS or Timestamps. Instead, you can get operational data that these options provide, like high-precision timestamps.
This is useful for performance measurement and network analysis.
How it Works:
You use setsockopt() to enable timestamping on the socket. The SO_TIMESTAMPING option allows you to get hardware and software timestamps for when a packet was sent or received.
Example: Getting Timestamps on Received Packets
import socket
import struct
import time
import sys
# You need root/admin privileges to create a raw socket
if not hasattr(socket, 'SO_TIMESTAMPING'):
print("SO_TIMESTAMPING is not available on this system.")
sys.exit(1)
def create_raw_socket():
# Create a raw socket to capture all incoming IP packets
try:
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))
s.bind(("eth0", 0)) # Bind to a specific network interface
return s
except PermissionError:
print("Permission denied. Please run this script as root/admin.")
sys.exit(1)
except Exception as e:
print(f"Error creating socket: {e}")
sys.exit(1)
def get_timestamps(s):
# Enable software and hardware timestamps
s.setsockopt(socket.SOL_SOCKET, socket.SO_TIMESTAMPING, struct.pack("3I", 1, 1, 0))
packet, ancdata, _, addr = s.recvmsg(1024)
timestamps = {}
for cmsg_level, cmsg_type, cmsg_data in ancdata:
if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SO_TIMESTAMPING:
# The data contains three 64-bit unsigned integers (ns)
ts_arr = struct.unpack("3Q", cmsg_data)
timestamps['software_ts'] = ts_arr[0] # Software receive timestamp
timestamps['hardware_ts'] = ts_arr[1] # Hardware receive timestamp
# ts_arr[2] is the hardware transmit timestamp
return timestamps
if __name__ == "__main__":
raw_sock = create_raw_socket()
print("Listening for packets on eth0...")
try:
while True:
ts_data = get_timestamps(raw_sock)
if ts_data:
# Convert nanoseconds to seconds for readability
sw_ts_sec = ts_data['software_ts'] / 1_000_000_000.0
print(f"Packet received. Software Timestamp: {sw_ts_sec:.9f}")
except KeyboardInterrupt:
print("\nStopping listener.")
raw_sock.close()
Limitation: This method does not let you read the TCP options from the incoming packet header itself. It only gives you the results of using options (like timestamps). To inspect the header, you'd have to manually parse the raw byte data, which is complex and error-prone.
The Advanced Native Approach: python-linux-network
For a true, native Python experience that rivals scapy's power for low-level control, you can use a C extension library like python-linux-network. This library provides direct bindings to Linux kernel networking functions.
Installation: This is more complex as it requires building from source.
# You'll need build essentials and the kernel headers sudo apt-get update sudo apt-get install -y python3-dev build-essential linux-headers-$(uname -r) # Clone and build the library git clone https://github.com/dreibh/python-linux-network.git cd python-linux-network python3 setup.py build sudo python3 setup.py install
How it Works:
This library gives you access to libnl (Netlink) and other advanced APIs, allowing you to do things like set socket options that scapy uses under the hood.
This is for very advanced use cases and is significantly more complex than using scapy. For 99% of users, scapy is the recommended tool.
Summary: Which Method to Choose?
| Method | Pros | Cons | Best For |
|---|---|---|---|
scapy |
Easy to use, powerful, cross-platform, great for crafting/sniffing packets. | Requires root/admin for raw sockets. Can be slow for high-throughput tasks. |
Most use cases: Security research, protocol development, network testing, learning. |
socket.SO_TIMESTAMPING |
Native Python, no external dependencies (besides socket). |
Very limited. Can only get timestamps, not set arbitrary TCP options. | Performance measurement, high-precision timing applications. |
python-linux-network |
Maximum power and performance, native-level control. | Extremely complex to install and use. Not cross-platform. | High-performance network applications, kernel-level interaction, advanced research. |
Final Recommendation: Start with scapy. It is the most versatile, well-documented, and Pythonic way to interact with TCP options in Python.
