Haskell: неподдерживаемая операция (семейство адресов не поддерживается семейством протоколов)

У меня есть этот фрагмент кода, подключающийся к локальному графиту (который на самом деле просто 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 (погуглил пример «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, это Протокол AF_TIPC, довольно редкая вещь, о которой я раньше не слышал. Я также открыл пакет Haskell packFamily. ' sources и был удивлен, увидев, что он не обрабатывает там значение 30 (не знает о AF_TIPC).

У меня есть вопросы: что лучше всего сделать сейчас? Я правильно понял задачу? Должен ли Haskell лучше обрабатывать неизвестное семейство ИИ? // спасибо!

ОБНОВЛЕНИЕ: я решил проблему, добавив подсказку для использования ipv4:

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

но мне все еще интересно, как решить этот вопрос "правильным путем".


person Konstantine Rybnikov    schedule 27.10.2014    source источник
comment
Причина, по которой вы используете имена AF_* вместо чисел, заключается в том, что числа не одинаковы в каждой системе. Linux socket.h не говорит вам, что означает 30 в операционных системах, отличных от Linux. Лучше проверьте socket.h на своей машине. (Я предсказываю, что 30 будет AF_INET6)   -  person    schedule 27.10.2014
comment
@WumpusQ.Wumbley, вы правы, спасибо! AF_INET6 равен 30 в socket.h на моей машине.   -  person Konstantine Rybnikov    schedule 27.10.2014
comment
@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 */   -  person Konstantine Rybnikov    schedule 27.10.2014
comment
@WumpusQ.Wumbley вот socket.h paste.ubuntu.com/8704870   -  person Konstantine Rybnikov    schedule 27.10.2014
comment
Я знаю только сторону C этого вопроса... но я подозреваю, что 17 относится к IPPROTO_UDP. Если вы хотите использовать это поле результата getaddrinfo, оно передается третьим аргументом в socket(). Обычно вы можете поставить 0, потому что семейства адресов и типа сокета (SOCK_DGRAM, SOCK_STREAM и т. д.) достаточно для однозначного определения протокола.   -  person    schedule 27.10.2014


Ответы (1)


По умолчанию 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. Когда вы это сделаете, не забудьте закрыть сокеты, которые не могут подключиться.

person jch    schedule 27.10.2014
comment
Спасибо @jch. Но вот чего я не понимаю: › К сожалению, функции сокетов не позволяют подключить сокет IPv4 к локальному хосту с IPv6-адресом (и его адрес [::1] в значительной степени является адресом AF_INET6, поэтому я до сих пор не понимаю, почему я получаю ошибка. - person Konstantine Rybnikov; 27.10.2014
comment
Как вы создали сокет? - person jch; 27.10.2014
comment
спасибо! Ты прав. В библиотеке github.com/ocharles/network-carbon/blob/master/src/Network/ - person Konstantine Rybnikov; 27.10.2014