BSD套接字ip6 inet_pton以及如何检索范围ID

时间:2012-04-26 08:39:44

标签: c++ c sockets bsd

我目前正在研究IPv6类,并使用inet_pton从字符串中检索IP的实际二进制表示,即:

    AdressV6::AdressV6(const String & _ip)
    {
        int result = inet_pton(AF_INET6, _ip.c_str(), &(m_nativeAdress));

        if(result <= 0)
            //throw...

        //How can I retrieve the sope ID from that?
    }

有没有通用的方法呢?您是否只是手动解析字符串并查找听起来非常防弹的“%”:(

谢谢!

我现在尝试手动解析,这似乎有效。不过,如果有更好的方法,请告诉我:

        //retrieve scope ID
        uint32 scopeId = 0;
        size_t pos = _ip.find("%");
        if(pos != String::npos)
        {
            String theId = _ip.substr(pos+1);
            scopeId = atoi(theId.c_str());
        }
        m_scopeId = scopeId;

3 个答案:

答案 0 :(得分:4)

在基于BSD和BSD的系统(例如,包括MacOS X)上,范围ID作为第二个16位字嵌入到链路本地地址的地址本身中。请参考FreeBSD Handbook并搜索“8.1.1.3范围索引”(不带引号)。

因此假设intf1的范围ID为1且intf2的范围ID为2,inet_pton()将在以下平台上按如下方式转换字符串:

"fe80::1234%intf1" -> fe80:1::1234
"fe80::1234%intf2" -> fe80:2::1234
"fe80::1234"       -> fe80::1234

最后一个地址只是没有范围,因此无法真正用于发送数据。

请注意,这是非标准的; inet_pton()在基于Linux或Windows的系统上不起作用。但是,我认为即使在基于Linux和Windows的系统上,inet_pton()最后也允许使用范围ID,但它会忽略它。

对于非链接本地地址,这个技巧当然不起作用,但这些地址通常不是作用域的。它们可以是作用域,但通常每个接口都有一个自己唯一的接口IPv6地址,基于其接口标识符(即使您使用DHCPv6,在这种情况下它具有由DHCP服务器分配的DHCP地址,以及自动生成的) IPv6接口地址,除非禁止此自动生成。)

struct sockaddr_in6结构有一个范围ID的字段,但定义该字段的RFC(RFC 2553 - Section 3.3)并没有详细说明如何解释该字段。它只说:

  

sin6_scope_id到接口或接口集的映射是   留待实施和未来的主题规范   网站标识符。

所以这个字段完全是特定于实现的。

如果您希望正确填写此字段,并且您的代码应尽可能跨平台,则应使用getaddrinfo()

struct addrinfo hints;
struct addrinfo * result;

memset(&hints, 0, sizeof(hints));
// AI_NUMERICHOST prevents usage of DNS servers,
// it tells getaddrinfo that the input string is a numeric IP address.
hints.flags = AI_NUMERICHOST;
if (getaddrinfo("fe80::1234%intf1", NULL, &hints, &result) == 0) {
    // result->ai_addr claims to be a pointer to struct sockaddr,
    // in fact it will be a pointer to a struct sockaddr_in6 in our case.
    struct sockaddr_in6 * so = (struct sockaddr_in6 *)result->ai_addr;

    // It will be prefilled like this:
    //
    // so->sin6_family   ==> AF_INET6;
    // so->sin6_port     ==> 0
    // so->sin6_flowinfo ==> 0
    // so->sin6_addr     ==> fe80::1234
    // so->sin6_scope_id ==> "intf1" as scope ID

    // Do something with that sockaddr,
    // e.g. set a port number and connect a socket to that address.

    freeaddrinfo(result);
}

一个额外提示:如果要将返回的getaddrinfo()用于服务器套接字(要在本地绑定的套接字,然后在其上调用accept()),则还应设置被动标志:

hints.flags = AI_NUMERICHOST | AI_PASSIVE;

并非在大多数情况下都会发挥作用,但这是使用getaddrinfo()的正确方法。

答案 1 :(得分:1)

inet_pton()不支持范围ID。我不了解其他平台,但在Windows上,您可以使用RtlIpv6StringToAddressEx()代替。

答案 2 :(得分:0)

inet_pton()半支持范围标识符,范围是在使用一个地址解析地址时不会引发错误。主要限制是调用的参数是struct in6_addr,它不包含范围标识符的字段,超级结构struct sockaddr_in6是必需的。

简单的方法是将getnameinfo()getaddrinfo()包含struct sockaddr个参数以方便使用。例如,

socklen_t
sockaddr_len (
        const struct sockaddr*  sa
        )
{
        socklen_t sa_len;
        switch (sa->sa_family) {
        case AF_INET:   sa_len = sizeof(struct sockaddr_in); break;
        case AF_INET6:  sa_len = sizeof(struct sockaddr_in6); break;
        default:        sa_len = 0; break;
        }
        return sa_len;
}

int
sockaddr_ntop (
        const struct sockaddr* restrict sa,
        char*                  restrict host,
        size_t                          hostlen
        )
{
        return getnameinfo (sa, sockaddr_len (sa),
                            host, hostlen,
                            NULL, 0,
                            NI_NUMERICHOST);
}

int
sockaddr_pton (
        const char*      restrict src,
        struct sockaddr* restrict dst           /* will error on wrong size */
        )
{
        struct addrinfo hints = {
                .ai_family      = AF_UNSPEC,
                .ai_socktype    = SOCK_STREAM,          /* not really */
                .ai_protocol    = IPPROTO_TCP,          /* not really */
                .ai_flags       = AI_NUMERICHOST
        }, *result = NULL;
        const int status = getaddrinfo (src, NULL, &hints, &result);
        if (0 == status) {
                memcpy (dst, result->ai_addr, result->ai_addrlen);
                freeaddrinfo (result);
                return 1;
        }
        return 0;
}

要回答原始前提但给出struct sockaddr,可能需要额外的API,例如:

uint32_t
sockaddr_scope_id (
        const struct sockaddr*  sa
        )
{
        uint32_t scope_id;
        if (AF_INET6 == sa->sa_family) {
                struct sockaddr_in6 s6;
                memcpy (&s6, sa, sizeof(s6));
                scope_id = s6.sin6_scope_id;
        } else
                scope_id = 0;
        return scope_id;
}