简体   繁体   English

BSD套接字ip6 inet_pton以及如何获取作用域ID

[英]BSD Sockets ip6 inet_pton and how to retrieve the scope ID

I am currently working on an IPv6 class and use inet_pton to retrieve the actual binary representation of the IP from a string ie: 我目前正在研究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?
    }

Is there a common way to do that? 有一种通用的方法吗? Do you just manually parse the string and look for the "%" that does not sound very bullet proof :( 您是否只是手动解析字符串并寻找听起来不太防弹的“%”,:(

Thank you! 谢谢!

I tried manual parsing for now which seems to work. 我现在尝试手动解析,这似乎可行。 Still, if there is a better way please let me know: 不过,如果有更好的方法,请让我知道:

        //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;

On BSD and BSD based systems (this includes MacOS X for example), the scope ID is embedded into the address itself for link local addresses as the second 16 bit word. 在BSD和基于BSD的系统上(例如,包括MacOS X),范围ID嵌入到地址本身中,作为第二个16位字用于链接本地地址。 Please refer to the FreeBSD Handbook and search for "8.1.1.3 Scope Index" (without the quotes). 请参考FreeBSD手册并搜索“ 8.1.1.3范围索引”(不带引号)。

So assuming that intf1 has scope ID 1 and intf2 has scope ID 2, inet_pton() will convert the strings as follows on these platforms: 因此,假设intf1的作用域ID为1,intf2的作用域ID为2, inet_pton()将在这些平台上按以下方式转换字符串:

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

The last address is simply unscoped and thus cannot be really used for sending out data. 最后一个地址只是不受作用域限制,因此不能真正用于发送数据。

Please note that this is non-standard; 请注意,这是非标准的; inet_pton() does not work that way on Linux or Windows based systems. inet_pton()在基于Linux或Windows的系统上无法正常工作。 However, I think even on Linux and Windows based systems, inet_pton() allows a scope ID at the end, it will simply ignore it, though. 但是,我认为即使在基于Linux和Windows的系统上, inet_pton()也会在最后允许一个作用域ID,但是它只会忽略它。

For non-link-local address, this trick doesn't work, of course, yet those addresses are usually not scoped. 对于非链接本地地址,此技巧当然不起作用,但是这些地址通常没有范围。 They can be scoped, but usually every interface has an own, unique interface IPv6 address, based on its interface identifier (even if you use DHCPv6, in which case it has a DHCP address assigned by the DHCP server, as well as the auto generated IPv6 interface address, unless this auto generation has been forbidden). 可以对它们进行范围划分,但是通常每个接口都有一个唯一的接口IPv6地址,具体取决于其接口标识符(即使您使用DHCPv6,在这种情况下,它也具有由DHCP服务器分配的DHCP地址以及自动生成的IPv6接口地址,除非禁止自动生成)。

The struct sockaddr_in6 structure has a field for the scope ID but the RFC that defines this field ( RFC 2553 - Section 3.3 ) does not really give much detail how this field is to be interpreted. struct sockaddr_in6结构具有一个用于范围ID的字段,但是定义此字段的RFCRFC 2553-第3.3节 )并未真正给出如何解释该字段的详细信息。 It only says: 它只说:

The mapping of sin6_scope_id to an interface or set of interfaces is left to implementation and future specifications on the subject of site identifiers. sin6_scope_id到一个接口或一组接口的映射取决于站点标识符的实现和将来的规范。

So this field is entirely implementation specific. 因此,该字段完全是特定于实现的。

If you want this field to be filled in correctly, and your code should be as cross-platform as possible, you should use getaddrinfo() : 如果您希望此字段正确填写,并且您的代码应尽可能跨平台,则应使用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);
}

One extra tip: If you want to use the returned getaddrinfo() for a server socket (a socket that you want to bind locally and then call accept() on it), you should also set the passive flag: 一个额外的提示:如果要将返回的getaddrinfo()用于服务器套接字(要在本地绑定然后在其上调用accept()的套接字),则还应该设置被动标志:

hints.flags = AI_NUMERICHOST | AI_PASSIVE;

Not that it will play a role in most case but that is the correct way of using getaddrinfo() . 并不是说它在大多数情况下都会发挥作用,而是那是使用getaddrinfo()的正确方法。

inet_pton() does not support scope IDs. inet_pton()不支持作用域ID。 I don't know about other platforms, but on Windows you can use RtlIpv6StringToAddressEx() instead. 我不知道其他平台,但是在Windows上,您可以使用RtlIpv6StringToAddressEx()代替。

inet_pton() semi-supports scope identifiers, the scope is that it will not raise an error when parsing an address with one. inet_pton()半支持作用域标识符,作用域是在解析一个地址时不会引发错误。 The major limitation is that the parameter to the call is a struct in6_addr which does not contain a field for the scope identifier, the super structure struct sockaddr_in6 is required for that. 主要限制是调用的参数是struct in6_addr ,它不包含作用域标识符的字段,为此需要超结构struct sockaddr_in6

Easy way forward is to wrap getnameinfo() and getaddrinfo() with struct sockaddr parameters for convenience. 前进的简单方法是使用struct sockaddr参数包装getnameinfo()getaddrinfo()以便于使用。 For example, 例如,

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;
}

To answer the original premise but given a struct sockaddr , an additional API may be warranted, for example: 为了回答最初的前提但给出了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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM