第一,先说说两个结构体分别所在头文件的位置:
sockaddr_in 在头文件 #include <netinet/in.h>中
sockaddr 在头文件 #include <sys/socket.h>中
第二,两个结构体的原型:
- /* Internet address. */
- typedef uint32_t in_addr_t;
- struct in_addr
- {
- in_addr_t s_addr;
- };
/* Structure describing an Internet socket address. */
- struct sockaddr_in
- {
- __SOCKADDR_COMMON (sin_);
- in_port_t sin_port; /* Port number. */
- struct in_addr sin_addr; /* Internet address. */
- /* Pad to size of `struct sockaddr'. */
- unsigned char sin_zero[sizeof (struct sockaddr) -
- __SOCKADDR_COMMON_SIZE -
- sizeof (in_port_t) -
- sizeof (struct in_addr)];
- };
/* Structure describing a generic socket address. */
- struct sockaddr
- {
- __SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
- char sa_data[14]; /* Address data. */
- };
第三,二者的用法和区别:
有人在建立socket的时候,很少有人去在意两个结构体的实际区别,而是直接套用demo,一气呵成,适当改改传入参数就OK了,人家的结构体之间的关联肯定是一点问题都没有的,这一点我们必须承认。
但是对于新手,在程序设计使用这些结构体时往往会疏忽大意,这里有一个罕见但是极为隐蔽的BUG存在,我们一起看看,
以一段代码做示例,进行说明:
- int tcp_connect(unsigned short port, char *addr)
- {
- int f;
- int on=1;
- int one = 1;/*used to set SO_KEEPALIVE*/
- struct sockaddr_in s;
- int v = 1;
- if((f = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))<0)
- {
- fprintf(stderr, "socket() error in tcp_connect.\n");
- return -1;
- }
- setsockopt(f, SOL_SOCKET, SO_REUSEADDR, (char *) &v, sizeof(int));
- s.sin_family = AF_INET;
- s.sin_addr.s_addr = inet_addr(addr);
- s.sin_port = htons(port);
- // set to non-blocking
- if(ioctl(f, FIONBIO, &on) < 0)
- {
- fprintf(stderr,"ioctl() error in tcp_connect.\n");
- return -1;
- }
- if(connect(f,(struct sockaddr*)&s, sizeof(s)) < 0)
- {
- fprintf(stderr,"connect() error in tcp_connect.\n");
- return -1;
- }
- if(setsockopt(f, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one))<0)
- {
- fprintf(stderr,"setsockopt() SO_KEEPALIVE error in tcp_connect.\n");
- return -1;
- }
- return f;
- }
其中第6行,第26行红色字体部分为两个结构体使用的方式,sockaddr_in 用于结构体定义,而sockaddr 用于结构体类型的转换
这里对照一下两个结构体的原型,我们发现,真正存储IP地址的变量是什么?
sockaddr_in 中的IP地址存储在 sin_addr
sockaddr 中的IP地址存储在 sa_data[14]
问题就在这,sin_addr的原型是 typedef uint32_t in_addr_t; 是一个占用4个字节的无符号整形,这样他在存储四段IP地址例如255.255.255.255以下的IP 是不存在任何问题的
但是 sa_data[14]存储 255.255.255.255的IP地址就出问题了,它是一种以字符形式存储的,所以14字节的空间是不够的,出现溢出问题,这里我们先了解一下两个结构体的区别
1)sa_data[14], 把目标地址和端口信息存在了一起,是一个通用地址结构,为统一地址结构,统一接口函数,使不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用
2)sin_family:指代协议族,在socket编程中只能是AF_INET
sin_port:存储端口号(使用网络字节顺序)
sin_addr:存储IP地址,使用in_addr这个数据结构
sin_zero:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节,是通过宏计算得到的动态地址空间。
以上两点就是主要区别,注意,sockaddr_in 的地址空间会随着 sockaddr 的地址空间变化
关于网上很多讲解,不多说!
我们回到之前提出的问题:
当你使用 类似 这种字符串拷贝函数strcpy()的时候,想把字符数组中的网络字符型地址IP拷贝到sockaddr 变量中,看似没有问题,档遇到四段位的IP地址拷贝是,会溢出了14字节的空间,想解决这个问题有两个方法
其一:修改 sockaddr 中 sa_data[14] 为 sa_data[16]
其二:不要使用strcpy这类的函数直接拷贝,其实在程序设计时,有时候会忽视这里,有的朋友如果是遇到此类问题,看到本文会有所体会,但是如果没有遇到过可能不会理解(这里除外那些明白的大神会 噗呲的笑出声,什么智商会这么用啊)
所以本文只针对那些对 结构体不太透彻的人
为说明问题,继续举例:
- struct ifreq stIfr; //linux 网络接口用来配置ip地址
- struct sockaddr stServerAddr;
- char s8Str[128];
- strcpy(stIfr.ifr_name, "eth0");
- if(ioctl( fd, SIOCGIFADDR, &stIfr) < 0)
- {
- //printf("Failed to get host eth0 ip\n");
- strcpy(stIfr.ifr_name, "wlan0");
- if(ioctl( fd, SIOCGIFADDR, &stIfr) < 0)
- {
- printf("Failed to get host eth0 or wlan0 ip\n");
- }
- }
- sock_ntop_host(&stIfr.ifr_addr, sizeof(struct sockaddr), s8Str, 128);
经过以上调用后,IP地址就顺理成章的存储在了 s8Str 数组中,这时,你先提取IP地址,
信手拈来 strcpy (stServerAddr.sa_data,s8Str , sizeof(s8Str ));
这就是问题导火线,sa_data[14]极有可能发生溢出,为后续使用带来诸多麻烦
笔者在此做出提示,也是对我遇到此类问题产生的总结