TCP Rust
TCPImplementation
Project Goal
- Learn how to write rust
- Learn how to implement TCP protocal
Implement TCP in Rust
For this page, I will take notes on tcp concepts
Getting the basics
1. Three-Way Handshake (Connection Establishment)
The TCP connection starts with the SYN → SYN-ACK → ACK process.
Client Server
| |
| ---------- SYN ----------> | (Client requests connection)
| |
| <-------- SYN-ACK -------- | (Server acknowledges)
| |
| ---------- ACK ----------> | (Client confirms connection)
| |
Connection Established!
- SYN: Client sends a connection request to the server.
- SYN-ACK: Server acknowledges the request and sends its own connection request.
- ACK: Client acknowledges the server’s response.
2. Data Transmission
Once the connection is established, data flows in both directions with sequence numbers and acknowledgments.
Client Server
| |
| ------ DATA [SEQ=1] ------> | (Send data with sequence number 1)
| <--- ACK [SEQ=2, ACK=2] ---- | (Server acknowledges receipt)
| |
| <------ DATA [SEQ=2] ------ | (Server sends data back)
| ------ ACK [SEQ=3, ACK=3] --> | (Client acknowledges receipt)
| |
- SEQ: Sequence number identifies the position of the data.
- ACK: Acknowledgment ensures the previous data was received.
TCP guarantees reliability through:
- Sequence Numbers: To order packets.
- Acknowledgments: To confirm delivery.
- Retransmission: If a packet is lost.
3. Connection Termination (Four-Way Handshake)
To close the connection, TCP uses a FIN → ACK → FIN → ACK sequence.
Client Server
| |
| -------- FIN --------> | (Client wants to close connection)
| <-------- ACK -------- | (Server acknowledges FIN)
| |
| <-------- FIN -------- | (Server closes connection)
| -------- ACK --------> | (Client acknowledges FIN)
| |
Connection Closed!
- FIN: A “finish” flag to indicate one side is done sending data.
- ACK: Acknowledge the connection is closing.
Overall Summary
Connection Start: Three-Way Handshake
-------------------------------------
SYN -> SYN-ACK -> ACK
Data Transmission: Reliable Data Flow
-------------------------------------
DATA [SEQ] -> ACK [SEQ]
(Ensures order, error checking, and retransmission)
Connection End: Four-Way Handshake
----------------------------------
FIN -> ACK -> FIN -> ACK
This is a simplified version of TCP to help you visualize how it works step by step. Each step ensures a reliable connection with ordered data delivery, acknowledgments, and connection closure.
Code
- package used
- linux tuntap
References
3.1. Internet Header Format
A summary of the contents of the internet header follows:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Example Internet Datagram Header
Figure 4.
Note that each tick mark represents one bit position.
Version: 4 bits
The Version field indicates the format of the internet header. This
document describes version 4.
IHL: 4 bits
Internet Header Length is the length of the internet header in 32
bit words, and thus points to the beginning of the data. Note that
the minimum value for a correct header is 5.
What is a TUN Interface
let nic = tun_tap::Iface::new("tun0", tun_tap::Mode::Tun)?;
- a
TUN interface
is a virtual network device that operates at the IP level(layer 3) - it is used for packet routing and is often employed for VPNs, network simulations, or custom routing logic
receiving data
let nbytes = nic.recv(&mut buf[..])?;
-
need to grant capabilities to executable files.
-
linix command:
setcap
-
create a
ip
extern crate tun_tap; // Declares the use of the tun_tap crate, which provides bindings for creating and managing TUN/TAP interfaces.
extern crate hex; // hexdecimal encoding/decoding
use std::io;
use hex_fmt::HexFmt;
fn main() -> io::Result<()> {
// see if tun_tap working
/*
* Ok(()) : Success
* Err(io::Error) : an error from the standard I/O library
* */
print!("======run till here 0=============\n");
// create a TUN interface
// network interface name: `tun0`
// `tun_tap::Mode::Tun`: specifies that this is a **TUN interface** (layer 3 - IP packets)
let nic = tun_tap::Iface::new("tun0", tun_tap::Mode::Tun)?;
print!("======run till here 1=============\n");
// create a new interface
// this buffer will hold the incoming packet data from the TUN interface
let mut buf = [0u8; 1504]; // create a buffer of size 1504 bytes
print!("======run till here 2=============\n");
// receiving data
// this is a **blocking operation**, the program will wait until a packet is received
let nbytes = nic.recv(&mut buf[..])?; // Receives a packet from the interface, return #bytes copied into the buffer
print!("======run till here 3=============\n");
// nbytes: the number of bytes read into buffer
// &buf[..nbytes] : a slice of the buffer containing only the received data
// {:x} : attempts to format the buffer slice as hexadecimal
print!("======run till here 4=============\n");
eprintln!("read {} bytes: {}", nbytes, HexFmt(&buf[..nbytes]));
Ok(()) // success
}
run Rust program
cargo r --release
we will get this output
root@61cbf190478a:/home/developer/tcp_implementation# cargo r --release
Finished `release` profile [optimized] target(s) in 0.12s
Running `target/release/tcp_implementation`
^@^@
execute the excutable
get perminssion for the excutatble
setcap cap_net_admin=eip target/release/tcp_implementation
check the ip address
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: gre0@NONE: <NOARP> mtu 1476 qdisc noop state DOWN group default qlen 1000
link/gre 0.0.0.0 brd 0.0.0.0
4: gretap0@NONE: <BROADCAST,MULTICAST> mtu 1462 qdisc noop state DOWN group default qlen 1000
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
5: erspan0@NONE: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN group default qlen 1000
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
6: ip_vti0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
7: ip6_vti0@NONE: <NOARP> mtu 1332 qdisc noop state DOWN group default qlen 1000
link/tunnel6 :: brd :: permaddr 3e76:a20:6dc::
8: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
9: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
link/tunnel6 :: brd :: permaddr 56e3:7e5a:1fcb::
10: ip6gre0@NONE: <NOARP> mtu 1448 qdisc noop state DOWN group default qlen 1000
link/gre6 :: brd :: permaddr 1256:ba4f:67ba::
20: tun0: <POINTOPOINT,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 500
link/none
22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:15:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.21.0.2/16 brd 172.21.255.255 scope global eth0
valid_lft forever preferred_lft forever
- notice there is one called
tun0
which created by us
Then we use the following commands:
ip addr add 192.168.0.1/24 dev tun0
ip link set up dev tun0 # notice this line
whenever we execute this line, our rust program(which running also) will run main
root@61cbf190478a:/home/developer/tcp_implementation# target/release/tcp_implementation
======run till here 0=============
======run till here 1=============
======run till here 2=============
======run till here 3=============
======run till here 4=============
read 52 bytes: 000086dd6000000000083afffe80000000000000707e1f5329cf04abff0200000000000000000000000000028500beeb00000000
we are getting the hexdecimal, lets split it into arr:
eprintln!("read {} bytes: {}", nbytes, HexFmt(&buf[..nbytes]));
change to
eprintln!("read {} bytes: {:02x?}", nbytes, &buf[..nbytes]);
Lets also create a run.sh
#!/bin/bash
# 1. build the rust project
cargo b --release
# 2. add executable permission (no need as I'm root)
# setcap cap_net_admin=eip ../target/release/tcp_implementation
# 3. run rust executable in background
../target/release/tcp_implementation &
pid=$! # create a thread to wait response
# 4. add tun0 ip addr
ip addr add 192.168.0.1/24 dev tun0
ip link set up dev tun0
# 5. check ip addr
# ip addr
# wait process to finish
wait $pid
we see the bytes:
read 52 bytes: [00, 00, 86, dd, 60, 00, 00, 00, 00, 08, 3a, ff, fe, 80, 00, 00, 00, 00, 00, 00, af, ef, b2, 6a, 91, 4b, 1b, ed, ff, 02, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 02, 85, 00, 6d, a4, 00, 00, 00, 00]
but we don’t know what exactly they are, so let’s try to interpret them
- package use:
tshark
create a infinite loop to keep recieving data
// receiving data
// this is a **blocking operation**, the program will wait until a packet is received
loop{
let nbytes = nic.recv(&mut buf[..])?; // Receives a packet from the interface, return #bytes copied into the buffer
//print!("======run till here 3=============\n");
// nbytes: the number of bytes read into buffer
// &buf[..nbytes] : a slice of the buffer containing only the received data
// {:x} : attempts to format the buffer slice as hexadecimal
//print!("======run till here 4=============\n");
eprintln!("read {} bytes: {:02x?}", nbytes, &buf[..nbytes]);
}
if we first ping
the tun0
with a address, we will get continuing packet recieved
- run the rust program with the shell script we created
sh run.sh
- ping the
tun0
we created with a ip address
ping -I tun0 192.168.0.2
we will get continue packets received
root@61cbf190478a:/home/developer/tcp_implementation/src# read 52 bytes: [00, 00, 86, dd, 60, 00, 00, 00, 00, 08, 3a, ff, fe, 80, 00, 00, 00, 00, 00, 00, 33, 83, 88, 94, bd, 02, be, f2, ff, 02, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 02, 85, 00, 45, 2a, 00, 00, 00, 00]
read 88 bytes: [00, 00, 08, 00, 45, 00, 00, 54, 27, 0d, 40, 00, 40, 01, 92, 48, c0, a8, 00, 01, c0, a8, 00, 02, 08, 00, 85, dd, 00, 01, 00, 01, e1, 01, 66, 67, 00, 00, 00, 00, 6b, e4, 00, 00, 00, 00, 00, 00, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1a, 1b, 1c, 1d, 1e, 1f, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 2a, 2b, 2c, 2d, 2e, 2f, 30, 31, 32, 33, 34, 35, 36, 37]
read 88 bytes: [00, 00, 08, 00, 45, 00, 00, 54, 28, 5c, 40, 00, 40, 01, 90, f9, c0, a8, 00, 01, c0, a8, 00, 02, 08, 00, 41, db, 00, 01, 00, 02, e2, 01, 66, 67, 00, 00, 00, 00, ad, e5, 01, 00, 00, 00, 00, 00, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1a, 1b, 1c, 1d, 1e, 1f, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 2a, 2b, 2c, 2d, 2e, 2f, 30, 31, 32, 33, 34, 35, 36, 37]
read 88 bytes: [00, 00, 08, 00, 45, 00, 00, 54, 29, f3, 40, 00, 40, 01, 8f, 62, c0, a8, 00, 01, c0, a8, 00, 02, 08, 00, 65, 7c, 00, 01, 00, 03, e3, 01, 66, 67, 00, 00, 00, 00, 88, 43, 02, 00, 00, 00, 00, 00, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1a, 1b, 1c, 1d, 1e, 1f, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 2a, 2b, 2c, 2d, 2e, 2f, 30, 31, 32, 33, 34, 35, 36, 37]
video timestamp: Implementing TCP in Rust 31:02, Jon Gjengset
Issue face and fixing
functions learning:
The u16::from_be_bytes()
function in Rust constructs a u16
integer from a 2-element byte array interpreted in big-endian (network) byte order. This means the most significant byte is at the first position of the array.
Parameters:
bytes
: A[u8; 2]
array representing the bytes in big-endian order.
Output:
- Returns a
u16
integer composed from the provided byte array.
Example:
fn main() {
let byte_array: [u8; 2] = [0x12, 0x34];
let number = u16::from_be_bytes(byte_array);
println!("The number is: {}", number);
// Output: The number is: 4660
}
Explanation:
In this example, the byte array [0x12, 0x34]
is interpreted as the hexadecimal number 0x1234
, which equals 4660
in decimal. The u16::from_be_bytes()
function correctly assembles these bytes into the u16
integer 4660
.
This function is particularly useful when dealing with data from network protocols or file formats that use big-endian byte ordering. It ensures that the byte sequence is interpreted correctly as a u16
value on any platform.
For more details, refer to the official Rust documentation
pgrep -af target
ex. Output
root@61cbf190478a:/home/developer/tcp_implementation/src# pgrep -af target
5153 ../target/release/tcp_implementation
is used to search for processes whose name or arguments match the string target
and display detailed information about them.
pgrep -af <pattern>
What It Does:
- Searches for processes where the full command line contains the specified
pattern
(in this case, “target”). - Prints the process IDs (PIDs) and their full command lines for all matching processes.
we run run.sh
, we get
read 48 bytes (flags: 0, proto: 86dd: [60, 0, 0, 0, 0, 8, 3a, ff, fe, 80, 0, 0, 0, 0, 0, 0, 64, 35, cb, f9, 8f, e3, aa, 94, ff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 85, 0, 12, 90, 0, 0, 0, 0])
so, what does protocal number 86dd
do:
The hexadecimal value 0x86DD is the EtherType that indicates the payload of an Ethernet frame contains an IPv6 (Internet Protocol version 6) packet.
EtherType in Ethernet Frames:
In Ethernet networking, the EtherType field is a two-octet (16-bit) value within an Ethernet frame that specifies the protocol encapsulated in the payload. This field helps the receiving device determine how to process the incoming data.
Significance of 0x86DD
:
When the EtherType field is set to 0x86DD
, it signifies that the payload is an IPv6 packet. This designation is crucial for network devices to correctly interpret and route IPv6 traffic.
Example Ethernet Frame Structure with IPv6:
+-------------------+-------------------+-------------------+
| Destination MAC | Source MAC | EtherType = 0x86DD|
+-------------------+-------------------+-------------------+
| IPv6 Header and Payload |
+-------------------------------------------------------+
In this structure:
- Destination MAC: The hardware address of the destination device.
- Source MAC: The hardware address of the source device.
- EtherType:
0x86DD
, indicating the payload is an IPv6 packet. - IPv6 Header and Payload: The actual IPv6 data being transmitted.
Note:
It’s important to distinguish between EtherType values and IP protocol numbers. EtherType values, like 0x86DD
, are used in Ethernet frames to indicate the encapsulated protocol. In contrast, IP protocol numbers are used within the IP header to specify the next-level protocol (e.g., TCP, UDP).
For more detailed information, you can refer to the EtherType Wikipedia page.
[!NOTE] When receiving packets from a TUN interface (or similar network tap), you may get both IPv4 and IPv6 packets, and you have to manually filter them based on your requirements. Here’s why and how this works:
Why Both IPv4 and IPv6 Packets Are Received
-
TUN Interface Behavior:
- A TUN interface operates at the network (IP) layer and captures raw IP packets.
- Since IPv4 and IPv6 are both network layer protocols, a TUN interface doesn’t inherently differentiate between them—it passes everything it receives from the kernel’s networking stack.
-
Mixed Traffic in Networks:
- Many networks support both IPv4 and IPv6 traffic simultaneously (dual-stack environments).
- Without filtering, you might receive a mix of packets intended for both protocols, leading to unnecessary complexity if you’re only interested in one.
How to Differentiate IPv4 and IPv6 Packets
The protocol field in the packet header helps identify whether a packet is IPv4 or IPv6:
IPv4 Packets:
- EtherType/Protocol Number:
0x0800
(decimal: 2048).
IPv6 Packets:
- EtherType/Protocol Number:
0x86DD
(decimal: 34525).
Manually Filtering Packets in Your Code
You can use the protocol
field from the packet’s metadata to filter out unwanted packets.
Example in Rust:
loop {
let nbytes = nic.recv(&mut buf[..])?;
// Extract protocol field (bytes 2-3)
let proto = u16::from_be_bytes([buf[2], buf[3]]);
// Check protocol type
match proto {
0x0800 => {
// Handle IPv4 packet
eprintln!("Skipping IPv4 packet");
},
0x86DD => {
// Handle IPv6 packet
eprintln!("Processing IPv6 packet: {:02x?}", &buf[4..nbytes]);
},
_ => {
// Unknown protocol
eprintln!("Unknown protocol: {:x}", proto);
}
}
}
Output:
- IPv4 packets will be skipped with a message:
Skipping IPv4 packet
. - IPv6 packets will be processed further and logged.
Advantages of Filtering:
-
Streamlining Data Processing:
- By focusing only on the relevant protocol, you reduce the complexity of your packet analysis or handling logic.
-
Resource Optimization:
- Ignoring unnecessary packets saves computational resources and improves performance, especially in high-traffic environments.
-
Focused Debugging/Analysis:
- Isolating a specific protocol (e.g., IPv6) simplifies debugging and makes the data stream more manageable.
- we need to add a filter ipv4(
0x0800
) to the program:
if proto != 0x0800 {
// no ipv4
continue
}
Now we need to encode the ipv4
header to our rust code
match etherparse::Ipv4HeaderSlice::from_slice(&buf[4..nbytes]) {
Ok(p) => {
/*
nbytes: the number of bytes read into buffer
&buf[..bytes]: a slice of the buffer containing only the received data
{:x} : attempts to format the buffer slice as hexadecimal*/
eprintln!(
"read {} bytes (flags: {:x}, proto: {:x}: {:x?})",
nbytes - 4,
flags,
proto,
p
);
}
Err(e) => {
eprintln!("ignoring weird packet {:?}", e)
}
}
when ping 192.168.2.2
using tun0
, we will get
read 84 bytes (flags: 0, proto: 800: Ipv4HeaderSlice { slice: [45, 0, 0, 54, ab, fa, 40, 0, 40, 1, 1f, ed, ac, 15, 0, 2, c0, a8, 2, 2]})
Lets parse and print out things we actually care about
add log when recieve ping
loop {
let nbytes = nic.recv(&mut buf[..])?; // Receives a packet from the interface(when a packet arrives), return #bytes copied into the buffer
//print!("======run till here 3=============\n");
/*
* u16::from_be_bytes
* constructs a `u16` integer from a 2-element byte array interpreted in big-endian
* (network) byte order. This means the most significant byte is at the first position
* of the array
*
* Parameters:
* `bytes`: a `[u8; 2] array representing the bytes in big-endian order
*
* Output
* returns a `u16` integer composed from the provided byte array*/
// the first 4 bytes of the buffer ar einterpreted as metadata
// enthernet frame we got,
// link level protocal
let _eth_flags = u16::from_be_bytes([buf[0], buf[1]]); // byte 0-1: represent flags (interpreted as a u16 integer in big-endian order)
let eth_proto = u16::from_be_bytes([buf[2], buf[3]]); // protocol type (e.g. IPv4, IPv6)
// filter anything that is not ipv4 packet
if eth_proto != 0x0800 {
// no ipv4
continue;
}
match etherparse::Ipv4HeaderSlice::from_slice(&buf[4..nbytes]) {
Ok(p) => {
// nbytes: the number of bytes read into buffer
// &buf[..nbytes] : a slice of the buffer containing only the received data
// {:x} : attempts to format the buffer slice as hexadecimal
//print!("======run till here 4=============\n");
// NOTE: get proto type : 86dd , which is the enthertype that indicates the payload of an
// enthernet frame contains an IPV6 (internet protocol version 6) packet
// ip level protocal, should be set to tcp
let src = p.source_addr();
let destination = p.destination_addr();
let protocal = p.protocol();
eprintln!(
"{} -> {} {}b of proto: {:x} ",
src,
destination,
p.payload_len(),
protocal,
);
}
Err(e) => {
eprintln!("ignoring weird packet {:?}", e)
}
}
}
and I got
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
192.168.2.1 -> 192.168.2.2 64b of proto: 1
run ip addr show tun0
, get
root@61cbf190478a:/home/developer/tcp_implementation# ip addr show tun0
33: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 500
link/none
inet 192.168.2.1/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::cc61:19f0:9760:9006/64 scope link stable-privacy
valid_lft forever preferred_lft forever
the tun0 interface is configured correctly and active (state UP). The IP address 192.168.2.1/24 is assigned, and is in the UP state. The interface is ready to handle both IPv4 and IPv6 traffic.
- assign an IPv4 Address to
tun0
sudo ip addr add 192.168.2.1/24 dev tun0
sudo ip link set dev tun0 up
-
ensure proper routing
- add a route for the IPv4 subnet:
sudo ip route add 192.168.2.0/24 dev tun0
When your program prints Packet captured on tun0: protocol 1, length 88
, it indicates that a packet with protocol number 1
and a total length of 88
bytes was captured on the tun0
interface.
Understanding Protocol Number 1:
In the IPv4 header, the “Protocol” field specifies the next-level protocol used in the data portion of the packet. The protocol number 1
corresponds to the Internet Control Message Protocol (ICMP). ICMP is primarily used for diagnostic or control purposes, such as the ping
command, which sends ICMP Echo Request messages to test network connectivity.
Why is the Protocol Number Displayed as 1?
The protocol number is displayed as 1
because your program is likely printing the decimal value of the “Protocol” field from the IPv4 header. In the IPv4 header, protocol numbers are represented as 8-bit integers. While these numbers can be expressed in hexadecimal (base 16), they are often displayed in decimal (base 10) for readability. In this case, the decimal value 1
corresponds to the hexadecimal value 0x01
, both representing ICMP.
Interpreting the Packet Length:
The length 88
indicates the total size of the captured packet in bytes. This includes the IPv4 header, the ICMP header, and any additional data payload. An ICMP Echo Request (commonly used by ping
) typically consists of:
- IPv4 Header: Usually 20 bytes.
- ICMP Header: 8 bytes.
- Data Payload: Variable length; in this case, approximately 60 bytes to reach a total packet size of 88 bytes.
Conclusion:
The message Packet captured on tun0: protocol 1, length 88
signifies that your program has intercepted an ICMP packet of 88 bytes on the tun0
interface. This is typical behavior when tools like ping
are used, as they generate ICMP traffic to test network connectivity.
For a comprehensive list of IP protocol numbers and their corresponding protocols, you can refer to the IANA Protocol Numbers.