linux udp raw socket

tcp/udp網絡通信與socket實際上是兩個概念,不過因爲我們平常使用tcp/udp,不可避免的使用socket,所以認爲兩者是同一個事物。

我們現在所說的或者最常用到的都是BSD版本的socket。socket是對tcp/udp等網絡協議的封裝,提供上層接口,供我們使用,可以編寫程序在網絡間傳遞數據。

tcp/ip是一種協議標準,規定了數據如何傳輸,socket相當於對這個標準的實現。

https://www.rfc-editor.org/rfc/rfc147.html

https://man7.org/linux/man-pages/man2/socket.2.html

raw socket 原生套接字

使用linux開發udp socket程序時,我們只需要接收發送數據,並不需要關心每個數據包的頭如何組裝、MTU大小是多少、IP如何分片,這都是底層socket實現的。如果需要對收發數據包的頭進行操作,linux提供了原生套接字,可以獲取到完整的數據包——完整的數據包包括以太網頭->IP頭->TCP/UDP頭->負載數據。

使用原生套接字需要root權限

TCP/IP層原生套接字發送數據

linux提供的原生套接字功能,分兩部分,一部分是獲取到TCP/IP層,會屏蔽上層的以太網層的頭部信息,也會屏蔽當前IP層分片等信息,也就是獲取到的數據是處理過的,並不是真正原始的數據包,不過當前數據包包含IP層的頭和TCP/UDP層的頭;但是發送數據又不會幫我們自動分片。另一部分是獲取原始數據包,也就是網絡上數據包是什麼樣,就會獲取到什麼樣的數據,包括以太網層的頭,也就是真正原始的數據包。

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/udp.h>

#define PCKT_LEN 8192

unsigned short csum(unsigned short *buf, int nwords)
{
    unsigned long sum;
    for (sum = 0; nwords > 0; nwords--)
    {
        sum += *buf++;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return (unsigned short) (~sum);
}

int main(int argc, char const *argv[])
{
    u_int16_t src_port, dst_port;
    u_int32_t src_addr, dst_addr;
    src_addr = inet_addr("192.168.10.111");
    dst_addr = inet_addr("192.168.10.112");
    src_port = atoi("1234");
    dst_port = atoi("1234");

    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(dst_port);
    sin.sin_addr.s_addr = dst_addr;

    int sd;
    // 接收數據的緩衝區
    char buffer[PCKT_LEN];
    memset(buffer, 0, PCKT_LEN);
    // 數據包含ip頭和udp頭,所以使用linux提供的ip頭和udp頭的結構體映射數據
    struct iphdr *ip = (struct iphdr *) buffer;
    struct udphdr *udp = (struct udphdr *) (buffer + sizeof(struct iphdr));

    // 創建socket
    // AF_INET表示ipv4協議
    // SOCK_RAW表示原生socket
    // IPPROTO_UDP表示接收udp協議
    sd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sd < 0)
    {
        perror("socket() error");
        exit(-1);
    }

    int optval = 1;
    // 設置socket,由我們管理ip頭和udp頭
    // sd表示要設置的socket id
    // IPPROTO_IP表示設置的是IP層
    // IP_HDRINCL表示由用戶層管理tcp/ip層的頭
    // &optval 設置IP_HDRINCL的數值,這裏1表示打開,傳遞的是設置數值的指針
    // sizeof(optval) 設置參數數值的大小,因爲上面傳遞的是指針,這裏就要給定大小,不然不知道設置的內容有多長
    if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &optval, sizeof(optval)) < 0)
    {
        perror("setsockopt error");
        exit(-1);
    }

    // 設置ip的頭
    ip->ihl = 5;
    ip->version = 4;
    ip->tos = 16;
    ip->id = htons(54321);
    ip->ttl = 64;
    ip->protocol = 17;
    // 設置ip頭中源ip的地址,這裏就可以隨意修改,很多僞造數據包攻擊(比如反射攻擊)就是用到這種方法
    ip->saddr = src_addr;
    ip->daddr = dst_addr;

    // 設置udp頭
    udp->source = htons(src_port);
    udp->dest = htons(dst_port);
    // 設置ip的check位,有ip協議得知,這裏只需要校驗頭,不包括payload
    ip->check = csum((unsigned short *) buffer, sizeof(struct iphdr) + sizeof(struct udphdr));
    int sendbufflen = 0;
    // 從udp頭之後,填充用戶數據,這裏纔是我們使用普通 socket發送數據填充的地方
    unsigned short *mbuffer = buffer + sizeof(struct iphdr) + sizeof(struct udphdr);
    int datalen = 1000;
    for (int i = 0; i < datalen; i++)
    {
        *(mbuffer + i) = (unsigned short) i;
    }
    sendbufflen = sizeof(struct iphdr) + sizeof(struct udphdr) + datalen;
    // 設置ip和udp中記錄數據包長度的字段。注意需要使用htons,把本地字節序轉爲網絡字節序
    ip->tot_len = htons(sendbufflen);
    udp->len = htons(sizeof(struct udphdr) + datalen);
    // 發送數據
    if (sendto(sd, buffer, sendbufflen, 0, (struct sockaddr *) &sin, sizeof(sin)) < 0)
    {
        perror("sendto error");
        exit(-1);
    }

    close(sd);
    return 0;
}

這樣我們就可以僞造一份網絡數據包,並且發送到指定的服務器和端口。通過wireshark等抓包可以看到源ip地址並不是我們機器的地址,而是指定的地址。
https://man7.org/linux/man-pages/man3/setsockopt.3p.html

AF_INET與PF_INET

https://man7.org/linux/man-pages/man2/socket.2.html

有時候我們發現代碼中有用AF_INET,也有用PF_INET,這兩者有什麼區別呢?實際上官方文檔已經給了說明:

HISTORY         top

       POSIX.1-2001, 4.4BSD.

       socket() appeared in 4.2BSD.  It is generally portable to/from
       non-BSD systems supporting clones of the BSD socket layer
       (including System V variants).

       The manifest constants used under 4.x BSD for protocol families
       are PF_UNIX, PF_INET, and so on, while AF_UNIX, AF_INET, and so
       on are used for address families.  However, already the BSD man
       page promises: "The protocol family generally is the same as the
       address family", and subsequent standards use AF_* everywhere.

現在用的socket基本上都是有BSD版本遷移而來,當時設定根據協議簇和地址簇進行區分,但是最後沒有實現,PF_*AF_*現在是相等的,所以用AF_INETPF_INET是一樣的,不過建議使用AF_*

raw - Linux IPv4 raw sockets

IP_HDRINCL

設置了IP_HDRINCL後,有些ip頭的數據系統是可以幫我們填充的:

┌───────────────────────────────────────────────────┐
│IP Header fields modified on sending by IP_HDRINCL │
├──────────────────────┬────────────────────────────┤
│IP Checksum           │ Always filled in           │
├──────────────────────┼────────────────────────────┤
│Source Address        │ Filled in when zero        │
├──────────────────────┼────────────────────────────┤
│Packet ID             │ Filled in when zero        │
├──────────────────────┼────────────────────────────┤
│Total Length          │ Always filled in           │
└──────────────────────┴────────────────────────────┘

其他設置ip頭的方式

按照官方文檔所說,從linux 2.2起,ip頭所有的字段都可以通過ip socket設置進行修改,也就是原生socket只需用在新的協議或者用戶層無法控制的協議,比如ICMP。

Starting with Linux 2.2, all IP header fields and options can be
set using IP socket options. This means raw sockets are usually
needed only for new protocols or protocols with no user interface
(like ICMP).

https://man7.org/linux/man-pages/man7/raw.7.html

sendto error: Message too long

如果使用了IP_HDRINCL,自己進行填充ip頭,就會遇到這個問題,因爲系統發送數據會被MTU限制(大部分是1500個字節),超過了需要分片,但是設置了IP_HDRINCL,系統就不會自動幫你分片了,所以需要手動分片。

有人說獲取udp數據最大是65535,這句話本身是正確的,udp協議規則一個udp包最大是65535個字節,但是網絡數據包受到MTU的限制,導致ip數據包並不會這麼大,udp被ip數據包封裝,所以也同樣受到ip數據包大小的限制,這纔有了IP分片(IP Fragement),這也是爲什麼有IP分片,而沒有tcp或者udp分片。使用普通的socket通信,底層系統會自動分片,也會自動組裝分片,所以接收和發送的最大長度可以是udp協議設定的65535個字節。

雖然官方文檔說設置IP_MTU_DISCOVER可以控制對大於MTU的包分片,但是測試下來,針對UDP,並沒有效果。

https://man7.org/linux/man-pages/man7/ip.7.html
https://man7.org/linux/man-pages/man7/raw.7.html

BUGS         top

       Transparent proxy extensions are not described.

       When the IP_HDRINCL option is set, datagrams will not be
       fragmented and are limited to the interface MTU.

       Setting the IP protocol for sending in sin_port got lost in Linux
       2.2.  The protocol that the socket was bound to or that was
       specified in the initial socket(2) call is always used.

TCP/IP層原生套接字接收數據

接收一個原生socket數據,並且修改部分信息,再發送給另一個ip

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/udp.h>

#define PCKT_LEN 8192

unsigned short csum(unsigned short *buf, int nwords)
{
    unsigned long sum;
    for (sum = 0; nwords > 0; nwords--)
    {
        sum += *buf++;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return (unsigned short) (~sum);
}

int main(int argc, char const *argv[])
{
    u_int16_t src_port, dst_port;
    u_int32_t src_addr, dst_addr;
    src_addr = inet_addr("192.168.10.111");
    dst_addr = inet_addr("192.168.10.112");
    src_port = atoi("1234");
    dst_port = atoi("1234");

    // 創建原生接收socket
    int recvsd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (recvsd < 0)
    {
        perror("recvsd");
        return -1;
    }

    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(dst_port);
    sin.sin_addr.s_addr = dst_addr;

    int sd;
    // 接收數據的緩衝區
    char buffer[PCKT_LEN];
    memset(buffer, 0, PCKT_LEN);

    // 創建socket
    // AF_INET表示ipv4協議
    // SOCK_RAW表示原生socket
    // IPPROTO_UDP表示接收udp協議
    sd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sd < 0)
    {
        perror("socket() error");
        exit(-1);
    }

    int optval = 1;
    // 設置socket,由我們管理ip頭和udp頭
    // sd表示要設置的socket id
    // IPPROTO_IP表示設置的是IP層
    // IP_HDRINCL表示由用戶層管理tcp/ip層的頭
    // &optval 設置IP_HDRINCL的數值,這裏1表示打開,傳遞的是設置數值的指針
    // sizeof(optval) 設置參數數值的大小,因爲上面傳遞的是指針,這裏就要給定大小,不然不知道設置的內容有多長
    if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &optval, sizeof(optval)) < 0)
    {
        perror("setsockopt error");
        exit(-1);
    }

    int recvlen = 0;
    // 接收數據
    // 這裏把接收保存的地址設置爲NULL,所以地址長度也指定爲0,是因爲這裏使用不到,我們在下面sendto的時候指定了一個其他地址
    recvlen = recvfrom(recvsd, buffer, PCKT_LEN, 0, NULL, 0);
    if (recvlen <= 0)
    {
        perror("recv error");
        exit(-1);
    }

    // 數據包含ip頭和udp頭,所以使用linux提供的ip頭和udp頭的結構體映射數據
    struct iphdr *ip = (struct iphdr *) buffer;
    struct udphdr *udp = (struct udphdr *) (buffer + sizeof(struct iphdr));
    // 這裏只做了部分修改,如果想修改其他的,可以任意修改對應的變量
    ip->saddr = src_addr;
    ip->daddr = dst_addr;

    // 設置ip的check位,有ip協議得知,這裏只需要校驗頭,不包括payload
    ip->check = csum((unsigned short *) buffer, sizeof(struct iphdr) + sizeof(struct udphdr));

    // 發送數據
    if (sendto(sd, buffer, recvlen, 0, (struct sockaddr *) &sin, sizeof(sin)) < 0)
    {
        perror("sendto error");
        exit(-1);
    }

    close(sd);
    close(recvsd);
    return 0;
}

上面的代碼我們會發現一個不一樣的地方,就是沒有bind,而可以直接接收數據,因爲當前設定的原生socket是ip層,指定端口是tcp/udp層,所以原生socket不支持bind端口,即使綁定了,也不會生效。

但是我們可以通過bind綁定地址,也可以通過SO_BINDTODEVICE設定網卡名稱。

https://man7.org/linux/man-pages/man7/raw.7.html

以太網層接收原數據

當到了以太網層後,也就是數據的最原始的一層(再上一層是鏈路層),就沒有tcp/udp層協議之說了,只能指定ip層的協議。

#include<stdio.h>
#include<malloc.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/if_ether.h>
#include<netinet/ip.h>
#include<netinet/udp.h>
#include<netinet/tcp.h>
#include<arpa/inet.h>
#include <unistd.h>

FILE *log_txt;
int iphdrlen;

struct sockaddr saddr;
struct sockaddr_in source, dest;

void ethernet_header(unsigned char *buffer)
{
    struct ethhdr *eth = (struct ethhdr *) (buffer);
    fprintf(log_txt, "\nEthernet Header\n");
    fprintf(log_txt, "\t|-Source Address	: %.2X-%.2X-%.2X-%.2X-%.2X-%.2X\n", eth->h_source[0], eth->h_source[1],
            eth->h_source[2], eth->h_source[3], eth->h_source[4], eth->h_source[5]);
    fprintf(log_txt, "\t|-Destination Address	: %.2X-%.2X-%.2X-%.2X-%.2X-%.2X\n", eth->h_dest[0], eth->h_dest[1],
            eth->h_dest[2], eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]);
    fprintf(log_txt, "\t|-Protocol		: %d\n", eth->h_proto);
}

void ip_header(unsigned char *buffer)
{
    struct iphdr *ip = (struct iphdr *) (buffer + sizeof(struct ethhdr));
    iphdrlen = ip->ihl * 4;
    memset(&source, 0, sizeof(source));
    source.sin_addr.s_addr = ip->saddr;
    memset(&dest, 0, sizeof(dest));
    dest.sin_addr.s_addr = ip->daddr;
    fprintf(log_txt, "\nIP Header\n");
    fprintf(log_txt, "\t|-Version              : %d\n", (unsigned int) ip->version);
    fprintf(log_txt, "\t|-Internet Header Length  : %d DWORDS or %d Bytes\n", (unsigned int) ip->ihl,
            ((unsigned int) (ip->ihl)) * 4);
    fprintf(log_txt, "\t|-Type Of Service   : %d\n", (unsigned int) ip->tos);
    fprintf(log_txt, "\t|-Total Length      : %d  Bytes\n", ntohs(ip->tot_len));
    fprintf(log_txt, "\t|-Identification    : %d\n", ntohs(ip->id));
    fprintf(log_txt, "\t|-Time To Live	    : %d\n", (unsigned int) ip->ttl);
    fprintf(log_txt, "\t|-Protocol 	    : %d\n", (unsigned int) ip->protocol);
    fprintf(log_txt, "\t|-Header Checksum   : %d\n", ntohs(ip->check));
    fprintf(log_txt, "\t|-Source IP         : %s\n", inet_ntoa(source.sin_addr));
    fprintf(log_txt, "\t|-Destination IP    : %s\n", inet_ntoa(dest.sin_addr));
}

void payload(unsigned char *buffer, size_t buflen)
{
    unsigned char *data = (buffer + iphdrlen + sizeof(struct ethhdr) + sizeof(struct udphdr));
    fprintf(log_txt, "\nData\n");
    int remaining_data = buflen - (iphdrlen + sizeof(struct ethhdr) + sizeof(struct udphdr));
    for (int i = 0; i < remaining_data; i++)
    {
        if (i != 0 && i % 16 == 0)
            fprintf(log_txt, "\n");
        fprintf(log_txt, " %.2X ", data[i]);
    }
    fprintf(log_txt, "\n");
}

void tcp_header(unsigned char *buffer, size_t buflen)
{
    fprintf(log_txt, "\n*************************TCP Packet******************************");
    ethernet_header(buffer);
    ip_header(buffer);

    struct tcphdr *tcp = (struct tcphdr *) (buffer + iphdrlen + sizeof(struct ethhdr));
    fprintf(log_txt, "\nTCP Header\n");
    fprintf(log_txt, "\t|-Source Port          : %u\n", ntohs(tcp->source));
    fprintf(log_txt, "\t|-Destination Port     : %u\n", ntohs(tcp->dest));
    fprintf(log_txt, "\t|-Sequence Number      : %u\n", ntohl(tcp->seq));
    fprintf(log_txt, "\t|-Acknowledge Number   : %u\n", ntohl(tcp->ack_seq));
    fprintf(log_txt, "\t|-Header Length        : %d DWORDS or %d BYTES\n", (unsigned int) tcp->doff, (unsigned int) tcp->doff * 4);
    fprintf(log_txt, "\t|----------Flags-----------\n");
    fprintf(log_txt, "\t\t|-Urgent Flag          : %d\n", (unsigned int) tcp->urg);
    fprintf(log_txt, "\t\t|-Acknowledgement Flag : %d\n", (unsigned int) tcp->ack);
    fprintf(log_txt, "\t\t|-Push Flag            : %d\n", (unsigned int) tcp->psh);
    fprintf(log_txt, "\t\t|-Reset Flag           : %d\n", (unsigned int) tcp->rst);
    fprintf(log_txt, "\t\t|-Synchronise Flag     : %d\n", (unsigned int) tcp->syn);
    fprintf(log_txt, "\t\t|-Finish Flag          : %d\n", (unsigned int) tcp->fin);
    fprintf(log_txt, "\t|-Window size          : %d\n", ntohs(tcp->window));
    fprintf(log_txt, "\t|-Checksum             : %d\n", ntohs(tcp->check));
    fprintf(log_txt, "\t|-Urgent Pointer       : %d\n", tcp->urg_ptr);

    payload(buffer, buflen);

    fprintf(log_txt, "*****************************************************************\n\n\n");
}

void udp_header(unsigned char *buffer, size_t buflen)
{
    fprintf(log_txt, "\n*************************UDP Packet******************************");
    ethernet_header(buffer);
    ip_header(buffer);
    fprintf(log_txt, "\nUDP Header\n");

    struct udphdr *udp = (struct udphdr *) (buffer + iphdrlen + sizeof(struct ethhdr));
    fprintf(log_txt, "\t|-Source Port    	: %d\n", ntohs(udp->source));
    fprintf(log_txt, "\t|-Destination Port	: %d\n", ntohs(udp->dest));
    fprintf(log_txt, "\t|-UDP Length      	: %d\n", ntohs(udp->len));
    fprintf(log_txt, "\t|-UDP Checksum   	: %d\n", ntohs(udp->check));

    payload(buffer, buflen);

    fprintf(log_txt, "*****************************************************************\n\n\n");
}

void data_process(unsigned char *buffer, size_t buflen)
{
    struct iphdr *ip = (struct iphdr *) (buffer + sizeof(struct ethhdr));
    switch (ip->protocol)
    {
    case 6:
        tcp_header(buffer, buflen);
        break;

    case 17:
        udp_header(buffer, buflen);
        break;
    }
}

int main()
{
    int sock_r, saddr_len;
    size_t buflen = 0;
    unsigned char *buffer = (unsigned char *) malloc(65536);
    memset(buffer, 0, 65536);
    log_txt = fopen("log.txt", "w");
    if (!log_txt)
    {
        printf("unable to open log.txt\n");
        return -1;
    }
    sock_r = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
    if (sock_r < 0)
    {
        printf("error in socket\n");
        return -1;
    }
    while (1)
    {
        saddr_len = sizeof(saddr);
        buflen = recvfrom(sock_r, buffer, 65536, 0, &saddr, (socklen_t *) &saddr_len);
        if (buflen < 0)
        {
            printf("error in reading recvfrom function\n");
            return -1;
        }
        fflush(log_txt);
        data_process(buffer, buflen);
    }
    close(sock_r);
}

https://man7.org/linux/man-pages/man7/packet.7.html

https://www.opensourceforu.com/2015/03/a-guide-to-using-raw-sockets/

以太網層發送原數據

#include<stdio.h>
#include<string.h>
#include<malloc.h>
#include<sys/socket.h>
#include<sys/ioctl.h>

#include<net/if.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<netinet/if_ether.h>
#include<netinet/udp.h>

#include<linux/if_packet.h>

#include<arpa/inet.h>

struct ifreq ifreq_c, ifreq_i, ifreq_ip;
int sock_raw;
unsigned char *sendbuff;

#define DESTMAC0    0xd0
#define DESTMAC1    0x67
#define DESTMAC2    0xe5
#define DESTMAC3    0x12
#define DESTMAC4    0x6f
#define DESTMAC5    0x8f

int total_len = 0, send_len;

void get_eth_index()
{
    memset(&ifreq_i, 0, sizeof(ifreq_i));
    strncpy(ifreq_i.ifr_name, "你的網卡名稱", IFNAMSIZ - 1);
    // 獲取網卡信息
    if ((ioctl(sock_raw, SIOCGIFINDEX, &ifreq_i)) < 0)
    {
        printf("error in index ioctl reading");
    }
}

void get_mac()
{
    memset(&ifreq_c, 0, sizeof(ifreq_c));
    strncpy(ifreq_c.ifr_name, "wlan0", IFNAMSIZ - 1);
    // 獲取網卡mac地址
    if ((ioctl(sock_raw, SIOCGIFHWADDR, &ifreq_c)) < 0)
    {
        printf("error in SIOCGIFHWADDR ioctl reading");
    }
    // 設置網卡mac地址
    struct ethhdr *eth = (struct ethhdr *) (sendbuff);
    eth->h_source[0] = (unsigned char) (ifreq_c.ifr_hwaddr.sa_data[0]);
    eth->h_source[1] = (unsigned char) (ifreq_c.ifr_hwaddr.sa_data[1]);
    eth->h_source[2] = (unsigned char) (ifreq_c.ifr_hwaddr.sa_data[2]);
    eth->h_source[3] = (unsigned char) (ifreq_c.ifr_hwaddr.sa_data[3]);
    eth->h_source[4] = (unsigned char) (ifreq_c.ifr_hwaddr.sa_data[4]);
    eth->h_source[5] = (unsigned char) (ifreq_c.ifr_hwaddr.sa_data[5]);
    // 設置目的網卡地址,這裏隨便設置的,如果使用需要確定設置正確,不然數據發送出去目的服務器有可能會丟掉該數據包
    eth->h_dest[0] = DESTMAC0;
    eth->h_dest[1] = DESTMAC1;
    eth->h_dest[2] = DESTMAC2;
    eth->h_dest[3] = DESTMAC3;
    eth->h_dest[4] = DESTMAC4;
    eth->h_dest[5] = DESTMAC5;

    eth->h_proto = htons(ETH_P_IP);

    total_len += sizeof(struct ethhdr);
}

void get_data()
{
    // 設置發送的數據
    sendbuff[total_len++] = 0xAA;
    sendbuff[total_len++] = 0xBB;
    sendbuff[total_len++] = 0xCC;
    sendbuff[total_len++] = 0xDD;
    sendbuff[total_len++] = 0xEE;
}

// 設置udp的頭
void get_udp()
{
    struct udphdr *uh = (struct udphdr *) (sendbuff + sizeof(struct iphdr) + sizeof(struct ethhdr));

    uh->source = htons(23451);
    uh->dest = htons(23452);
    uh->check = 0;

    total_len += sizeof(struct udphdr);
    get_data();
    uh->len = htons((total_len - sizeof(struct iphdr) - sizeof(struct ethhdr)));
}

unsigned short checksum(unsigned short *buff, int _16bitword)
{
    unsigned long sum;
    for (sum = 0; _16bitword > 0; _16bitword--)
    {
        sum += htons(*(buff)++);
    }
    do
    {
        sum = ((sum >> 16) + (sum & 0xFFFF));
    } while (sum & 0xFFFF0000);

    return (~sum);
}

void get_ip()
{
    memset(&ifreq_ip, 0, sizeof(ifreq_ip));
    strncpy(ifreq_ip.ifr_name, "你的網卡名稱", IFNAMSIZ - 1);
    // 獲取當前網卡ip地址
    if (ioctl(sock_raw, SIOCGIFADDR, &ifreq_ip) < 0)
    {
        printf("error in SIOCGIFADDR \n");
    }

    struct iphdr *iph = (struct iphdr *) (sendbuff + sizeof(struct ethhdr));
    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 16;
    iph->id = htons(10201);
    iph->ttl = 64;
    iph->protocol = 17;
    iph->saddr = inet_addr(inet_ntoa((((struct sockaddr_in *) &(ifreq_ip.ifr_addr))->sin_addr)));
    iph->daddr = inet_addr("目的ip地址");
    total_len += sizeof(struct iphdr);
    get_udp();

    iph->tot_len = htons(total_len - sizeof(struct ethhdr));
    iph->check = htons(checksum((unsigned short *) (sendbuff + sizeof(struct ethhdr)), (sizeof(struct iphdr) / 2)));
}

int main()
{
    sock_raw = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
    if (sock_raw == -1)
    {
        printf("error in socket");
        return -1;
    }

    sendbuff = (unsigned char *) malloc(64);
    memset(sendbuff, 0, 64);
    // 構造數據包
    get_eth_index();
    get_mac();
    get_ip();
    // 構造發送地址信息,由於使用的是以太網層的socket,所以地址只能是物理地址,也就是mac地址
    struct sockaddr_ll sadr_ll;
    sadr_ll.sll_ifindex = ifreq_i.ifr_ifindex;
    sadr_ll.sll_halen = ETH_ALEN;
    sadr_ll.sll_addr[0] = DESTMAC0;
    sadr_ll.sll_addr[1] = DESTMAC1;
    sadr_ll.sll_addr[2] = DESTMAC2;
    sadr_ll.sll_addr[3] = DESTMAC3;
    sadr_ll.sll_addr[4] = DESTMAC4;
    sadr_ll.sll_addr[5] = DESTMAC5;

    while (1)
    {
        send_len = sendto(sock_raw, sendbuff, 64, 0, (const struct sockaddr *) &sadr_ll, sizeof(struct sockaddr_ll));
        if (send_len < 0)
        {
            printf("error in send");
            return -1;
        }
    }
}

https://linux.die.net/man/7/netdevice
https://www.opensourceforu.com/2015/03/a-guide-to-using-raw-sockets/
https://man7.org/linux/man-pages/man7/packet.7.html
https://man7.org/linux/man-pages/man7/raw.7.html
https://linux.die.net/man/7/netdevice

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章