打印
[应用相关]

传递参数时,为何要对套接字地址进行强制类型转换

[复制链接]
513|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wakayi|  楼主 | 2019-7-4 11:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在进行IPV6的UDP设计时,偶然发现一个问题,就是大部分套接字函数都需对地址进行强制转换,先看一下程序:

这是bind函数:

bind(sockIPV6, (struct sockaddr*)&sockAddr, sizeof(sockAddr))
这是recvfrom函数:

recvfrom(sockIPV6, UdpBuffer, 100, 0, (struct sockaddr*)&sockAddr, &slen)
这是sendto函数:

sendto(sockIPV6, UdpBuffer, len, 0, (const struct sockaddr*)&sockAddr, sizeof(sockAddr))
无一例外,这些函数在处理sockAddr之前都进行了强制数据类型转换,将其转换为sockaddr。

这个问题,在进行IPV4设计时,稀里糊涂的就过去了,没有深究过,今天在写IPV6时,疑惑就比较大了。

在IPV6中,sockAddr定义的数据类型是sockaddr_in6,在IPV4中定义的数据类型是sockaddr_in,为何能转换成同一数据类型呢?仔细分析后,发现里面有很大的玄机。


使用特权

评论回复
沙发
wakayi|  楼主 | 2019-7-4 11:19 | 只看该作者
sockaddr_in的定义:


struct sockaddr_in {
  u8_t            sin_len;
  sa_family_t     sin_family;
  in_port_t       sin_port;
  struct in_addr  sin_addr;
#define SIN_ZERO_LEN 8
  char            sin_zero[SIN_ZERO_LEN];
};


sin_len:1字节,指明结构体有用数据的长度

sin_family:1字节,表示结构体的Family类型,指明是IPV4,还是IPV6

sin_port:2字节,端口号

sin_addr:4字节,IP地址

sin_zero:8字节,占位用

合计:16字节


使用特权

评论回复
板凳
wakayi|  楼主 | 2019-7-4 11:20 | 只看该作者
sockaddr_in6的定义:


struct sockaddr_in6 {
  u8_t            sin6_len;      /* length of this structure    */
  sa_family_t     sin6_family;   /* AF_INET6                    */
  in_port_t       sin6_port;     /* Transport layer port #      */
  u32_t           sin6_flowinfo; /* IPv6 flow information       */
  struct in6_addr sin6_addr;     /* IPv6 address                */
  u32_t           sin6_scope_id; /* Set of interfaces for scope */
}


sin6_len:1字节,指明结构体有用数据的长度

sin6_family:1字节,表示结构体的Family类型,指明是IPV4,还是IPV6

sin6_port:2字节,端口号

sin6_flowinfo:4字节,包含IPV6报头中的通信流类别字段和流标签字段

sin6_addr:16字节,IPV6地址

sin6_scope_id:4字节,包含了范围ID,它用于标识一系列的接口,这些接口与地址字段中的地址相对应

合计:28字节


使用特权

评论回复
地板
wakayi|  楼主 | 2019-7-4 11:20 | 只看该作者
sockaddr的定义:


struct sockaddr {
  u8_t        sa_len;
  sa_family_t sa_family;
  char        sa_data[14];
};


sin_len:1字节,指明结构体有用数据的长度

sin_family:1字节,表示结构体的Family类型,指明是IPV4,还是IPV6

sa_data:14字节,占位用

合计:16字节


使用特权

评论回复
5
wakayi|  楼主 | 2019-7-4 11:21 | 只看该作者
将sockaddr_in和sockaddr_in6转换为sockaddr是为保证代码的统一性,这样做后,socket中函数就可以采用统一的格式进行调用。

LwIP在进行初始设计时,本身不支持IPV6,所以将sockaddr_in和sockaddr定义为相同的长度。

在windows操作系统中,这3个结构体定义的长度是一致的的,都是28字节。

如果,这样问题又来了,在LwIP中sockaddr_in6和sockaddr长度不一致,是如何完成转换的呢?


使用特权

评论回复
6
wakayi|  楼主 | 2019-7-4 11:21 | 只看该作者
我们那一个socket函数进行分析就好,例如我们选择bind函数,其内部定义如下:

int
lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)
{
  struct lwip_sock *sock;
  ip_addr_t local_addr;
  u16_t local_port;
  err_t err;

  sock = get_socket(s);
  if (!sock) {
    return -1;
  }

  if (!SOCK_ADDR_TYPE_MATCH(name, sock)) {
    /* sockaddr does not match socket type (IPv4/IPv6) */
    sock_set_errno(sock, err_to_errno(ERR_VAL));
    return -1;
  }

  /* check size, family and alignment of 'name' */
  LWIP_ERROR("lwip_bind: invalid address", (IS_SOCK_ADDR_LEN_VALID(namelen) &&
             IS_SOCK_ADDR_TYPE_VALID(name) && IS_SOCK_ADDR_ALIGNED(name)),
             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
  LWIP_UNUSED_ARG(namelen);

  SOCKADDR_TO_IPADDR_PORT(name, &local_addr, local_port);
  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_bind(%d, addr=", s));
  ip_addr_debug_print_val(SOCKETS_DEBUG, local_addr);
  LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", local_port));

#if LWIP_IPV4 && LWIP_IPV6
  /* Dual-stack: Unmap IPv4 mapped IPv6 addresses */
  if (IP_IS_V6_VAL(local_addr) && ip6_addr_isipv4mappedipv6(ip_2_ip6(&local_addr))) {
    unmap_ipv4_mapped_ipv6(ip_2_ip4(&local_addr), ip_2_ip6(&local_addr));
    IP_SET_TYPE_VAL(local_addr, IPADDR_TYPE_V4);
  }
#endif /* LWIP_IPV4 && LWIP_IPV6 */

  err = netconn_bind(sock->conn, &local_addr, local_port);

  if (err != ERR_OK) {
    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_bind(%d) failed, err=%d\n", s, err));
    sock_set_errno(sock, err_to_errno(err));
    return -1;
  }

  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_bind(%d) succeeded\n", s));
  sock_set_errno(sock, 0);
  return 0;
}


使用特权

评论回复
7
wakayi|  楼主 | 2019-7-4 11:22 | 只看该作者
可以看到,对于sockaddr其使用指针进行传递的。有关name的处理有很多函数,我们通过字面上理解,可定位于下面的宏。

SOCKADDR_TO_IPADDR_PORT(name, &local_addr, local_port);
继续找到其具体定义:

#define SOCKADDR_TO_IPADDR_PORT(sockaddr, ipaddr, port) sockaddr_to_ipaddr_port(sockaddr, ipaddr, &(port))
再找到sockaddr_to_ipaddr_port的具体定义:

static void
sockaddr_to_ipaddr_port(const struct sockaddr* sockaddr, ip_addr_t* ipaddr, u16_t* port)
{
  if ((sockaddr->sa_family) == AF_INET6) {
    SOCKADDR6_TO_IP6ADDR_PORT((const struct sockaddr_in6*)(const void*)(sockaddr), ipaddr, *port);
    ipaddr->type = IPADDR_TYPE_V6;
  } else {
    SOCKADDR4_TO_IP4ADDR_PORT((const struct sockaddr_in*)(const void*)(sockaddr), ipaddr, *port);
    ipaddr->type = IPADDR_TYPE_V4;
  }
}
到这里,我们就看的很清楚了,通过sockaddr_to_ipaddr_port函数,再强制转换为相应的结构体。


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

88

主题

4087

帖子

1

粉丝