Raw Socket Linux 发送/接收数据包
Posted
技术标签:
【中文标题】Raw Socket Linux 发送/接收数据包【英文标题】:Raw Socket Linux send/receive a packet 【发布时间】:2012-12-14 19:50:05 【问题描述】:在接收数据包时遇到一些问题。
我可以接收和读取传入的数据包,但我认为我没有与任何主机握手。
我只想在收到答案时向具有开放端口的远程计算机发送一个数据包,以查看 TTL(生存时间)和窗口大小。
有谁知道错误在哪里? (我对C编程的了解不是很深)
代码:
#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;
【问题讨论】:
对于传入的数据包,我的意思是,还有其他来自任何地方的传入,但不是来自我需要的 为什么要解析数据包?我试图在 vmware 下的干净的 unix 机器上得到一些响应。当我手动发送一个数据包时,我得到了我需要的东西。但如果我只是等待什么都不会发生。在真实系统下,有数据包进来。但这些来自其他地方。 我刚刚重新发布了我的 cmets 作为实际答案。希望你不要介意。 第 2 层 => 数据链路?我认为第 2 层有数据包套接字。在我的代码中,我使用类型转换以正确的方式访问缓冲区。你不明白我的问题。我的问题是,我正在接收数据包。程序可以运行,但我没有收到我发送的数据包的答复。 root@***:/********# ./OS_Sniffer 141.28.78.142 【参考方案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
【讨论】:
【参考方案2】:-
您在
buffer
中接收和存储数据包,但您在不解析该缓冲区的情况下打印来自ip
和tcp
的数据。您应该在收到它之后和在打印之前解析来自buffer
的数据包。
您的代码假定所有数据包都是 TCP,但事实并非如此。 RAW 套接字仅支持第 3 层协议(IP、ICMP 等)。换句话说,在创建 RAW 套接字时使用 IPPROTO_TCP
会产生误导。坚持IPPROTO_IP
,并为您关心的每个协议(TCP、UDP 等)在您的代码中添加必要的条件。这恰好是有效的,因为 Linux 内核验证了协议号,并回退到 IPPROTO_IP
。但是,这可能不适用于其他系统。
检查您的网络通信是否使用正确的字节顺序。 network-byte-order 是 Big-Endian,而 host-byte-order 取决于您的架构,因此您可能需要来回转换多字节字段。
您的tcp->seq
可能具有无效值,因为TCP 只接受最大为65535 的值,而random()
返回从0 到RAND_MAX
(0x7fffffff) 的值。试试tcp->seq = htonl(random() % 65535);
TCP 标头的偏移量计算不正确。应该是 sizeof(struct iphdr)
而不是 sizeof(struct tcphdr)
。
【讨论】:
是否可以使用原始套接字接收 TCP 或 UDP 数据包?我认为这些传入的数据包总是传递给 TCP 和 UDP 驱动程序。 @Barmar 这是可能的。内核将每个数据报的副本传递给每个创建了与数据报协议匹配的 RAW 套接字的进程。 发现错误!它是缓冲区,因为我正在向远程主机发送一些垃圾。因此我无法收到答案。更正:char buffer[1024]=0; 关于您的第 2 点,在 Linux 上调用socket()
时,尝试使用 IPPROTO_IP
会导致错误 Protocol not supported
。使用 IPPROTO_TCP
或 IPPROTO_RAW
都可以,虽然我没有检查它是否过滤 TCP 数据包。
@jweyrich,ICMP 是第 3 层?以上是关于Raw Socket Linux 发送/接收数据包的主要内容,如果未能解决你的问题,请参考以下文章
Python Raw Socket 无法接收 ICMP 消息;出现在 Wireshark 中