Haskell:不支持的操作(地址族未被协议族支持)

3
我有一段代码连接到本地的Graphite(实际上只是运行在本地主机上的nc -l -p 2023):
getCarbonAddr :: Config -> IO SockAddr
getCarbonAddr cfg = do
    let host = (graphiteHost . graphiteConfig) cfg
    let port = (graphitePort . graphiteConfig) cfg
    -- addrInfos <- getAddrInfo (Just defaultHints)
    addrInfos <- getAddrInfo Nothing
                   (Just host)
                   (Just (show port))
    putStrLn $ "addrInfos: " ++ show addrInfos
    c <- case addrInfos of
           (addrInfo : _) -> return (addrAddress addrInfo)
           _ -> unsupportedAddressError host
    return c
  where
    unsupportedAddressError h = ioError $ userError $
      "unsupported address: " ++ h

主机和端口的配置值分别为"localhost"和2023。当我在我的OS X上升级到Yosemite后运行此程序时,我看到以下崩溃:

addrInfos: [AddrInfo {addrFlags = [], addrFamily = AF_INET6, addrSocketType = Datagram, addrProtocol = 17, addrAddress = [::1]:2023, addrCanonName = Nothing},AddrInfo {addrFlags = [], addrFamily = AF_INET6, addrSocketType = Stream, addrProtocol = 6, addrAddress = [::1]:2023, addrCanonName = Nothing},AddrInfo {addrFlags = [], addrFamily = AF_INET, addrSocketType = Datagram, addrProtocol = 17, addrAddress = 127.0.0.1:2023, addrCanonName = Nothing},AddrInfo {addrFlags = [], addrFamily = AF_INET, addrSocketType = Stream, addrProtocol = 6, addrAddress = 127.0.0.1:2023, addrCanonName = Nothing},AddrInfo {addrFlags = [], addrFamily = AF_INET6, addrSocketType = Datagram, addrProtocol = 17, addrAddress = [fe80::1%lo0]:2023, addrCanonName = Nothing},AddrInfo {addrFlags = [], addrFamily = AF_INET6, addrSocketType = Stream, addrProtocol = 6, addrAddress = [fe80::1%lo0]:2023, addrCanonName = Nothing}]
LocalJob: connect: unsupported operation (Address family not supported by protocol family)

这个问题对我来说很奇怪,所以我决定运行这个C程序(在Google中搜索“getaddrinfo”示例,更改主机名和端口,并添加ai_family的打印):

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>

#ifndef   NI_MAXHOST
#define   NI_MAXHOST 1025
#endif

int main(void)
{
    struct addrinfo *result;
    struct addrinfo *res;
    int error;

    /* resolve the domain name into a list of addresses */
    error = getaddrinfo("localhost", "2023", NULL, &result);
    if (error != 0)
    {
        fprintf(stderr, "error in getaddrinfo: %s\n", gai_strerror(error));
        return EXIT_FAILURE;
    }

    /* loop over all returned results and do inverse lookup */
    for (res = result; res != NULL; res = res->ai_next)
    {
        char hostname[NI_MAXHOST] = "";

        error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0);
        if (error != 0)
        {
            fprintf(stderr, "error in getnameinfo: %s\n", gai_strerror(error));
            continue;
        }
        if (*hostname != '\0')
            printf("hostname: %s. ai_family: %i\n", hostname, res->ai_family);
    }

    freeaddrinfo(result);
    return EXIT_SUCCESS;
}

启动后,我看到了这个输出:
  getaddrinfotest  ./main
hostname: localhost. ai_family: 30
hostname: localhost. ai_family: 30
hostname: localhost. ai_family: 2
hostname: localhost. ai_family: 2
hostname: localhost. ai_family: 30
hostname: localhost. ai_family: 30

所以,ai_family 30 看起来很奇怪。根据 socket.h sources 中的理解,它是 AF_TIPC 协议,这是我之前从未听说过的相当罕见的东西。我还打开了 haskell 的 packFamily' sources,惊讶地发现它在那里没有处理值为 30(不知道 AF_TIPC)。

我的问题是:现在该怎么办?我是否正确理解了问题?haskell 是否应该更好地处理未知的 ai family?//谢谢!

更新:通过提示使用 ipv4,我解决了一个问题:

addrInfos <- getAddrInfo (Just (defaultHints { addrFamily=AF_INET }))
               (Just host)
               (Just (show port))

但我仍然想知道如何以“正确的方式”解决这个问题。


你使用 AF_* 名称而不是数字的原因是数字在每个系统上都不同。Linux 的 socket.h 在非 Linux 操作系统上无法告诉你 30 的含义。最好检查一下自己机器上的 socket.h。(我预测 30 将是 AF_INET6 - user2404501
@WumpusQ.Wumbley 你是正确的,谢谢!在我的机器上,AF_INET6 在 socket.h 中是30。 - Konstantine Rybnikov
@WumpusQ.Wumbley 所以,在我尝试连接的这个地址中:AddrInfo {addrFlags = [],addrFamily = AF_INET6,addrSocketType = Datagram,addrProtocol = 17,addrAddress = [::1]:2023,addrCanonName = Nothing} 我看到它正确地将 addrFamily 解析为 AF_INET6,但我想知道 addrProtocol 17 是什么意思。我找到的唯一有价值的 17 是 #define AF_ROUTE 17 /* Internal Routing Protocol */ - Konstantine Rybnikov
@WumpusQ.Wumbley 这是socket.h的链接:http://paste.ubuntu.com/8704870/ - Konstantine Rybnikov
我只了解这个问题的 C 方面... 但我怀疑 17 是指 IPPROTO_UDP。如果您想使用 getaddrinfo 结果中的该字段,它应该放在 socket() 的第三个参数中。通常你可以把它设置为 0,因为地址族和套接字类型 (SOCK_DGRAMSOCK_STREAM 等) 足以唯一确定一个协议。 - user2404501
1个回答

3

默认情况下,getaddrinfo(以及其Haskell绑定getAddrInfo)返回一个addrinfo的链接列表,其中可以包含IPv4地址、IPv6地址或两者混合。不幸的是,sockets函数不允许将IPv4套接字连接到IPv6地址,因此在迭代地址列表时,您需要创建正确类型的套接字:

addrinfos <- getAddrInfo Nothing (Just hostname) (Just (show port))
let first = head addrinfos
sock <- socket (addrFamily first) Stream defaultProtocol
connect sock (addrAddress first)

请注意,将 (addrFamily first) 传递给 socket 函数,以便在正确的协议族中创建套接字。
在实际代码中,您需要遍历列表 addrinfos 并尝试连接 getAddrInfo 返回的所有地址。在执行此操作时,请不要忘记关闭无法连接的套接字。

谢谢@jch。但是有一件事我不明白:
不幸的是,套接字函数不允许将IPv4套接字连接到IPv6地址
localhost(及其地址[::1])几乎是AF_INET6地址,所以我仍然不明白为什么会出现错误。
- Konstantine Rybnikov
1
你是如何创建套接字的? - jch
谢谢!你是正确的。在这个库中,它被硬编码为ipv4 https://github.com/ocharles/network-carbon/blob/master/src/Network/Carbon/Plaintext.hs#L47 - Konstantine Rybnikov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接