uIP原始套接字
UIP中文文档第五 原始套接字_protosockets_库

详细说明:原始套接字(protosocket)为uIP提供了一个与传统BSD套接字接口类似的接口.不同于为传统uIP事件驱动接口写的程序,为原始套接字(protosocket)接口写的程序是顺序方式执行的,并且无需以明确的状态机方式实现.原始套接字(protosocket)只能用于TCP连接.原始大接字(protosocket)库使用"原始线程(protothreads)"来提供顺序控制流.这使得原始套接字在内存方面变得轻量型,但也同时意味着原始套接字继承了"原始线程"的功能限制.每个原始套接字只能生存于单个函数中.自动变量(栈变量)不能跨原始套接字函数调用存在. 注意:由于原始套接字库使用的是"原始线程(protothreads)",在调用原始套接字库函数时,局部变量并不总能得到保存.所以这里建议局部变量的使用要十分小心。
原始套接字库提供了一些无需处理重传和回应的发送数据函数,和一些无需对被分解成多个TCP段的数据进行处理的读取数据函数。
由于每个原始套接字都作为一个“原始线程”来运行,应在使用原始套接字的函数起始处通过调用PSOCK_BEGIN()的方式启用原始套接字。
与之类似,原始套接字可以通过调用PSOCK_EXIT()结束掉。
相关文件:1.psock.h 原始套接字库头文件复制代码相关结构体:1.struct psock_bufstruct psock 代表一个原始套接字。
复制代码相关宏定义:1.。
2.#define PSOCK_INIT(psock, buffer, buffersize) 初始化一个原始套接字。
3.#define PSOCK_BEGIN(psock) 在一个函数中启用一个原始套接字的原始线程。
4.#define PSOCK_SEND(psock, data, datalen) 发送数据。
5.#define PSOCK_SEND_STR(psock, str) 发送一个以零结尾的字符串。
利用原始套接字构造并发送数据包的方法

利用原始套接字构造并发送数据包的方法摘要:一、原始套接字概述1.定义与作用2.分类与应用场景二、构造数据包1.数据包结构2.常用协议及其封装三、发送数据包1.发送原理2.发送方法与实例四、原始套接字的安全性1.安全风险2.防范措施五、实践与应用1.操作步骤与注意事项2.应用案例分享正文:一、原始套接字概述1.定义与作用原始套接字(Raw Socket)是一种允许用户直接操作网络底层协议的套接字类型。
它允许用户在操作系统层面上构造和发送数据包,而不受协议栈的限制。
原始套接字广泛应用于网络数据包捕获、分析、发送等领域,为开发者提供了极大的灵活性。
2.分类与应用场景原始套接字根据底层协议可分为以下几类:(1)ICMP原始套接字:主要用于处理ICMP协议,如ping命令。
(2)UDP原始套接字:用于无连接的UDP数据传输。
(3)TCP原始套接字:用于建立和维护连接的TCP协议。
(4)IP原始套接字:用于处理IP层数据包,如IPv4和IPv6。
二、构造数据包1.数据包结构网络数据包从下到上分为:链路层、网络层、传输层和应用层。
原始套接字直接操作网络层,因此需要了解各层的数据结构。
(1)链路层:以太网帧,包含目的MAC地址、源MAC地址、类型字段等。
(2)网络层:IP数据报,包含版本、首部长度、服务类型、总长度、标识、标志、片偏移、生存时间、协议、头部校验和、源IP地址、目的IP地址等。
(3)传输层:TCP或UDP段,包含源端口、目的端口、序列号、确认号等。
(4)应用层:根据不同协议,如HTTP、DNS等,包含请求头、响应头、查询字符串等。
2.常用协议及其封装了解各层协议后,需要熟悉如何将这些协议封装到数据包中。
以下以IP协议和UDP协议为例:(1)IP协议封装:设置IP数据报的版本、首部长度、服务类型等,填充源IP地址、目的IP地址,计算头部校验和。
(2)UDP协议封装:设置UDP段的首部,包括源端口、目的端口、长度和校验和。
uip

uIP的ARP协议代码分析之一ARP请求对于一个设备用的ARP协议而言,重要的东西包括三方面:1.一个本地的IP与MAC地址的缓存表.以有对应的更新和查询操作.2.一个发送ARP请求的函数.3.一个处理ARP回复的函数.下面我们来看uIP中是如何实现的(代码见uip_arp.c:首先,定义一个缓存表的数据结构,99行起:struct arp_entry {u16_t ipaddr[2];struct uip_eth_addr ethaddr;u8_t time;};只有三个项,很简单第一项是ip地址,16*2=4*8位的,保存四个八位组.第二项是MAC地址.第三项是缓存更新时间.下来是ARP请求发送函数:/*-----------------------------------------------------------------------------------*//*** Prepend Ethernet header to an outbound IP packet and see if we need* to send out an ARP request.*为传出的IP包添加以太网头并看是否需要发送ARP请求.* This function should be called before sending out an IP packet. The* function checks the destination IP address of the IP packet to see* what Ethernet MAC address that should be used as a destination MAC* address on the Ethernet.*此函数应该在发送IP包时调用,它会检查IP包的目的IP地址,看看以太网应该使用什么目的MAC地址.* If the destination IP address is in the local network (determined* by logical ANDing of netmask and our IP address), the function* checks the ARP cache to see if an entry for the destination IP* address is found. If so, an Ethernet header is prepended and the* function returns. If no ARP cache entry is found for the* destination IP address, the packet in the uip_buf[] is replaced by* an ARP request packet for the IP address. The IP packet is dropped* and it is assumed that they higher level protocols (e.g., TCP)* eventually will retransmit the dropped packet.*如果目的IP地址是在局域网中(由IP地址与子网掩码的与逻辑决定),函数就会从ARP缓存表中查找有*无对应项.若有,就取对应的MAC地址,加上以太网头,并返回,否则uip_buf[]中的数据包会被替换成一个*目的IP在址的ARP请求.原来的IP包会被简单的仍掉,此函数假设高层协议(如TCP)会最终重传扔掉的包.* If the destination IP address is not on the local network, the IP* address of the default router is used instead.*如果目标IP地址并非一个局域网IP,则会使用默认路由的IP地址.* When the function returns, a packet is present in the uip_buf[]* buffer, and the length of the packet is in the global variable* uip_len.函数返回时,uip_buf[]中已经有了一个包,其长度由uip_len指定.*//*-----------------------------------------------------------------------------------*/voiduip_arp_out(void){struct arp_entry *tabptr;/* Find the destination IP address in the ARP table and constructthe Ethernet header. If the destination IP addres isn't on thelocal network, we use the default router's IP address instead.//在ARP表中找到目的IP地址,构成以太网头.如果目的IP地址不在局域网中,则使用默认路由的IP.If not ARP table entry is found, we overwrite the original IPpacket with an ARP request for the IP address. *///如果ARP表中找不到,则将原来的IP包替换成一个ARP请求./* First check if destination is a local broadcast. 首先检查目标是不是广播*/if(uip_ipaddr_cmp(IPBUF->destipaddr, broadcast_ipaddr)) {memcpy(IPBUF->ethhdr.dest.addr, broadcast_ethaddr.addr, 6);} else {/* Check if the destination address is on the local network. 检查目标地址是否在局域网内 */if(!uip_ipaddr_maskcmp(IPBUF->destipaddr, uip_hostaddr, uip_netmask)) {/* Destination address was not on the local network, so we need touse the default router's IP address instead of the destinationaddress when determining the MAC address. 目的地址不在局域网内,所以保用默认路由器的地址来确在MAC地址*/uip_ipaddr_copy(ipaddr, uip_draddr);} else {/* Else, we use the destination IP address. 否则,使用目标IP地址*/uip_ipaddr_copy(ipaddr, IPBUF->destipaddr);}for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {//这里遍历表,对比目的IP与ARP缓存表中的IP.tabptr = &arp_table;if(uip_ipaddr_cmp(ipaddr, tabptr->ipaddr)) {break;}}if(i == UIP_ARPTAB_SIZE) {/* The destination address was not in our ARP table, so weoverwrite the IP packet with an ARP request. 如果遍历到头没找到,将原IP包替换为ARP请求并返回*/memset(BUF->ethhdr.dest.addr, 0xff, 6);memset(BUF->dhwaddr.addr, 0x00, 6);memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);uip_ipaddr_copy(BUF->dipaddr, ipaddr);uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);BUF->opcode = HTONS(ARP_REQUEST); /* ARP request. */BUF->hwtype = HTONS(ARP_HWTYPE_ETH);BUF->protocol = HTONS(UIP_ETHTYPE_IP);BUF->hwlen = 6;BUF->protolen = 4;BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];uip_len = sizeof(struct arp_hdr);return;}/* Build an ethernet header. 如果是在局域网中,且在ARP缓存中找到了(如果没找到进行不到这一步,在上面就返回了),则构建以太网头*/memcpy(IPBUF->ethhdr.dest.addr, tabptr->ethaddr.addr, 6);}memcpy(IPBUF->ethhdr.src.addr, uip_ethaddr.addr, 6);IPBUF->ethhdr.type = HTONS(UIP_ETHTYPE_IP);uip_len += sizeof(struct uip_eth_hdr);}以上内容自325行起.下面再总结一下其基本顺序:用IPBUF->ethhdr.dest.addr来存储目的IP地址,它有可能是局域网内一主机IP,也可能是路由器IP(如果是发往外网,即原来的目的IP不在局域网内),还有可能是广播专用的IP. 先看是不是在广播:如果是广播,将IPBUF->ethhdr.dest.addr设为广播IP.再看是不是在局域网内:如果不是,则将IPBUF->ethhdr.dest.addr设为路由器IP.如果在局域网内,查看是否已经存在于ARP缓存表中:如果不在,将要发送的换成ARP请求,返回.如果已存在,则查找使用查找到的MAC地址为IP包添加以太网头.这里还要解释一些细节问题,主要是:1.如何在IP包上添加以太网头2.如果将IP包替换成ARP请求,ARP请求的格式是什么.3.广播地址这些问题将在二楼来说.将IP包替换成ARP请求部分代码(实际上IP包是放在uip_buf[]里的,这里只是将uip_buf[]填充上ARP请求即可),于uip_arp.c的388行:/* The destination address was not in our ARP table, so weoverwrite the IP packet with an ARP request. */memset(BUF->ethhdr.dest.addr, 0xff, 6);memset(BUF->dhwaddr.addr, 0x00, 6);memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);uip_ipaddr_copy(BUF->dipaddr, ipaddr);uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);BUF->opcode = HTONS(ARP_REQUEST); /* ARP request. */BUF->hwtype = HTONS(ARP_HWTYPE_ETH);BUF->protocol = HTONS(UIP_ETHTYPE_IP);BUF->hwlen = 6;BUF->protolen = 4;BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];uip_len = sizeof(struct arp_hdr);return;首先解释这里的BUF(于uip_arp.c的116行):#define BUF ((struct arp_hdr *)&uip_buf[0])可见这里的BUF就是uip_buf[],只不过这里将它取做一个struct arp_hdr的结构体:struct arp_hdr {struct uip_eth_hdr ethhdr;u16_t hwtype; //硬件类型u16_t protocol; //协议类型u8_t hwlen;u8_t protolen;u16_t opcode; //操作码struct uip_eth_addr shwaddr; //源以太网地址u16_t sipaddr[2]; //源IP地址struct uip_eth_addr dhwaddr; //目的以太网地址u16_t dipaddr[2]; //目的IP地址};struct uip_eth_hdr {struct uip_eth_addr dest;struct uip_eth_addr src;u16_t type;};这是arp_hdr的第一个成员ethhdr的类型定义,对应图片中的前三项:6+6+2,目的以太网地址,源以太网地址,2字节数据类型(ARP请求和应答为0x0806).struct arp_hdr的第二个成员u16_t hwtype,对应图片中第三项,2字节硬件类型(值为1表示以太网).struct arp_hdr的第三个成员u16_t protocol,对应图片中第四项,2字节要映射的协议地址类型(ip地址为0x0800).struct arp_hdr的第四个成员u8_t hwlen,对应图片中第五项,1字节硬件地址长度(对MAC地址来说为6).struct arp_hdr的第五个成员u8_t protolen,对应图片中第六项,1字节协议地址长度(对IP地址来说为4).struct arp_hdr的第六个成员u16_t opcode,对应图片中第七项,2字节操作码(1 ARP请求,2 ARP应答,3 RARP请求,4 RARP应答,必须字段).struct arp_hdr的第七个成员struct uip_eth_addr shwaddr,对应图片中第八项,6字节源以太网地址.struct arp_hdr的第八个成员u16_t sipaddr[2];,对应图片中第九项,2字节源IP地址.struct arp_hdr的第九个成员struct uip_eth_addr dhwaddr,对应图片中第十项,6字节目标以太网地址.struct arp_hdr的第十个成员u16_t dipaddr[2];,对应图片中第十一项,2字节目标IP地址.上面绿色的表示已经详解的,红字的表示要进一步说明的.这就是一个ARP帧的结构,可以看到,里面的源和目的以太网地址都是重复的.我们再看函数中的代码:memset(BUF->ethhdr.dest.addr, 0xff, 6);memset(BUF->dhwaddr.addr, 0x00, 6);memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);这里四个memset,重复的源和目的以太网地址一起设置了,四个memset对应图片中的1,10,2,8项.但是:对1和10两项,都是源以太网地址,但置的值是不同的,分别为0xff*6和0x00*6.为什么会这样呢?因为他们的用处不一样,见:【相关资料】ARP分组格式(帧格式,报文格式)6+6–以太网的源地址和目的地址,目的地址为全1的为广播地址注意这里说,目的地址为全为1的广播地址.什么意思?当你发送一个ARP请求是,你是想知道一个IP对应的以太网地址(即MAC地址),但是你现在不知道目的主机的以太网地址,你问谁啊?不知道问谁,这种情况下,只能是广播一下了,0xff*6就是广播地址.从图片中可以看到,ARP包是分成两部分的,前6+6+2叫做"以太网首部",它的意义就是"分组是从谁(源地址)发给谁(目的地址)的什么类型(帧类型,请求和应答为0x0806)",第二部分则是内容.来看这个例子:请求帧如下(为了清晰在每行的前面加了字节计数,每行16个字节):以太网首部(14字节)0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06ARP帧(28字节)0000: 00 010010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 370020: 00 00 00 00 00 00 c0 a8 00 02填充位(18字节)0020: 00 77 31 d2 50 100030: fd 78 41 d3 00 00 00 00 00 00 00 00以太网首部:目的主机采用广播地址,源主机的MAC地址是00:05:5d:61:58:a8,上层协议类型0x0806表示ARP。
UIP中文文档第六 uIP原始线程(protothreads

详细说明:“原始线程”是一种轻量级的、无需堆栈的线程。
它主要用于内存极为受限的系统,如深入嵌入式系统、传感器网络等。
“原始线程”是以C代码实现的,为事件驱动的系统提供了线性的代码执行空间。
“原始线程”即可以用在有实时操作系统的系统中,也可以用在没有实时操作系统的系统中。
它在事件驱动的系统上层提供了阻塞上下文,省去了每个线程的单独堆栈空间开销。
原始线程的目标是在没有复杂的状态机或多线程的情况下实现控制顺序流。
原始线程在C语言内部实现了条件阻塞。
原始线程相对于纯事件驱动方法的优势是原始线程提供了可以阻塞函数的顺序代码结构。
在纯事件驱动的方法中,阻塞的实现必须将一个函数手动分成两个部分。
一部分放在阻塞调用前,一部分放在阻塞调用后。
这让使用if()和while()语句变得困难。
原始线程相对于普通线程的优势是,它不需要为每个线程分配堆栈。
在内存受限的系统中,为每个线程分配堆栈空间会占用大量的内存。
相比而言,原始线程的每个线程只需2到12个字节的状态字,具体多大与构架有关。
注意:由于原始线程有阻塞调用中中不需保存堆栈上下文,原始线程阻塞时,局部变量也不再保留。
这意味着局部变量的使用必须十分小心。
如果不确定的话,尽量不要有原始线程中使用局部变量。
主要特性:∙无机器相关代码-代码完成是C写的。
∙不使用错误-倾向于像longjump()这样的函数。
∙很小的RAM开销-每条线程仅两个字节。
∙即可使用OS,也可以不使用。
∙提供阻塞等待而无需多线程或堆栈切换。
主要应用:∙内存受限系统∙事件驱动的协议栈∙深入嵌入式系统∙传感器网络原始线程包括四个基本的操作:初妈化(PT_INIT()),执行(PT_BEGIN()),条件阻塞(PT_WAIT_UNTIL()),和退出(PT_EXIT()).在此之上还有两个方便使用的函数:反向条件阻塞(PT_WAIT_WHILE())和原始线程阻塞(PT_WAIT_THREAD()).见:uIP中文文档第五原始套接字原始线程线的分布是基于一个类似于BSD的协议的。
公开课 原始套接字编程

公开课原始套接字编程一、引言在当今互联网时代,网络编程已成为程序员必备技能之一。
本次公开课将为大家介绍原始套接字编程,通过学习本课程,你将掌握原始套接字的基本概念和实战技巧,为日后的网络编程任务奠定基础。
二、原始套接字编程基本概念1.套接字概述套接字(Socket)是应用程序与底层操作系统之间的接口,允许应用程序在网络中发送和接收数据。
套接字分为客户端和服务器端,通过套接字,客户端可以与服务器端进行通信。
2.原始套接字与封装套接字的区别原始套接字(Raw Socket)是指不经过任何协议封装的套接字,它直接使用底层网络协议(如TCP/IP)进行通信。
封装套接字(Wrapped Socket)则是将套接字包装在某种协议中,如HTTP、FTP等。
3.原始套接字编程的应用场景原始套接字编程适用于需要底层网络控制的情景,如网络数据捕获、网络数据解析等。
此外,它还常用于网络攻击防护和网络安全研究。
三、原始套接字编程实战1.创建原始套接字创建原始套接字需要使用socket函数,如:socket(AF_INET,SOCK_RAW, IPPROTO_IP)。
2.绑定套接字到端口使用bind函数将套接字绑定到指定的本地端口,如:bind(套接字,(struct sockaddr_in *)&local_addr)。
3.监听连接使用listen函数监听连接,如:listen(套接字,5)。
4.接受连接当有新的连接请求时,使用accept函数接受连接,如:accept(套接字,(struct sockaddr *)&remote_addr,& sock)。
5.发送和接收数据使用send和recv函数发送和接收数据,如:send(套接字,发送数据,发送长度);recv(套接字,接收数据,接收长度)。
6.关闭套接字在完成通信后,使用close函数关闭套接字,如:close(套接字)。
四、原始套接字编程进阶话题1.异步I/O编程在处理大量网络数据时,可以使用异步I/O编程提高程序性能,如使用poll、epoll等。
原始接字编程

北京理工大学珠海学院 孙细斌
原始套接字(Raw Socket)
IP协议:是TCP/IP协议族中最为核心的协议。 它提供不可靠、无连接的服务。所有的 TCP、UDP、ICMP、IGMP数据都被封装 在IP数据报中传送。IP协议头部结构如下:
北京理工大学珠海学院 孙细斌
原始套接字(Raw Socket)
IP协议头部结构如下:
北京理工大学珠海学院 孙细斌
原始套接字(Raw Socket)
ICMP协议介绍: ICMP是“Internet Control Message Protocol” (Internet控制消息协议)的缩写。它是 TCP/IP协议族的一个子协议,用于在IP主 机、路由器之间传递控制消息。控制消息 是指网络通不通、主机是否可达、路由是 否可用等网络本身的消息。这些控制消息 虽然并不传输用户数据,但是对于用户数 据的传递起着重要的作用。
原始套接字(Raw Socket) 利用Raw Socket技术编写基于 ICMP协议的ping命令步骤 (2)定义原始socket对象
Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
北京理工大学珠海学院 孙细斌
原始套接字(Raw Socket)
class ICMPPacket { • public ICMPPacket(byte icmpType,byte subCode,UInt16 checkSum,UInt16 identity,UInt16 sequence,int dataSize) • { • this.icmpType = icmpType; • this.subCode = subCode; • this.checkSum = checkSum; • this.identity = identity; • this.sequence = sequence; • data = new byte[dataSize]; • for (int i = 0; i < dataSize; i++) • { • data[i]=(byte)'Y'; • } • } • }
网络编程_第9讲 原始套接字编程
9.2.3
不需要bind()函数
原始套接字不需要使用bind()函数,因为进行发送和接 收数据的时候可以指定要发送和接收的目的地址的IP。例如 使用函数sendto()和函数recvfrom()来发送和接收数据, sendto()和recvfrom()函数分别需要指定IP地址。 sendto (rawsock, data, datasize, 0, (struct sockaddr *) &to, sizeof (to)); recvfrom(rawsock, data,size , 0,(struct sockaddr)&from, &len) ; 当系统对socket进行了绑定的时候,发送和接收的函 数可以使用send()和recv()及read()和write()等不需要指定 目的地址的函数。
0 类型(8位) 7 8 15 16 代码(8位) 校验和(16位) 31
(此部分不同的类型和代码格式不同)
9.5.3
0 源端口号(16位) UDP数据长度(16位)
UDP头部结构
15 16 目的端口号(16位) 8个字节 UDP校验和(16位) 31
数据
0 source len
15 16 dest
数据
IP头部的结构
15 16 总长度(16位) 标识(3位) 片偏移(13位) 头部校验和(16位) 源IP地址(32位) 目的IP地址(32位) 选项(32位) 20个字节 31
数据
9.5.2
ICMP头部结构
ICMP的头部结构比较复杂,主要包含消息类型 icmp_type,消息代码icmp_code、校验和icmp_cksum 等,不同的ICMP类型其他部分有不同的实现。 1.ICMP的头部结构 2.不同类型的ICMP请求
原始套接字(Raw Socket)解析
ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧
ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧
ETH_P_ARP 0x8035 只接受发往本机mac的rarp类型的数据帧
ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)
ipHeader.checksum = 0;
ipHeader.sourceIP = inet_addr("localhost");
ipHeader.destIP = desIP;
//填充TCP报头
tcpHeader.th_dport = htons(desPort);
最后,进入udp输入例程 ...
ps:如果校验和出错的话,内核会直接丢弃该数据包的.而不会拷贝给sock_raw的套接字,因为校验和都出错了,数据肯定有问题的包括所有信息都没有意义了.
进一步分析他们的能力.
1. socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
addr_in.sin_addr.S_un.S_addr = inet_addr(desIP);
//填充IP报头
ipHeader.h_verlen = (4 << 4 | sizeof(ipHeader) / sizeof(unsigned long));
// ipHeader.tos=0;
{
printf("send error!:%d\n", WSAGetLastError());
第6章 原始套接字
TCP
UDP OSPF
数据部分
IP 数据报
协议字段指出应将数据 部分交给哪一个进程
ICMP—1; IGMP—2;TCP—6;
UDP—17;OSPF--89
表1.4 ICMP报文类型
重定向: 0 5 1 2 3 8 9 10 11 0 0 0 0 1 12 13 14 15 16 17 18 0 1 0 0 0 0 0 0 对网络重定向 对主机重定向 对服务类型和网络重定向 对服务类型和主机重定向 请求回显(Ping 请求) 路由器通告 路由器请求 超时: 传输期间生存时间为 0(Traceroute) 在数据报组装期间生存时间为 0 参数问题: IP 首部错误(包括各种差错) 缺少必需的选项 时间戳请求 时间戳应答 信息请求(作废) 信息应答(作废) 地址掩码请求 地址掩码应答 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
6.3.1 IP数据报格式
固 定 部 分 20 字节 可变 部分
版 本 首部长度 标
服务类型 识 标志
总 长 度
片 偏 移 首 部 检 验 和
生存时间
协
议
源 地 址 目 的 地 址
可 选 字 段 (长 度 可 变)
填
充
上面是 IPv4 数据报的首部
typedef struct _IPHeader // 20字节的IP头 { UCHAR iphVerLen; // 版本号和头长度(各占4位) UCHAR ipTOS; // 服务类型 USHORT ipLength; // 封包总长度,即整个IP报的长度 USHORT ipID; // 封包标识,惟一标识发送 的每一个数据报 USHORT ipFlags; // 标志 UCHAR ipTTL; // 生存时间,就是TTL UCHAR ipProtocol; // 协议,可能是TCP、UDP、ICMP等 USHORT ipChecksum; // 校验和 ULONG ipSource; // 源IP地址 ULONG ipDestination; // 目标IP地址 } IPHeader, *PIPHeader;
原始套接字
传输层 (TCP、UDP)
网络互联层 (IP)
主机到网络 (网络接口层)
应用程序
13.1
原始套接字概述
标准套接字
TCP/UDP IP
网络 核心
原始套接字 用户空间 内核空间
ICMP
套接字与内核的访问关系
13.1
原始套接字概述
原始套接字能够提供以下3种标准套接字不具备的功能:
(0 – 40字节)
数据
全长 ip_len
分片偏移 ip_off
首部校验和 ip_cksum
13.3.1 IP首部结构
31
20 字节
最大 65535 字节
Linux中 struct ip 结构体说明:
struct ip {
#if __BYTE_ORDER == __LITTLE_ENDIAN
接收时间戳
发送时间戳
13.3.2ICMP首部结构
类型:13或14,时间戳请求和应答 代码:0
Linux中 struct icmp 结构体说明(BSD):
struct icmp {
u_int8_t
u_int8_t
icmp_type; icmp_code;
u_int16_t
icmp_cksum;
③ 如果IP以分片形式到达,则所有分片都已经接收到并重组后才传给原始 套接字 。
④ 内核不能识别的协议、格式等传给原始套接字,因此,可以使用原始套 接字实现自定义协议格式。
13.2.3
原始套接收报文
⑤ 如果收到的数据中的协议类型与自定义的原始套接字匹配,则将接收到
的数据复制到原始套接字接收缓冲区中。
② 原始套接字创建完成后,一般还需要指定套接字数据格式类型,使得 原始套接字可确定以从网络接收哪种格式的数据。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
UI原始套接字(protosockets)库
2016-03-05 11:08:17| 分类:TCP/UDP |
详细说明:
原始套接字(protosocket)为uIP提供了一个与传统BSD套接字接口类似的接口。
不同于为传统uIP事件驱动接口写的程序,为原始套接字(protosocket)接口写的程序是顺序方式执行的,并且无需以明确的状态机方式实现。
原始套接字(protosocket)只能用于TCP连接。
原始套接字(protosocket)库使用"原始线程(protothreads)"来提供顺序控制流。
这使得原始套接字在内存方面变得轻量型,但也同时意味着原始套接字继承了"原始线程"的功能限制。
每个原始套接字只能生存于单个函数中,自动变量(栈变量)不能跨原始套接字函数调用存在。
注意:
由于原始套接字库使用的是"原始线程(protothreads)",在调用原始套接字库函数时,局部变量并不总能得到保存.所以这里建议局部变量的使用要十分小心。
原始套接字库提供了一些无需处理重传和回应的发送数据函数,和一些无需对被分解成多个TCP段的数据进行处理的读取数据函数。
由于每个原始套接字都作为一个“原始线程”来运行,应在使用原始套接字的函数起始处通过调用PSOCK_BEGIN() 的方式启用原始套接字。
与之类似,原始套接字可以通过调用PSOCK_EXIT()结束掉。
相关文件:
psock.h 原始套接字库头文件。
相关结构体:
struct psock_bufstruct psock 代表一个原始套接字。
#define PSOCK_INIT(psock, buffer, buffersize) 初始化一个原始套接字。
#define PSOCK_BEGIN(psock) 在一个函数中启用一个原始套接字的原始线程。
#define PSOCK_SEND(psock, data, datalen) 发送数据。
#define PSOCK_SEND_STR(psock, str) 发送一个以零结尾的字符串。
#define PSOCK_GENERATOR_SEND(psock, generator, arg) 通过函数(generator)产生数据并发送出去。
#define PSOCK_CLOSE(psock) 关闭一个原始套接字。
#define PSOCK_READBUF(psock) 读数据直到缓冲区满。
#define PSOCK_READTO(psock, c) 读数据到字符c.
#define PSOCK_DATALEN(psock) 获得上次读到的数据长度。
#define PSOCK_EXIT(psock) 退出原始套接字的原始线程。
#define PSOCK_CLOSE_EXIT(psock) 关闭一个原始套接字,并退出其原始线程。
#define PSOCK_END(psock) 声明一个原始套接字的原始线程的结尾。
#define PSOCK_NEWDATA(psock) 查明是否有数据到达原始套接字。
#define PSOCK_WAIT_UNTIL(psock, condition) 等待,直到条件(condition)为真。
#define PSOCK_WAIT_THREAD(psock, condition) PT_WAIT_THREAD(&((psock)->pt), (condition))
u16_t psock_datalen(struct psock *psock) char psock_newdata(psock * s)
参数:psock 是(struct psock *)指向要启用的原始套接字的结构体指针。
1. #define PSOCK_BEGIN(psock)
启用一个原始套接字的原始线程。
此宏启用一个原始套接字关联的原始线程,必须在调用其它原始套接字函数之前出现。
2. #define PSOCK_CLOSE(psock)
此宏用于关闭一个原始套接字,只能用于此原始套接字生存的原始线程中.
3. #define PSOCK_CLOSE_EXIT(psock)
此宏用于关闭一个原始套接字,并退出原始套接字生存的线程.
4. #define PSOCK_DATALEN(psock)
返回之前通过PSOCK_READTO() 或PSOCK_READ()读到的数据长度.
5. #define PSOCK_END(psock)
此宏用于声明原始套接字的原始线程结束.常于PSOCK_BEGIN(psock)配合使用.
6. #define PSOCK_EXIT(psock)
此宏用于终止原始套接字的原始线程,必须与PSOCK_CLOSE一起使用.
也可以直接使用PSOCK_CLOSE_EXIT();
7. #define PSOCK_GENERATOR_SEND(psock,generator,arg)
通过函数产生数据并发送.
generator 指向产生数据的函数的指针.
arg 要传给函数的参数.
使宏用于通过函数产生数据,并通过原始套接字发送,它可以用于动态产生数据并传输,而不必先将产生的数据存入缓冲区.此函数减小了缓冲区内存的开销,产生器函数由应用程序实现,使用时要将
函数的指针作为一个参数传给宏,产生器函数应该将产生的数据直接存放在uip_appdata缓冲区中,并返回产生数据的长度。
数据每一次发送时,原始套接字层会调用产生器函数,并且如果出现重发的情况,则也会调用一次产生器函数,也就是说重发的值并非第一次产生器函数调用时的值。
8. #define PSOCK_INIT(psock ,buffer,buffersize)
这个宏初始化一个原始套接字,它必须在使用套接字之前得到调用. 此宏还规定了套接字的输入缓冲区.
参数:
buffer (char * ) 指向原始套接字的输入缓冲区的指针.
buffersize (unsigned int) 输入缓冲区的大小.
9. #define PSOCK_NEWDATA(psock)
此宏用于查明是否有数据到达套接字。
它常和PSOCK_WAIT_UNTIL() 连用,查看套接字上是否有新的数据到达。
10. #define PSOCK_READBUF(psock)
读数据直到缓冲区满。
此宏会阻塞数据等待,把数据读入由PSOCK_INIT()指定的输入缓冲区。
读数据操作会进行到缓冲区满为止。
11. #define PSOCK_READTO(psock,c)
读数据直到某字符出现。
此宏会阻塞数据等待并开始读取数据到由PSOCK_INIT()指定的输入缓冲区,数据读取会一直持续到由参数c指定的字符出现为止。
c (char )用于指示停止读取数据的字符。
12. #define PSOCK_SEND(psock,data,datalen)
发送数据。
此宏通过原始套接字发送数据。
原始套接字阻塞原始线程,直到所有的数据发送完毕,并且已经被完程TCP终端接收。
data (char *) 指向要发送的数据的指针。
datalen (unsigned int)要发送的数据长度。
13. #define PSOCK_SEND_STR(psock,str)
发送一个以零结尾的字符串。
str 要发送的字符串。
14. #define PSOCK_WAIT_UNTIL(psock,condition) 等待直接条件为真。
阻塞原始线程,直到条件为真。
宏PSOCK_NEWDATA()可以用来检查此宏等待时有没有新数据到来。
此宏的典型用法如下:
1. PT_THREAD(thread(struct psock *s, struct timer *t))
2. {
3. PSOCK_BEGIN(s);
4.
5. PSOCK_WAIT_UNTIL(s, PSOCK_NEWADATA(s) || timer_expired(t));
6.
7. if(PSOCK_NEWDATA(s)) {
8. PSOCK_READTO(s, '\n');
9. } else {
10. handle_timed_out(s);
11. }
12.
13. PSOCK_END(s);
14. }。