如何在C++中创建原始TCP/IP数据包?

24

我是一名初学者 C++ 程序员 / 网络管理员,但只要有人指点,我就能学会如何做。大多数教程都使用旧代码进行演示,由于某些原因不再起作用。

由于我使用 Linux,所以我只需要一个关于如何编写原始 Berkeley 套接字的说明。有人可以给我快速介绍一下吗?


1
Beej的网络指南是任何与Linux环境下基本网络有关的内容的黄金标准。它简单明了,直击要点,我极力推荐。 - Paul Wicks
1
原始套接字非常棘手,在不同的系统上有些微不同。我只知道有人使用它们来编写嗅探器(如“wireshark”)和一些相当神秘的诊断工具。它们绝对不是学习套接字编程的好地方。在大多数情况下,“我是初学者C++程序员/网络管理员”意味着“远离SOCK_RAW”。 - Chuck Kollars
1
@ChuckKollars,我不同意你的看法。我认为学习原始套接字是学习C++和特别是C的好方法。使用库是一回事,但在Linux上使用原始套接字是非常有洞察力的经验。C++可以做很多独特的事情。我认为避免你所暗示的痛苦是一个错误。 - motoku
你使用的是哪个系统?"原始"套接字实现在不同的系统中有很大的差异,因此你所学到的(实际上主要是关于系统而不是C++),并不能适用于其他地方。 - Chuck Kollars
@ChuckKollars,没错;我更多地是在指结构的多种用途,而不是代码的可移植性。 - motoku
Wireshark和tcpdump都使用libpcap来捕获网络流量;在Linux上,libpcap使用PF_PACKET套接字来进行捕获,这可能是SOCK_RAWSOCK_DGRAM(选择取决于您正在捕获的设备)。它们都不使用例如PF_INETPF_INET6 SOCK_RAW数据包;这些用于发送原始的IPv4或IPv6数据包。 - user16139739
12个回答

14

首先阅读Beej关于套接字编程的指南。它将带领您了解如何开始编写网络代码。之后,您应该能够通过阅读页面手册来获取更多信息。

大部分内容将包括阅读您喜欢的库的文档,以及对页面手册的基本理解。


1
您提供的链接已经失效了。这是新的链接:https://beej.us/guide/bgnet/html/ - AhmedWas

11
首先,如果您能澄清一下“原始”的含义,那将会很有帮助。传统上,这意味着您想要自己构建所有的第4层头部(TCP/UDP/ICMP...头部),以及可能是IP头部的一部分。对于这种情况,您需要比已经被许多人提到的Beej's教程更多的内容。在这种情况下,您需要使用SOCK_RAW参数调用socket来获取原始IP套接字(请参见http://mixter.void.ru/rawip.html了解一些基础知识)。
如果您真正想要的只是能够与一些远程主机(例如stackoverflow.com上的80端口)建立TCP连接,那么您可能不需要“原始”套接字,而Beej's指南将为您服务。
关于此主题的权威参考资料,您应该真正阅读《Unix网络编程卷1:套接字联网API(第3版)》(作者:Stevens等人)。

5

对于TCP客户端:

使用gethostbyname查找DNS名称转换为IP地址,它将返回一个hostent结构。我们将其称为返回值主机。

hostent *host = gethostbyname(HOSTNAME_CSTR);

填充套接字地址结构:

sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons(REMOTE_PORT);
sock.sin_addr.s_addr = ((struct in_addr *)(host->h_addr))->s_addr;

创建一个套接字并调用connect:
s = socket(AF_INET, SOCK_STREAM, 0); 
connect(s, (struct sockaddr *)&sock, sizeof(sock))

对于TCP服务器端:

设置一个套接字

使用bind将您的地址绑定到该套接字上。

使用listen在该套接字上开始侦听

调用accept以获取已连接的客户端。 <--此时,您会生成一个新线程来处理连接,同时再次调用accept以获取下一个已连接的客户端。

一般通信:

使用send和recv在客户端和服务器之间进行读写。

BSD套接字源代码示例:

您可以在维基百科上找到一些很好的示例代码。

进一步阅读:

我强烈推荐这本书这个在线教程

alt text

4:


3
对于新的项目,不应再使用gethostbyname函数,因为它仅适用于IP版本(IPv4)。由于IPv4地址库存正在迅速枯竭,因此您应该使用getaddrinfo函数,它是版本无关的(可同时处理v4和v6)。 - bortzmeyer

4

一个好的起点是使用Asio,它是一个很棒的跨平台(包括Linux)网络通信库。


1
你永远无法使一个UDP数据包看起来或表现得像一个TCP数据包 - 这两者的IP数据包头是互相排斥的。 - Mike F

3
你可以像在普通的C语言中一样完成这个操作,标准库中没有C++特定的方法。
我用来学习的教程是http://beej.us/guide/bgnet/。在搜索了很多后,这是我找到的最好的教程,它提供了清晰的示例和对所有描述函数的良好解释。

3

我过去一年一直在开发libtins。它是一个高级别的C++数据包构建和嗅探库。

除非你想要重新发明轮子并实现每个协议的内部细节,否则我建议你使用一些已经为你完成这项工作的更高级别的库。


1

简单:

#include <sys/socket.h>
#include <sys/types.h>

int socket(int protocolFamily, int Type, int Protocol)
// returns a socket descriptor

int bind(int socketDescriptor, struct sockaddr* localAddress, unsigned int addressLength)
// returns 0 

都在sys/socket.h里面


1

Libpcap 可以让你构建完整的数据包(从第二层到第七层)并将它们发送到网络上。虽然听起来很有趣,但是需要注意一些问题。你需要创建所有适当的头文件并自己进行校验和计算。Libnet 可以帮助你解决这个问题。

如果你想离开 C++ 编程池,可以使用 scapy 来进行 Python 编程。它使得构建和传输 TCP/IP 数据包变得非常简单。


1

关于这个问题有很多参考资料(当然,Stevens的书让人难以忘怀),但我觉得Beej指南对入门非常有用。它足够详细,可以让你真正理解发生了什么,但又足够简单,不会花费几天时间写一个“hello world” udp客户端/服务器。


1
请澄清你的问题,楼主。
几乎所有回答都认为你在寻求套接字教程。我理解你的问题是需要创建一个能够发送任意IP数据包的原始套接字。 正如我在之前的回答中所说,一些操作系统限制了原始套接字的使用。 http://linux.die.net/man/7/raw “只有具有有效用户ID为0或CAP_NET_RAW功能的进程才允许打开原始套接字。”

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