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支持—不是

Echo Reply 网络不可达 主机不可达 协议不可达 端口不可达 重定向 Echo Request socket网络不可达_c语言


板子和主机都是有链路本地地址的,所以都是开启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上使用套接字不太熟悉,遇到问题只能去网上找是不是有一样的问题,或者换代码,还是得对自己的代码思路清晰一点,增加解决问题的能力