Linux 上IPv6 udp套接字在板子上总是出现网络不可达,在主机上只有发送缓存<40字节才 能发送,41个字节的都会出现网络不可达。
问题描述:
最开始只发现了再主机上可以跑(测试数据<40字节),然后移到板子上(由于需求,在
板子上测试的时候就直接测试60字节数据,然后就发现不通,但是当时没注意到这个差别)而且打印系统返回的错误“网络不可达”,就以为是板子的IPv6配置或是板子对v6套接字的支持问题。
存在问题代码
/*
* IPv6套接字编程
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h> // for close()
//IPv6压缩格式地址
static char src[] = "fe80::aa5e:45ff:fe32:4cc4";
static char dst[] = "fe80::b934:59c9:1a44:a672";
int main()
{
int udp6_socket,ret,addr_len;
struct sockaddr_in6 saddr;
struct sockaddr_in6 daddr;
//char buffer[] = "Hello world6HHHHHHHHHHHHHHHHHHHHHHH";
//u_int32_t buffer[360];
u_int32_t buffer[360];
// int buffer[360];
//u_int16_t buf[360];
for(size_t i = 0; i < 48; i++)
{
buffer[i] = 'A'+i;
//buf[i] = 'A'+i;
}
//memcpy(buf,buffer,sizeof(buffer));
/*
u_int8_t * p = (u_int8_t *)buf;
for(size_t i = 0; i < sizeof(buffer); i++)
{
p[i] = buffer[i];
}
*/
/*
for(size_t i = 0; i < 50; i++)
{
printf("%x ",buf[i]);
if (i%16 ==0) {
printf("\n");
}
}
*/
// 设置IPv6地址,这个函数支持三种IPv6地址格式,包括首选地址,压缩格式和映射格式
if ((inet_pton(AF_INET6,(char *)&src[0], &saddr.sin6_addr) != 1))
{
printf("invalid ipv6 addr \r\n");
return -1;
}
if ((inet_pton(AF_INET6,(char *)&dst[0], &daddr.sin6_addr) != 1))
{
printf("invalid ipv6 adddr \r\n");
return -1;
}
bzero(&saddr,sizeof(saddr));
bzero(&saddr,sizeof(saddr));
saddr.sin6_family = AF_INET6;
saddr.sin6_port = htons(30000);
daddr.sin6_family = AF_INET6;
daddr.sin6_port = htons(40000);
//创建IPv6套接字,IPv6使用AF_INET6
udp6_socket = socket(AF_INET6, SOCK_DGRAM,0);
int result = setsockopt(udp6_socket,SOL_SOCKET,SO_BINDTODEVICE,"eth0",sizeof("eth0"));
if (result == -1)
{
printf("setsockopt fail,errno: %d ,%s \n",errno,strerror(errno));
}
if (udp6_socket == -1)
{
printf("create udp6_socket fail\r\n");
return -1;
}
//绑定地址
addr_len = sizeof(struct sockaddr_in6);
///* ?????????Dont know why there will be an unreached error if I try to bind
int i = bind(udp6_socket,(struct sockaddr*)&saddr, addr_len);
if (i != 0) {
printf("udp6 bind addr fail,errno: %d ,%s \n",errno,strerror(errno));
//printf("errInfo: %s \r\n", strerror(errno));
close(udp6_socket);
return -1;
}
//*/
i = 0;
//发送
while(1)
{
i++;
//ret = sendto(udp6_socket, &buf[0], sizeof(buf), 0, (struct sockaddr *)&daddr, addr_len);
// ret = sendto(udp6_socket, &buffer[0], 48, 0, (struct sockaddr *)&daddr, addr_len);
ret = sendto(udp6_socket, buffer, 24, 0, (struct sockaddr *)&daddr, addr_len);
if (ret > 0)
{
printf("%d :udp6 send %d bytes success \r\n",i,ret);
}
else
{
printf("send errno: %d ,error: %s \n ",errno,strerror(errno));
//break;
sleep(1);
continue;
}
sleep(1);
}
//关闭套接字
close(udp6_socket);
return 0;
}
解决过程:
1、 查看是否开启IPv6支持—不是
板子和主机都是有链路本地地址的,所以都是开启IPv6的。
2、然后就想着是不是IPv6的路由没配好,结果发现当双方是直连的时候,是本地链路IPv6地址,根本不需要配置理由,而且也是支持v6的
3、后来发现主机上有时候也会出现网络不可达的问题,
再仔细一看,发现发送缓存只要>40字节就会网络不可达,<40是可以正常传输的。
4、一下就蒙了,这是为啥,为啥刚好卡在40字节,就尝试改了改发送缓存数组的的类型
//这是原来定义的以8位位基本单位的缓存数组
unsigned char buf[20];
//改成其他大于8位的类型就都可以发送长度>40的数组就可以成功发送
unsigned u_int16_t buf[60];
5、把之前写的IPv6 原始套接字的代码跑了一下,发现可以跑
综上,说明不是板子对v6机制支持,或者v6路由配置的问题。
然后就重写了一下套接字部分的代码,(区别感觉就是使用的结构体不同了),然后就不存在上述问题了。ok
以下是能正确发送所有合理长度的IPv6 udp 套接字的代码版本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <net/if.h>
int main()
{
int sd,i;
sd = socket(AF_INET6,SOCK_DGRAM,0);
if (sd < 0) {
perror("socket");
return -1;
}
struct ifreq req;
strcpy(req.ifr_name,"eth0");
if (ioctl(sd,SIOCGIFINDEX, &req) < 0) {
perror("siocgifindex");
return -2;
}
struct sockaddr_in6 to;
to.sin6_family = AF_INET6;
to.sin6_port = htons(10086);
to.sin6_scope_id = req.ifr_ifindex;
if (inet_pton(AF_INET6,"fe80::b934:59c9:1a44:a672", &to.sin6_addr) <= 0) {
perror("pton");
return -3;
}
char data[200];
for(i = 0; i < 10; i++)
{
sprintf(data,"ipv6 udp test %d\n",i);
// if (sendto(sd,data,strlen(data),0,(struct sockaddr *)&to, sizeof(to)) < 0)
if (sendto(sd,data,sizeof(data),0,(struct sockaddr *)&to, sizeof(to)) < 0)
{
perror("sendto");
}
}
close(sd);
return 0;
}
总结:
唉,还是对Linux上使用套接字不太熟悉,遇到问题只能去网上找是不是有一样的问题,或者换代码,还是得对自己的代码思路清晰一点,增加解决问题的能力