C语言实现手动封装数据包发送并解析

本文章的学习旨在完成手动封装以太网头部,外层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地址等信息,这对于跟踪网络流量的物理走向或排查低级别网络问题非常关键。

  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值