LinuxC下获取UDP包中的路由目的IP地址和头标识目的地址
在接受到UDP包后,有时候我们需要根据所接收到得UDP包,获取它的路由目的IP地址和头标识目的地址。
(一)主要的步骤:
在setsockopt中设置IP_PKTINFO,然后通过recvmsg来获取struct in_pktinfo(struct in_pktinfo是struct msghdr中msg_control的成员).in_pktinfo 结构体(如下所示),我们可以从in_pktinfo中获取路由目的地址(destination address of the packet)、头标识目的地址(source address of the packet)。这种方法只能用于UDP(数据报)传输中。
struct in_pktinfo{unsigned int ipi_ifindex; /* 接口索引 */struct in_addr ipi_spec_dst; /* 路由目的地址 */struct in_addr ipi_addr; /* 头标识目的地址 */};
ipi_ifindex指的是接收包的接口的唯一索引,ipi_spec_dst指的是路由表记录中的目的地址,而ipi_addr 指的是包头中的目的地址。如果给setsockopt传递了IP_PKTINFO,那么外发的包会通过在ipi_ifindex中指定的接口发送出去,同时把ipi_spec_dst设置为目的地址。
(二)下面的例子简单地说明如何获取UDP包中的源地址(interface addresses)、目标地址(destination addresses)。为了代码的简单,下面代码段省去了错误检查。
// sock 使用AF_INET协议族, socket类型SOCK_DGRAM setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)); // 这里,控制数据是脏数据。 char cmbuf[0x100]; // 目标IP地址 struct sockaddr_in peeraddr;//如果你想要获取UDP包中的数据,那么还需要为msg_iovec字段初始化 struct msghdr mh = {.msg_name = &peeraddr,.msg_namelen = sizeof(peeraddr),.msg_control = cmbuf,.msg_controllen = sizeof(cmbuf), }; recvmsg(sock, &mh, 0);struct cmsghdr *cmsg ; for ( // 遍历所有的控制头(the control headers)cmsg = CMSG_FIRSTHDR(&mh);cmsg != NULL;cmsg = CMSG_NXTHDR(&mh, cmsg)) {// 忽略我们不需要的控制头(the control headers)if (cmsg->cmsg_level != IPPROTO_IP ||cmsg->cmsg_type != IP_PKTINFO){continue;}struct in_pktinfo *pi = CMSG_DATA(cmsg);// 在这里, peeraddr是本机的地址(the source sockaddr)// pi->ipi_spec_dst 是UDP包中路由目的地址(the destination in_addr)// pi->ipi_addr 是UDP包中的头标识目的地址(the receiving interface in_addr) }
(三)下面我将给出一个完整可运行的例子,这个例子实现了接收UDP广播包,发送UDP广播包,并在接收的时候,打印出UDP包的路由目的IP地址和头标识目的地址。
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #define BUFLEN 255int main ( int argc, char **argv ) {struct sockaddr_in peeraddr, localaddr;int sockfd;int socklen, n; //(1)创建UDP数据报socket描述符sockfd = socket ( AF_INET, SOCK_DGRAM, 0 );if ( sockfd<0 ){printf ( "socket creating err in udptalk\n" );exit ( EXIT_FAILURE );}printf ( "IP address Checking!\n" );socklen = sizeof ( struct sockaddr_in );memset ( &peeraddr, 0, socklen ); //(2)设置目标主机IP和端口,这里我们使用广播方式peeraddr.sin_family=AF_INET;peeraddr.sin_port=htons ( atoi ( "8903" ) );peeraddr.sin_addr.s_addr = htonl ( INADDR_BROADCAST ); //(3设置本机IP和端口,这里我们设置可以接收符合端口的所有的包memset ( &localaddr, 0, socklen );localaddr.sin_family=AF_INET;localaddr.sin_addr.s_addr = htonl ( INADDR_ANY ); //设置接收任何主机 printf ( "try to bind local address \n" );localaddr.sin_port=htons ( atoi ( "8904" ) );//(4)设置IPPROTO_IP标志,以便获取UDP包中的信息int opt = 1;setsockopt ( sockfd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof ( opt ) );int nb = 0; //(5)设置为广播方式nb = setsockopt ( sockfd, SOL_SOCKET, SO_BROADCAST, ( char * ) &opt,sizeof ( opt ) );if ( nb == -1 ){printf ( "set socket error..." );exit ( EXIT_FAILURE );}printf ( "IP address Checking!\n" );char cmbuf[100];// 这里只是为控制数据申请一个空间 //(6)初始化msg_iovec字段,以便获取UDP包数据域char buffer[BUFLEN+1];struct iovec iov[1];iov[0].iov_base=buffer;iov[0].iov_len=sizeof ( buffer ); //(7)初始化struct msghdr,以便获取UDP包中目标IP地址和源地址struct msghdr mh ={.msg_name = &localaddr,.msg_namelen = sizeof ( localaddr ),.msg_control = cmbuf,.msg_controllen = sizeof (cmbuf ),.msg_iov=iov, .msg_iovlen=1}; //(8)将本机的地址信息与sockfd绑定起来if ( bind ( sockfd, &localaddr, socklen ) <0 ){printf ( "bind local address err in udptalk!\n" );exit ( 2 );}//发一个消息给目标主机if ( sendto ( sockfd, "HELLO", strlen ( "HELLO" ), 0, &peeraddr, socklen ) <0 ){printf ( "sendto err in udptalk!\n" );exit ( 3 );}printf ( "end of sendto \n" );printf ( "start of recv&send message loop!\n" );for ( ;; )//接收消息循环 {printf ( "Waiting For Message...!\n" );n=recvmsg ( sockfd, &mh, 0 );//判断socket是否有错误发生if ( n<0 ){printf ( "recvfrom err in udptalk!\n" );exit ( 4 );}else{cmbuf[n]=0;printf ( "Receive:%dByte。\tThe Message Is:%s\n", n,buffer );} //(9)初始化cmsghdr以便处理mh中的附属数据,通过遍历附属数据对象,找出我们感兴趣的信息struct cmsghdr *cmsg ;for ( cmsg = CMSG_FIRSTHDR ( &mh );cmsg != NULL;cmsg = CMSG_NXTHDR ( &mh, cmsg ) ){// 忽略我们不需要的控制头(the control headers)if ( cmsg->cmsg_level != IPPROTO_IP ||cmsg->cmsg_type != IP_PKTINFO ){continue;}struct in_pktinfo *pi = CMSG_DATA ( cmsg ); //(10)将地址信息转换后输出char dst[100],ipi[100];//用来保存转化后的源IP地址,目标主机地址 // pi->ipi_spec_dst 是UDP包中的路由目的IP地址(the destination in_addr)// pi->ipi_addr 是UDP包中的头标识目的地址(the receiving interface in_addr)if ( ( inet_ntop ( AF_INET,& ( pi->ipi_spec_dst ),dst,sizeof ( dst )) ) !=NULL ){printf ( "路由目的IP地址IPdst=%s\n",dst);}if ( ( inet_ntop ( AF_INET,& ( pi->ipi_addr ),ipi,sizeof ( ipi )) ) !=NULL ){printf ("头标识目的地址ipi_addr=%s\n",ipi);}}printf ( "Send Some Message To Server\n" );if ( sendto ( sockfd, "Hello", strlen ( buffer ), 0, &peeraddr, socklen) <0 ){printf ( "sendto err in udptalk!\n" );exit ( 3 );}} }
例子的使用说明
1、开启虚拟机下面的例子程序
2、通过windows下面的网络调试助手向虚拟机发送数据
结果截图
因为通过虚拟网卡的,所以我们看到目标IP地址并不是网络调试助手中设置的IP,而是虚拟网卡的地址,通过Linux下的tcpdump我们可以看到其中网卡转发的过程。
下面我将本篇涉及到的和结构体函数原型都附在下方
(一)涉及到的结构体
1、struct in_addr
struct in_addr {in_addr_t s_addr; };
结构体in_addr 用来表示一个32位的IPv4地址.
in_addr_t 一般为 32位的unsigned long,其字节顺序为网络顺序(network byte ordered),即该无符号整数采用大端字节序
2、struct msghdr
recvmsg()使用 msghdr 结构体(structure )减少参数传递的数目。这个结构体定义在 <sys/socket.h>中,如下所示
struct iovec { /* Scatter/gather array items */void *iov_base; /* Starting address */size_t iov_len; /* Number of bytes to transfer */ };struct msghdr {void *msg_name; /* optional address */socklen_t msg_namelen; /* size of address */struct iovec *msg_iov; /* scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */ };
struct msghdr看上去似乎是一个需要创建的巨大的结构。但是不要怕。其结构成员可分为四组:
套接口地址成员: msg_name与msg_namelen。 I/O向量引用:msg_iov与msg_iovlen。 附属数据缓冲区成员:msg_control与msg_controllen。 接收信息标记位:msg_flags。
在我们将这个结构分为上面的几类以后,结构看起来就不那样巨大了。
成员msg_name与msg_namelen 这些成员只有当我们的套接口是一个数据报套接口时才需要。msg_name成员指向我们要发送或是接收信息的套接口地址。成员msg_namelen指明了这个套接口地址的长度。 当调用recvmsg时,msg_name会指向一个将要接收的地址的接收区域。当调用sendmsg时,这会指向一个数据报将要发送到的目的地址。 注意,msg_name定义为一个(void *)数据类型。我们并不需要将我们的套接口地址转换为(struct sockaddr *)。
成员msg_iov与msg_iovlen 这些成员指定了我们的I/O向量数组的位置以及他包含多少项。msg_iov成员指向一个struct iovec数组。我们将会回忆起I/O向量指向我们的缓冲区。成员msg_iov指明了在我们的I/O向量数组中有多少元素。
成员msg_control与msg_controllen 这些成员指向了我们附属数据缓冲区并且表明了缓冲区大小。msg_control指向附属数据缓冲区,而msg_controllen指明了缓冲区大小。
成员msg_flags 当使用recvmsg时,这个成员用于接收特定的标记位(他并不用于sendmsg)。在这个位置可以接收的标记位如下表所示:
标记位 | 描述 |
MSG_EOR | 当接收到记录结尾时会设置这一位。这通常对于SOCK_SEQPACKET套接口类型十分有用。 |
MSG_TRUNC | 这个标记位表明数据的结尾被截短,因为接收缓冲区太小不足以接收全部的数据。 |
MSG_CTRUNC | 这个标记位表明某些控制数据(附属数据)被截短,因为缓冲区太小。 |
MSG_OOB | 这个标记位表明接收了带外数据。 |
MSG_ERRQUEUE | 个标记位表明没有接收到数据,但是返回一个扩展错误。 |
3、struct cmsghdr结构
recvmsg与sendmsg函数允许程序发送或是接收附属数据。然而,这些额外的信息受限于一定的格式规则。下面将会介绍控制信息头与程序将会用来管理这些信息的宏。
属信息可以包括0,1,或是更多的单独附属数据对象。在每一个对象之前都有一个struct cmsghdr结构。头部之后是填充字节,然后是对象本身。最后,附属数据对象之后,下一个cmsghdr之前也许要有更多的填充字节。在这里,我们将要关注的附属数据对象是文件描述符与证书结构。 图1显示了一个包含附属数据的缓冲区是如何组织的。
图1 辅助数据结构是由各种子结构、数据区, 填充字节构成
我们需要注意以下几点: cmsg_len与CMSG_LEN()宏值所显示的长度相同。 CMSG_SPACE()宏可以计算一个附属数据对象的所必需的空白。 msg_controllen是CMSG_SPACE()长度之后,并且为每一个附属数据对象进行计算。
struct cmsghdr {socklen_t cmsg_len; /* data byte count, including header */int cmsg_level; /* originating protocol */int cmsg_type; /* protocol-specific type *//* followed by unsigned char cmsg_data[]; */ };
其成员描述如下:
成员 | 描述 |
cmsg_len | 附属数据的字节计数,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。 |
cmsg_level | 这个值表明了原始的协议级别(例如,SOL_SOCKET)。 |
cmsg_type | 这个值表明了控制信息类型(例如,SCM_RIGHTS)。 |
cmsg_data | 这个成员并不实际存在。他用来指明实际的额外附属数据所在的位置。 |
这一章所用的例子程序只使用SOL_SOCKET的cmsg_level值。这一章我们感兴趣的控制信息类型如下(cmsg_level=SOL_SOCKET):
cmsg_level | 描述 |
SCM_RIGHTS | 附属数据对象是一个文件描述符 |
SCM_CREDENTIALS | 附属数据对象是一个包含证书信息的结构 |
(二)涉及的函数
1、setsockopt函数原型
//setsockopt函数原型 #include <sys/types.h> #include <sys/socket.h> int setsockopt(int s, int level, int optname,const void *optval, socklen_t optlen);
2、cmsg 宏 由于附属数据结构的复杂性,Linux系统提供了一系列的C宏来简化我们的工作。另外,这些宏可以在不同的UNIX平台之间进行移植,并且采取了一些措施来防止将来的改变。这些宏是由cmsg(3)的man手册页来进行描述的,其概要如下:
#include <sys/socket.h> struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh); struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg); size_t CMSG_ALIGN(size_t length); size_t CMSG_SPACE(size_t length); size_t CMSG_LEN(size_t length); void *CMSG_DATA(struct cmsghdr *cmsg);
CMSG_DATA()宏 这个宏接受一个指向cmsghdr结构的指针。返回的指针值指向跟随在头部以及填充字节之后的附属数据的第一个字节(如果存在)。如果指针mptr指向一个描述文件描述符的可用的附属数据信息头部,这个文件描述符可以用下面的代码来得到:
struct cmsgptr *mptr; int fd; /* File Descriptor */ . . . fd = *(int *)CMSG_DATA(mptr);
CMSG_FIRSTHDR()宏 这个宏用于返回一个指向附属数据缓冲区内的第一个附属对象的struct cmsghdr指针。输入值为是指向struct msghdr结构的指针(不要与struct cmsghdr相混淆)。这个宏会估计msghdr的成员msg_control与msg_controllen来确定在缓冲区中是否存在附属对象。然后,他会计算返回的指针。 如果不存在附属数据对象则返回的指针值为NULL。否则,这个指针会指向存在的第一个struct cmsghdr。这个宏用在一个for循环的开始处,来开始在附属数据对象中遍历。
CMSG_NXTHDR()宏 这个用于返回下一个附属数据对象的struct cmsghdr指针。这个宏会接受两个输入参数: 指向struct msghdr结构的指针 指向当前struct cmsghdr的指针 如果没有下一个附属数据对象,这个宏就会返回NULL。
参考链接
Get destination address of a received UDP packet
套接字选项(四)
inet_pton & inet_ntop函数
in_addr
Listen for and receive UDP datagrams in C
关于struct msghdr和struct cmsghdr
Linux Socket Programming by Example - Warren Gay
Linux Socket学习
转载于:https://www.cnblogs.com/dancheblog/p/3527006.html
LinuxC下获取UDP包中的路由目的IP地址和头标识目的地址相关推荐
- java获取jar包中的文件资源
java获取jar包中的文件资源 一.问题示例 1.1 项目开发时 1.2 打包成jar后 二.解决方案 2.1 解决方法 2.2 实现 问题描述: 我们常常在代码中读取一些资源文件(比如图片,音 ...
- 【Android 插件化】Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
- 在swt中获取jar包中的文件 uri is not hierarchical
uri is not hierarchical 学习了:http://blog.csdn.net/zdsdiablo/article/details/1519719 在swt中获取jar包中的文件: ...
- 获取jar包中的文件,及遍历jar包中的文件
获取jar包中的文件 ClassPathResource resource1 = new ClassPathResource("文件夹" + File.separator + &q ...
- 查看python包中所有方法_获取Python包中的所有类名
我需要获取 Python包中所有类的列表. 起初我得到所有文件名(它工作正常,从stackoverflow获取): from os import listdir, getcwd from os.pat ...
- 如何在Linux中找到您的IP地址
无论我们是否知道,我们每天都在使用Internet协议(IP). 例如,每当您在Web浏览器中键入网站名称或搜索词时,它都会查询该URL(或搜索引擎)的IP地址,然后加载该网站. 让我们将IP地址分为 ...
- 获取公网、局域网、以及根据ip地址找城市
1.公网代码 package com.fan.study.ip;import java.io.BufferedReader; import java.io.IOException; import ja ...
- ubuntu修改ip地址后如何保存_如何在 Ubuntu 中检查你的 IP 地址 | Linux 中国
不知道你的 IP 地址是什么?以下是在 Ubuntu 和其他 Linux 发行版中检查 IP 地址的几种方法.-- Sergiu 不知道你的 IP 地址是什么?以下是在 Ubuntu 和其他 Linu ...
- 如何获取域名(网址)对应的IP地址
域名(Domain Name),是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位.通俗点讲,域名就是我们平时进行网络浏览时所用到的网址( ...
最新文章
- Error: Cannot find module ‘webpack-cli/bin/config-yargs‘
- C#中JSON序列化和反序列化
- 如何做波动率的分析呢?
- 科大星云诗社动态20210406
- 苹果mac休眠快捷键_Mac技巧|如何高效使用苹果便笺?用便笺快捷键快速完成操作...
- win8 - 学习资源
- tasker使用手册_如何开始使用Tasker调整Android手机
- P1081-开车旅行【倍增,链表,dp】
- The valid characters are defined in RFC 7230 and RFC 3986问题
- 例2.3 日期差值 - 九度教程第6题(日期类问题)
- 深度神经网络是谁发明的,神经网络是谁发明的人
- 游戏测试----------------第4章
- 无线网络经常掉线的12种情况
- 笔记本辐射与日常电器辐射对比
- junit5_JUnit 5测试中的临时目录
- 创建create-react-app myapp项目报错
- java当中的定时器的4种使用方式(delay,period)
- 戴尔dell工作站5820,通电后电源指示灯白灯长亮-待更新
- Matlab修改背景色
- 中兴5G解决方案打造新体验,构建新生态
热门文章
- 打破认知:程序设计 #x3D; 算法 + 数据结构?
- Envoy Proxy的多面性:边缘网关、服务网格和混合网桥
- 从头认识Spring-2.3 注解装配-@autowired(5)-限定器@Qualifier(1)
- 设计一个魔方(六面)的程序 【微软面试100题 第四十四题】
- ASP.NET MVC 的 WebGrid 的 6 个重要技巧 【已翻译100%】
- 我们也可以从学习中获得快乐 ------稻盛和夫
- BZOJ 2947 Poi2000 促销 set
- 利用SMTP收发邮件
- Python微型Web框架Bottle源码分析
- 解决由于DNS解析导致Emacs启动巨慢的问题