根据Wikipedia,一个traceroute程序
默认情况下,Traceroute会发送一系列用户数据报协议 (UDP)数据包发往目的主机[...]生存时间 (TTL)值,也称为跳跃限制,用于确定 中间路由器被遍历到目的地。路由器 递减数据包'路由和丢弃数据包时TTL值为1 其TTL值已达到零,返回ICMP错误消息 ICMP时间超过。[..]
我开始编写程序(使用示例UDP程序作为指导)来遵守此规范,
#include <sys/socket.h>
#include <assert.h>
#include <netinet/udp.h> //Provides declarations for udp header
#include <netinet/ip.h> //Provides declarations for ip header
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define DATAGRAM_LEN sizeof(struct iphdr) + sizeof(struct iphdr)
unsigned short csum(unsigned short *ptr,int nbytes) {
register long sum;
unsigned short oddbyte;
register short answer;
sum=0;
while(nbytes>1) {
sum+=*ptr++;
nbytes-=2;
}
if(nbytes==1) {
oddbyte=0;
*((u_char*)&oddbyte)=*(u_char*)ptr;
sum+=oddbyte;
}
sum = (sum>>16)+(sum & 0xffff);
sum = sum + (sum>>16);
answer=(short)~sum;
return(answer);
}
char *new_packet(int ttl, struct sockaddr_in sin) {
static int id = 0;
char *datagram = malloc(DATAGRAM_LEN);
struct iphdr *iph = (struct iphdr*) datagram;
struct udphdr *udph = (struct udphdr*)(datagram + sizeof (struct iphdr));
iph->ihl = 5;
iph->version = 4;
iph->tos = 0;
iph->tot_len = DATAGRAM_LEN;
iph->id = htonl(++id); //Id of this packet
iph->frag_off = 0;
iph->ttl = ttl;
iph->protocol = IPPROTO_UDP;
iph->saddr = inet_addr("127.0.0.1");//Spoof the source ip address
iph->daddr = sin.sin_addr.s_addr;
iph->check = csum((unsigned short*)datagram, iph->tot_len);
udph->source = htons(6666);
udph->dest = htons(8622);
udph->len = htons(8); //udp header size
udph->check = csum((unsigned short*)datagram, DATAGRAM_LEN);
return datagram;
}
int main(int argc, char **argv) {
int s, ttl, repeat;
struct sockaddr_in sin;
char *data;
printf("\n");
if (argc != 3) {
printf("usage: %s <host> <port>", argv[0]);
return __LINE__;
}
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(argv[1]);
sin.sin_port = htons(atoi(argv[2]));
if ((s = socket(AF_PACKET, SOCK_RAW, 0)) < 0) {
printf("Failed to create socket.\n");
return __LINE__;
}
ttl = 1, repeat = 0;
while (ttl < 2) {
data = new_packet(ttl);
if (write(s, data, DATAGRAM_LEN) != DATAGRAM_LEN) {
printf("Socket failed to send packet.\n");
return __LINE__;
}
read(s, data, DATAGRAM_LEN);
free(data);
if (++repeat > 2) {
repeat = 0;
ttl++;
}
}
return 0;
}
...但是此时我有几个问题。
read(s, data, ...
一次读取整个数据包,还是需要解析从套接字读取的数据;寻找特定于IP数据包的标记?IPPROTO_ICMP
标志的第二个套接字,还是更容易编写过滤器;接受一切?答案 0 :(得分:1)
一个常见的缺陷是,此级别的编程需要非常小心地使用正确的包含文件。例如,您的程序将无法在NetBSD上编译,NetBSD在遵循相关标准时通常非常严格。
即使我添加了一些包含内容,也没有struct iphdr
,但有一个struct udpiphdr
。
所以现在剩下的答案并非基于在实践中尝试你的程序。
read(2)
可用于一次读取单个数据包。对于面向数据包的协议,例如UDP,您将永远不会从单个数据包中获取更多数据。
不过,您也可以使用recvfrom(2)
,recv(2)
或recvmsg(2)
来接收数据包。If fildes refers to a socket, read() shall be equivalent to recv() with no flags set.
* The udp port usage may appear bizarre (well, ok, it is bizarre). * The problem is that an icmp message only contains 8 bytes of * data from the original datagram. 8 bytes is the size of a udp * header so, if we want to associate replies with the original * datagram, the necessary information must be encoded into the * udp header (the ip id could be used but there's no way to * interlock with the kernel's assignment of ip id's and, anyway, * it would have taken a lot more kernel hacking to allow this * code to set the ip id). So, to allow two or more users to * use traceroute simultaneously, we use this task's pid as the * source port (the high bit is set to move the port number out * of the "likely" range). To keep track of which probe is being * replied to (so times and/or hop counts don't get confused by a * reply that was delayed in transit), we increment the destination * port number before each probe.
IPPROTO_ICMP
套接字接收回复比尝试接收所有数据包更有效。它还需要更少的权限。当然,发送原始数据包通常已经需要root,但如果使用更细粒度的权限系统,它可能会有所不同。答案 1 :(得分:1)
以下是我的一些建议(基于假设它是Linux机器)。
读取数据包
您可能希望读取整个1500字节的数据包(整个以太网帧)。不要担心 - 在read
返回读取的数据长度时,仍然可以完全读取较小的帧。
添加标记的最佳方法是让一些UDP有效负载(一个简单的unsigned int)应该足够好。在发送的每个数据包上增加它。 (我刚刚在traceroute上做了一个tcpdump - ICMP错误 - 确实返回了整个IP帧 - 所以你可以查看返回的IP帧,解析UDP有效载荷等等。注意你的DATAGRAM_LEN
会相应改变。 )当然你可以使用ID - 但要注意ID主要用于碎片。您应该对此感到满意 - '因为您不会在具有这些数据包大小的任何中间路由器上接近碎片限制。一般来说,为了我们的自定义目的而“窃取”用于其他目的的协议字段并不是一个好主意。
更简洁的方法是在原始套接字上实际使用IPPROTO_ICMP(如果您的计算机上安装了手册man 7 raw
和man 7 icmp
)。您不希望在设备上接收所有数据包的副本,而忽略那些不是ICMP的数据包。
如果您在SOCKET_RAW
上使用AF_PACKET
类型,则必须手动附加链接层标题,或者您可以执行SOCKET_DGRAM
并进行检查。同样man 7 packet
也有很多细微之处。
希望有帮助或者您正在查看一些实际代码?