本文章的学习旨在完成手动封装以太网头部,外层IP头部,GRE头部,内层IP头部以及TCP头部的内容,并将数据发送给服务端,服务端接收后手动解析数据包的内容并打印出来。
GRE VPN(Generic Routing Encapsulation)通用路由封装协议,是对某些网络层协议(如IP和IPX)的数据报进行封装,使这些被封装的数据报能够在另一个网络层协议(如IP)中传输。GRE是VPN(Virtual Private Network)的第三层隧道协议,即在协议层之间采用了一种被称之为Tunnel(隧道)的技术。
运行环境
VMware虚拟机运行两个ubuntu 20.04系统,IP地址分别为192.168.112.128和192.168.112.129
GRE封包
在一些应用场景中,比如公司有总部和分部,要求公司内部的IP地址不能暴露在公网,这时就可以使用GRE对数据包进行再次封装GRE在需要传输的数据包的头部添加GRE头部后,再次对整个数据包进行IP的封装。在GRE隧道传输时,数据包具有双层IP头部,第一个IP头部用于在公网中进行数据传递,第二个IP头部用于在公司内部进行数据传递,这个IP一般为私有地址。具有双层IP头部的包的外层解封之后,露出的是GRE头部,必须运行同样的GRE协议的接口才可以处理此报文。
GRE数据包格式:Gre数据包封装格式、通过NAT设备 - 知乎 GRE、MGRE 详解_gre协议-CSDN博客
RFC文档相关 https://blog.csdn.net/m0_65730854/article/details/122117391
|| 以太网帧头 || 新-IPv4报头 || Gre报头 || 原始-IPv4报头 || TCP报头 || HTTP ||,在代码中需要实现的是手动填充TCP报头信息,原始IPv4报头信息,GRE报头信息
简化GRE包头格式:
Flags and Version:0x3001
Protocol Type:IP(0x0800)
Key:0x00012dff
Sequence Number:2886730204
客户端代码
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/if_packet.h> // For sockaddr_ll
#include <net/ethernet.h>
#include <errno.h>
#define MAX_PACKET_SIZE 1500
uint16_t source_port = 1234;
uint16_t dest_port = 4321;
uint16_t window_size = 7500;
struct etherhdr
{
char source_mac[6];
char dest_mac[6];
uint16_t protocol; // 0x0800 denote IP
};
struct grehdr
{
uint16_t flags_and_version;
uint16_t protocol;
uint32_t key;
uint32_t sequence_number;
};
void print_str16(unsigned char buf[], size_t len)
{
int i;
unsigned char c;
if (buf == NULL || len <= 0)
return;
for (i = 0; i < len; i++)
{
c = buf[i];
printf("%02x", c);
}
printf("\n");
}
void pack_ether_header(char *buffer, char *source_mac, char *dest_mac)
{
char dst_mac[6] = {0x00, 0x0c, 0x29, 0xdb, 0xd8, 0x5f};
char src_mac[6] = {0x00, 0x0c, 0x29, 0x0b, 0x1b, 0x21};
memcpy(buffer, dst_mac, 6);
memcpy(buffer + 6, src_mac, 6);
struct etherhdr *etherh = (struct etherhdr *)buffer;
etherh->protocol = htons(ETH_P_IP);
}
// Function to pack GRE header (simplified, real GRE has more fields)
void pack_gre_header(char *buffer)
{
struct grehdr *greh = (struct grehdr *)(buffer + sizeof(struct etherhdr) + sizeof(struct iphdr));
greh->flags_and_version = htons(0x3001);
greh->protocol = htons(0x0800);
greh->key = htonl(0x00012dff);
greh->sequence_number = htonl(2886730204);
}
// Function to pack IP header
void pack_outer_ip_header(char *buffer, size_t len)
{
struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct etherhdr));
iph->protocol = IPPROTO_GRE;
iph->version = 4;
iph->ihl = 5; // Internet Header Length
iph->tot_len = htons(len);
iph->ttl = 255;
iph->saddr = inet_addr("192.168.112.129");
iph->daddr = inet_addr("192.168.112.128");
}
// Function to pack IP header
void pack_inner_ip_header(char *buffer, size_t len)
{
struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct etherhdr) + sizeof(struct iphdr) + sizeof(struct grehdr));
iph->protocol = IPPROTO_TCP;
iph->version = 4;
iph->ihl = 5; // Internet Header Length
iph->tot_len = htons(len);
iph->ttl = 255;
iph->saddr = inet_addr("10.0.0.1");
iph->daddr = inet_addr("10.0.0.2");
}
// Function to pack TCP header (simplified)
void pack_tcp_header(char *buffer, uint16_t src_port, uint16_t dst_port, uint16_t ws)
{
struct tcphdr *tcph = (struct tcphdr *)(buffer + sizeof(struct etherhdr) + sizeof(struct iphdr) + sizeof(struct grehdr) + sizeof(struct iphdr));
tcph->source = htons(src_port); // Example source port
tcph->dest = htons(dst_port); // Example destination port
tcph->seq = htonl(0); // Sequence number
tcph->ack = htonl(0); // Acknowledgment number
tcph->doff = 5; // // 5 * 4 = 20 B, the size of header
tcph->fin = 0; // finish
tcph->syn = 1; // SYN packet for connection setup
tcph->rst = 0;
tcph->psh = 0;
tcph->ack = 0;
tcph->window = htons(ws); // Window size
}
void parse_opt(int argc, char *argv[])
{
for (int opt = 0; (opt = getopt(argc, argv, "d:s:w:")) != -1;)
{
switch (opt)
{
case 'd':
dest_port = atoi(optarg);
break;
case 's':
source_port = atoi(optarg);
break;
case 'w':
window_size = atoi(optarg);
break;
case '?':
default:
fprintf(stderr,
"Usage: %s [-h] [-d DSTPORT] [-s SRCPORT] [-w WINDOWSIZE]\n"
"\n"
"Options:\n"
" -h Show this help message\n"
" -d Set the Destination TCP port\n"
" -s Set Source TCP port\n"
" -w TCP window size\n",
argv[0]);
exit(EXIT_FAILURE);
}
}
}
int main(int argc, char *argv[])
{
parse_opt(argc, argv);
const char payload[] = "This is a message from ubuntu 02";
char *source_mac = "00:0c:29:0b:1b:21"; // ubuntu 02 MAC
char *dest_mac = "00:0c:29:db:d8:5f"; // ubuntu 01 MAC
int sock_raw;
char buffer[MAX_PACKET_SIZE]; // Buffer to hold packet data
struct sockaddr_ll sll;
struct ifreq ifr;
char if_name[] = "ens33";
// Create a raw socket for sending ether packet
if ((sock_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) == -1)
{
perror("Socket creation error");
return -1;
}
strcpy(ifr.ifr_name, if_name);
if (ioctl(sock_raw, SIOCGIFINDEX, &ifr) != 0)
{
printf("get mac index error, %d\n", errno);
return errno;
}
sll.sll_ifindex = ifr.ifr_ifindex;
if (ioctl(sock_raw, SIOCGIFHWADDR, &ifr) != 0)
{
printf("get mac addr error, %d\n", errno);
return errno;
}
// Pack headers into the buffer, the result is || Ethernet header || outer IP header || GRE header || inner IP header || TCP header || payload ||
pack_ether_header(buffer, source_mac, dest_mac);
pack_outer_ip_header(buffer, sizeof(payload) + sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof(struct grehdr) + sizeof(struct iphdr));
pack_gre_header(buffer);
pack_inner_ip_header(buffer, sizeof(payload) + sizeof(struct tcphdr) + sizeof(struct iphdr));
pack_tcp_header(buffer, source_port, dest_port, window_size);
memcpy(buffer + sizeof(struct etherhdr) + sizeof(struct iphdr) + sizeof(struct grehdr) + sizeof(struct iphdr) + sizeof(struct tcphdr), &payload, sizeof(payload));
// Send the packet
size_t total_len = sizeof(struct etherhdr) + sizeof(struct iphdr) + sizeof(struct grehdr) + sizeof(struct iphdr) + sizeof(struct tcphdr) + sizeof(payload);
// print_str16((unsigned char *)buffer, total_len);
if (sendto(sock_raw, buffer, total_len, 0, (struct sockaddr *)&sll, sizeof(sll)) < 0)
{
perror("Sendto failed");
return -1;
}
printf("Packet sent successfully\n");
close(sock_raw);
return 0;
}
服务器代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#define MAX_PACKET_SIZE 1500
struct etherhdr
{
char source_mac[6];
char dest_mac[6];
uint16_t protocol; // 0x0800 denote IP
};
struct grehdr
{
uint16_t flags_and_version;
uint16_t protocol;
uint32_t key;
uint32_t sequence_number;
};
void print_str16(unsigned char buf[], size_t len)
{
int i;
unsigned char c;
if (buf == NULL || len <= 0)
return;
for (i = 0; i < len; i++)
{
c = buf[i];
printf("%02x", c);
}
printf("\n");
}
void print_mac_address(unsigned char mac[6])
{
for (int i = 0; i < 6; i++)
{
printf("%02x", mac[i]);
if (i < 5)
printf(":");
}
printf("\n");
}
void print_ip(uint32_t ip)
{
uint32_t host_order_ip = ntohl(ip);
uint8_t a = (host_order_ip >> 24) & 0xFF;
uint8_t b = (host_order_ip >> 16) & 0xFF;
uint8_t c = (host_order_ip >> 8) & 0xFF;
uint8_t d = host_order_ip & 0xFF;
printf("%d.%d.%d.%d\n", a, b, c, d);
}
void print_ether_header_info(struct etherhdr *etherh)
{
printf("src mac: ");
print_mac_address(etherh->source_mac);
printf("dst mac: ");
print_mac_address(etherh->dest_mac);
printf("\n");
}
void print_ip_header_info(struct iphdr *iph)
{
printf("source IP address: ");
print_ip(iph->saddr);
printf("destination IP address: ");
print_ip(iph->daddr);
}
void print_gre_header_info(struct grehdr *greh)
{
printf("\ngre header info:\n");
printf("flags and version: 0x%04x\n", ntohs(greh->flags_and_version));
printf("protocol: 0x%04x\n", ntohs(greh->protocol));
printf("key: 0x%08x\n", ntohl(greh->key));
printf("sequence number: %u\n", ntohl(greh->sequence_number));
}
void print_payload(ssize_t data_len, char *buffer)
{
int payload_offset = sizeof(struct etherhdr) + sizeof(struct iphdr) + sizeof(struct grehdr) + sizeof(struct iphdr) + sizeof(struct tcphdr);
int payload_len = data_len - payload_offset;
if (payload_len > 0)
{
char *payload = buffer + payload_offset;
printf("\nReceived payload:\n");
for (int i = 0; i < payload_len; i++)
printf("%c", payload[i]);
}
else
printf("No payload found.");
printf("\n---------------------------------------------------\n");
}
int main(int argc, char const *argv[])
{
int sock_raw;
char buffer[MAX_PACKET_SIZE];
// if ((sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_GRE)) == -1)
if ((sock_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) == -1)
{
perror("Socket creation error");
return -1;
}
struct sockaddr_ll sll;
socklen_t sll_len = 0;
while (1)
{
ssize_t len = recvfrom(sock_raw, buffer, MAX_PACKET_SIZE, 0, (struct sockaddr *)&sll, &sll_len);
if (len < 0)
{
perror("Recvfrom failed\n");
continue;
}
// Parse ether header
struct etherhdr *etherh = (struct etherhdr *)buffer;
if (ntohs(etherh->protocol) != ETH_P_IP)
{
printf("Not a IP packet\n");
continue;
}
printf("Ethernet header info:\n");
print_ether_header_info(etherh);
// Parse outer IP header
struct iphdr *outer_iph = (struct iphdr *)(buffer + sizeof(struct etherhdr));
if (outer_iph->protocol != IPPROTO_GRE)
{
printf("Not a GRE packet");
continue;
}
printf("outer IP header info:\n");
print_ip_header_info(outer_iph);
// Parse gre header
struct grehdr *greh = (struct grehdr *)(buffer + sizeof(struct etherhdr) + sizeof(struct iphdr));
print_gre_header_info(greh);
// Parse inner IP header
struct iphdr *inner_iph = (struct iphdr *)(buffer + sizeof(struct etherhdr) + sizeof(struct iphdr) + sizeof(struct grehdr));
if (inner_iph->protocol != IPPROTO_TCP)
{
printf("Not a TCP packet inside GRE\n");
continue;
}
printf("\ninner IP header info:\n");
print_ip_header_info(inner_iph);
// Parse TCP header
struct tcphdr *tcph = (struct tcphdr *)(buffer + sizeof(struct etherhdr) + sizeof(struct iphdr) + sizeof(struct grehdr) + sizeof(struct iphdr));
printf("\nTCP header info:\n");
printf("source port: %u, destination port: %u\n", ntohs(tcph->source), ntohs(tcph->dest));
printf("SYN = %u, ACK = %u, FIN = %u\n", tcph->syn, tcph->ack, tcph->fin);
// print payload
print_payload(len, buffer);
}
close(sock_raw);
return 0;
}
运行结果
先使用gcc -o client client.c对代码进行编译。
客户端能够指定发送端口、目的端口和窗口大小
服务端能够接收数据包信息并解析
Wireshark抓包结果
命令行参数释义
Options:
-d Set the Destination TCP port
-s Set Source TCP port
-w TCP window size
tcpdump抓包工具
linux默认安装的抓包工具,-s 0或者-S设置不进行截断,即获取完整的数据包
tcpdump -i ens33 # 抓取指定网口的包, lo 抓取回环网口的包
tcpdump -n #数字显示主机及端口,例如将ubuntu01更换为192.168.112.128
tcpdump host 192.168.112.128 #获取192.168.112.128收到和发出的所有数据包
tcpdump src host 192.168.112.128
tcpdump dst host 192.168.112.128 #指定目的地址
ether ip arp #只捕获某数据包
-v -vv #显示详细信息
-X -XX #数据包内容十六进制 + ASCII形式输出;所有内容格式化输出
-w capture.pcap #将内容保存到文件中
#常用命令
tcpdump -i ens33 'not arp' dst host 192.168.112.128 -n -XX -w capture.pcap
tcpdump -i any host 192.168.112.129 and host 192.168.112.128 -n -XX -w capture.pcap
#可以查看头部的相关信息,指定json格式查看
tshark -r capture.pcap -T json
-
-n
: 不对IP地址进行域名解析。这意味着在输出中,IP地址将以数字形式显示,而不是尝试解析为对应的主机名。这可以加快输出速度,尤其是在网络环境较大或DNS解析有问题时。 -
-nn
: 进一步扩展了-n
选项,不仅不解析IP地址,也不解析端口号为服务名称。通常,当tcpdump输出中涉及端口时,它会尝试将其转换为对应的服务名称(比如,端口80转换为'http')。使用-nn
后,端口号将以数字形式显示,而不是服务名称,使得输出更加简洁且易于机器解析。 -
-e
: 显示链路层头部信息。这在数据包分析中很有用,因为它可以展示物理层(如以太网)的源MAC地址和目标MAC地址等信息,这对于跟踪网络流量的物理走向或排查低级别网络问题非常关键。