Linux中的原始套接字发送/接收数据包

10

遇到接收数据报的问题。 我可以接收和读取传入的数据报,但是我认为我没有与任何主机进行握手。 我只想向远程计算机发送一个数据包,并在接收到答案时查看TTL(生存时间)和窗口大小。 有人知道错误出在哪里吗?

代码:

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>

struct pseudohdr {
    u_int32_t src_addr;
    u_int32_t dst_addr;
    u_int8_t padding;
    u_int8_t proto;
    u_int16_t length;
};

struct data_4_checksum {
    struct pseudohdr pshd;
    struct tcphdr tcphdr;
    char payload[1024];
};
unsigned short comp_chksum(unsigned short *addr, int len) {
    long sum = 0;

    while (len > 1) {
        sum += *(addr++);
        len -= 2;
    }

    if (len > 0)
        sum += *addr;

    while (sum >> 16)
        sum = ((sum & 0xffff) + (sum >> 16));

    sum = ~sum;

    return ((u_short) sum);

}

int main(int argc, char *argv[]) {

    int sock, bytes, on = 1;
    char buffer[1024];
    struct iphdr *ip;
    struct tcphdr *tcp;
    struct sockaddr_in to;
    struct pseudohdr pseudoheader;
    struct data_4_checksum tcp_chk_construct;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s ", argv[0]);
        fprintf(stderr, "<dest-addr>\n");
        return 1;
    }

    sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
    if (sock == -1) {
        perror("socket() failed");
        return 1;
    }else{
        printf("socket() ok\n");
    }

    if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) == -1) {
        perror("setsockopt() failed");
        return 2;
    }else{
        printf("setsockopt() ok\n");
    }

    ip = (struct iphdr*) buffer;
    tcp = (struct tcphdr*) (buffer + sizeof(struct tcphdr));

    int iphdrlen = sizeof(struct iphdr);
    int tcphdrlen = sizeof(struct tcphdr);
    int datalen = 0;
    printf("Typecasting ok\n");

    ip->frag_off = 0;
    ip->version = 4;
    ip->ihl = 5;
    ip->tot_len = htons(iphdrlen + tcphdrlen);
    ip->id = 0;
    ip->ttl = 40;
    ip->protocol = IPPROTO_TCP;
    ip->saddr = inet_addr("192.168.165.135");
    ip->daddr = inet_addr(argv[1]);
    ip->check = 0;

    tcp->source     = htons(12345);
    tcp->dest       = htons(80);
    tcp->seq        = random();
    tcp->doff       = 5;
    tcp->ack        = 0;
    tcp->psh        = 0;
    tcp->rst        = 0;
    tcp->urg        = 0;
    tcp->syn        = 1;
    tcp->fin        = 0;
    tcp->window     = htons(65535);

    pseudoheader.src_addr = ip->saddr;
    pseudoheader.dst_addr = ip->daddr;
    pseudoheader.padding = 0;
    pseudoheader.proto = ip->protocol;
    pseudoheader.length = htons(tcphdrlen + datalen);

    tcp_chk_construct.pshd = pseudoheader;
    tcp_chk_construct.tcphdr = *tcp;

    int checksum = comp_chksum((unsigned short*) &tcp_chk_construct,
            sizeof(struct pseudohdr) + tcphdrlen + datalen);

    tcp->check = checksum;

    printf("TCP Checksum: %i\n", checksum);
    printf("Destination : %i\n", ntohs(tcp->dest));
    printf("Source: %i\n", ntohs(tcp->source));

    to.sin_addr.s_addr = ip->daddr;
    to.sin_family = AF_INET;
    to.sin_port = tcp->dest;

    bytes = sendto(sock, buffer, ntohs(ip->tot_len), 0, (struct sockaddr*) &to,
            sizeof(to));

    if (bytes == -1) {
        perror("sendto() failed");
        return 1;
    }

    recv(sock, buffer, sizeof(buffer), 0);
    printf("TTL= %d\n", ip->ttl);
    printf("Window= %d\n", tcp->window);
    printf("ACK= %d\n", tcp->ack);
    printf("%s:%d\t --> \t%s:%d \tSeq: %d \tAck: %d\n",
                    inet_ntoa(*(struct in_addr*) &ip->saddr), ntohs(tcp->source),
                    inet_ntoa(*(struct in_addr *) &ip->daddr), ntohs(tcp->dest),
                    ntohl(tcp->seq), ntohl(tcp->ack_seq));

    return 0;
}

我指的是传入的数据包,也就是说,有其他数据包从各个地方进来,但不是我需要的。 - x4k3p
1
为什么要解析数据包?我试图在VMware下的一个干净的Unix系统中获得一些响应。当我手动发送一个数据包时,我得到了我需要的东西。但如果我只是等待,什么都不会发生。在真实的系统中,有数据包进来。但这些是从其他地方来的。 - x4k3p
我刚刚将我的评论重新发布为实际答案。希望你不介意。 - jweyrich
我只使用C语言工作了几周。这就是为什么对我来说有点困难的原因。 - x4k3p
理解了,但你做得很好。请看一下我的回答中的第4点。 - jweyrich
显示剩余7条评论
2个回答

13
  1. 您正在从buffer中接收和存储数据包,但是在打印iptcp的数据时,没有解析该缓冲区。您应该在接收数据包后,在打印之前buffer中解析数据包。
  2. 您的代码假设所有数据包都是TCP协议,这并不是实际情况。原始套接字只支持第3层协议(IP、ICMP等)。换句话说,在创建原始套接字时使用IPPROTO_TCP是具有误导性的。请坚持使用IPPROTO_IP,并为您关心的每个协议(TCP、UDP等)添加必要的条件。这在Linux内核验证协议编号并回退到IPPROTO_IP的情况下可以工作。然而,这可能在其他系统上无法正常工作。
  3. 请检查您的网络通信是否使用了正确的字节序。网络字节序为大端序,而主机字节序取决于您的架构,因此您可能需要来回转换多字节字段。
  4. tcp->seq可能具有无效值,因为TCP只接受最大值为65535的值,而random()返回0到RAND_MAX(0x7fffffff)之间的值。尝试tcp->seq = htonl(random() % 65535);
  5. 您的TCP头偏移量计算不正确。它应该是sizeof(struct iphdr)而不是sizeof(struct tcphdr)

使用原始套接字是否可以接收TCP或UDP数据包?我认为这些传入的数据包总是被传递给TCP和UDP驱动程序。 - Barmar
1
@Barmar 可以的。内核将每个数据报的副本传递给已创建与数据报协议匹配的 RAW 套接字的每个进程。 - jweyrich
发现错误了!是缓冲区的问题,因为我向远程主机发送了一些垃圾数据。因此我无法接收到回复。更正:char buffer[1024]={0}; - x4k3p
3
关于您所提到的第二点,在 Linux 上调用 socket() 时,尝试使用 IPPROTO_IP 会导致错误提示 不支持的协议(Protocol not supported)。使用 IPPROTO_TCPIPPROTO_RAW 却可以正常工作,尽管我没有检查它是否过滤了 TCP 数据包。 - Benno
@jweyrich,ICMP是第三层协议吗? - Pacerier
@Pacerier 是的。它被封装在IPv4/6数据包中。它是IP的一部分。 - jweyrich

1
ip = (struct iphdr*) buffer;
tcp = (struct tcphdr*) (buffer + sizeof(struct tcphdr)); //This is wrong

在这里,要获取buffer中TCP标头的数组索引,您需要像下面提到的那样将sizeof(struct iphdr)添加到buffer中。

ip = (struct iphdr*) buffer;
tcp = (struct tcphdr*) (buffer + sizeof(struct iphdr)); //This is correct

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接