通过设置套接字的选项(IP_PKTINFO) 来获取收到报文的源地址和目的地址。

这里使用的是调用的是recvmsg接口,实际上一个还有更简单的接口recvfrom接口也能够满足要求,可以参考这篇博客

javascript:void(0)

代码如下:

/*
 *  Description : UDP套接字编程获取源地址和目的地址
 *  Date : 20180527
 */

#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <sys/uio.h> 
#include <stdint.h>  
#include <stdlib.h>
#include <unistd.h>  
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include "udptest.h"


void main() {
    int on = 1;
    int ret, fd, addr_len, recv_len;
    
	char buffer[512] = {0};   /* 接收缓存 */
	struct msghdr msg;
	struct iovec iov[1];    
	struct sockaddr_in saddr; 	/* src */
	struct sockaddr_in daddr; 	/* dst */
	struct cmsghdr *cmhp;           
    struct sockaddr_in server_addr;   
	struct in_pktinfo *pktinfo = NULL;	 /* 用于指向获取的本地地址信息 */	
	char buff[CMSG_SPACE(sizeof(struct in_pktinfo) + CMSG_SPACE(sizeof(int)))] = {0};   /* 控制信息 */
	struct cmsghdr *cmh = (struct cmsghdr *)buff;   /* 控制信息 */
    addr_len = sizeof(struct sockaddr_in);

    memset(&buffer, 0, sizeof(buffer));
    memset(&msg, 0, sizeof(struct msghdr));
    memset(&iov, 0, sizeof(struct iovec));
    memset(&saddr, 0, sizeof(struct sockaddr_in));
    memset(&daddr, 0, sizeof(struct sockaddr_in)); 

    // 创建UDP套接字
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1) {
        log("create socket fail \r\n");
        return ;
    }    

    // 设置监听地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY; // 所有地址
    server_addr.sin_port = htons(3500);

    // 设置SO_REUSEADDR属性, 地址复用    
    if ((ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) != 0) {
        log("setsockopt reuseaddr fail, ret : %d,error : %d \r\n", ret, errno);
        close(fd);
        return ;
    }

    /* 设置IP_PKTINFO属性 */
    if (0 != setsockopt(fd, IPPROTO_IP, IP_PKTINFO, (char *)&on, sizeof(on))) {
        log("setsockopt ip_pktinfo fail, errno : %d \r\n", errno);
        close(fd);
        return ;  
    }    

    // 绑定本地监听地址
    if (0 != bind(fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))) {
        printf("bind local listening addr fail,errno : %d \r\n", errno);
        close(fd);
        return ;
    }    

    // 接收信息
     while(1) {
         msg.msg_name = &saddr;     // 存储报文来源地址,端口
         msg.msg_namelen = addr_len;
         msg.msg_iov = &iov[0];
         msg.msg_iovlen = 1;
         msg.msg_control = cmh;
         msg.msg_controllen = sizeof(buff);    
         iov[0].iov_base = &buffer;
         iov[0].iov_len = sizeof(buffer);                     
    
         // 超时退出
         recv_len = recvmsg(fd, &msg, 0);
         if (recv_len > 0)
         {
             /* 辅助信息 */
             msg.msg_control = cmh;
             msg.msg_controllen = sizeof(buff);
             for (cmhp = CMSG_FIRSTHDR(&msg); cmhp; cmhp = CMSG_NXTHDR(&msg, cmhp)) {
                 if (cmhp->cmsg_level == IPPROTO_IP) {
                     if (cmhp->cmsg_type == IP_PKTINFO) {
                         pktinfo = (struct in_pktinfo *)CMSG_DATA(cmhp);
                         daddr.sin_family = AF_INET;
                         daddr.sin_addr = pktinfo->ipi_addr;
                         /* 报文源地址、端口 */
                         log("saddr : %u:%u:%u:%u:%hu \r\n", NIPQUAD(saddr.sin_addr), ntohs(saddr.sin_port));
                         /* 报文目的地址 */
                         log("daddr : %u:%u:%u:%u \r\n", NIPQUAD(daddr.sin_addr));     
			 log("daddr : %u:%u:%u:%u \r\n", NIPQUAD(pktinfo->ipi_spec_dst));
                     }
                 }
             }

         }
         memset(buffer, 0, sizeof(buffer));
         memset(buff, 0, sizeof(buff));
         memset(&saddr, 0, sizeof(struct sockaddr_in));
         memset(&daddr, 0, sizeof(struct sockaddr_in));        
     }

    return ;
}

头文件:

#ifndef _UDPTEST_H
#define _UDPTEST_H

#define log(fmt, arg...) printf("[udptest] %s:%d "fmt, __FUNCTION__, __LINE__, ##arg)

#ifndef NIPQUAD
#define NIPQUAD(addr) \
	((unsigned char *)&addr)[0], \
	((unsigned char *)&addr)[1], \
	((unsigned char *)&addr)[2], \
	((unsigned char *)&addr)[3]
#endif

#endif

Makefile:

default:
	@gcc -o udptest udptest.c
clean:
	@rm -rf udptest	

命令行执行程序后使用网络调试助手发送报文查看打印信息:

linux udp 套接字编程获取源地址和目的地址(一)_套接字

这里可以看到有两个目的地址。这是因为在设置IP_PKTINFO 选项后,返回的结构体包含两个目的地址,通常情况下这两个地址是一样的,路由目的地址指的是报文原始的目的地址,如果是广播报文的话这里就是255.255.255.255,头标识目的地址指的是本地程序的地址,例如一台路由器可能连接多台PC,当外部程序发送报文的时候填写到目的地址是路由器的地址,路由器会把这样到报文转发到对应到设备,这时头标识目的地址就是对应设备的地址。

 

struct in_pktinfo
    {
        unsigned int ipi_ifindex;    /* 接口索引 */
        struct in_addr ipi_spec_dst; /* 路由目的地址 */
        struct in_addr ipi_addr;     /* 头标识目的地址 */
    };

通过wireshark 可以看出:

linux udp 套接字编程获取源地址和目的地址(一)_udp_02

 

linux 网络调试助手可以在这里下载,当然也可以找到对应到windows版本

链接: https://pan.baidu.com/s/1Z15YxE1r-dIOU_l9saEO0A

密码: i7am