Hatred's Log Place

DON'T PANIC!

Mar 5, 2016 - 3 minute read - programming c++

Source Specific Multicast и Asio

Недавно на ru.SO проскочил вопрос: как можно подключиться к SSM ( Source Specific Multicast) группе?

Нюанс в том, что для этого используется опция MCAST_JOIN_SOURCE_GROUP для которой нет объекта-обёртки. Но, как оказалось, такой объект пишется самостоятельно на раз-два. Под катом я продублирую свой ответ, как пример подхода реализации нужного функционала. Пример не самый идеологически правильный, но, как оказалось, рабочий. Автор сам предложил свой вариант с захватом сырого хендла. Такой подход тоже имеет смысл в некоторых ситуациях((у меня случилось однажды так подружить сетевые абстракции Asio и libev, реализовав, тем самым, реактор на Asio :simple_smile:)).

Что-бы установить опцию при помощи socket.set_option() (или прочитать текущее значение), нужно создать класс со следующим публичным интерфейсом:

  • template<typename Protocol> int level(const Protocol&) const - определяет уровень (IPPROTO_IP, IPPROTO_TCP, SOL_SOCKET и так далее)
  • template<typename Protocol> int name(const Protocol&) const - замысловато, но это ID опции (TCP_CORK, MCAST_JOIN_SOURCE_GROUP)
  • template<typename Protocol> SOME_DATA_T* data(const Protocol&) и
  • template<typename Protocol> const SOME_DATA_T* data(const Protocol&) const - сами данные опции
  • template<typename Protocol> std::size_t size(const Protocol&) const - вернёт размер данных
  • template<typename Protocol> void resize(const Protocol&, std::size_t s) - не совсем понимаю, просто проверять sizeof(data) == s и бросать исключение, если не выполняется.

SOME_DATA_T может быть специфичен для опции или быть просто void * (всё равно передаётся в setsockopt()). Для MCAST_JOIN_SOURCE_GROUP это будет struct group_source_req.

Собственно реализация для MCAST_JOIN_SOURCE_GROUP будет такой:

// Просто для помощи ради
using group_source_req_t = struct group_source_req;

// Костыль для ASIO_OS_DEF, что бы можно было на разных системах примерно одинаково работать
// типа если только опция отличается, но не данные. В данном случае можно обойтись вообще без этого
#define ASIO_OS_DEF_MCAST_JOIN_SOURCE_GROUP MCAST_JOIN_SOURCE_GROUP

struct mcast_join_source_group
{
    mcast_join_source_group()
        : m_data()
    {}

    mcast_join_source_group(std::string &source_ip, std::string &group_ip, int port, std::string &interface_name)
        : mcast_join_source_group()
    {
          int ifname_number = if_nametoindex(interface_name.c_str());

          struct sockaddr_in group_addr;
          group_addr.sin_addr.s_addr = inet_addr(group_ip.c_str());
          group_addr.sin_port = htons(port);
          group_addr.sin_family = AF_INET;

          struct sockaddr_in source_addr;
          source_addr.sin_addr.s_addr = inet_addr(source_ip.c_str());
          source_addr.sin_port = htons(port);
          source_addr.sin_family = AF_INET;

          memcpy(&(m_data.gsr_source), (struct sockaddr_storage *) &source_addr, sizeof(struct sockaddr_in));
          memcpy(&(m_data.gsr_group), (struct sockaddr_storage *) &group_addr, sizeof(struct sockaddr_in));

          m_data.gsr_interface = ifname_number;
    }

    // Можно объявить вспомогательные методы для правки отдельных значений или всех полей

    template<typename Protocol>
    int level(const Protocol&) const
    {
        // Протокол, к слову, может быть и IPv6, для них обоих она работает одинаково. За исключением пары нюансов
#ifdef ASIO_OS_DEF
        return ASIO_OS_DEF(IPPROTO_IP);
#else
        return IPPROTO_IP;
#endif
    }

    template<typename Protocol>
    int name(const Protocol&) const
    {
#ifdef ASIO_OS_DEF
        return ASIO_OS_DEF(MCAST_JOIN_SOURCE_GROUP);
#else
        return MCAST_JOIN_SOURCE_GROUP;
#endif
    }

    template<typename Protocol>
    group_source_req_t* data(const Protocol&)
    {
        return &m_data;
    }

    template<typename Protocol>
    const group_source_req_t* data(const Protocol&) const
    {
        return &m_data;
    }

    template<typename Protocol>
    std::size_t size(const Protocol&) const
    {
        return sizeof(m_data);
    }

    template<typename Protocol>
    void resize(const Protocol&, std::size_t s)
    {
        if (s != sizeof(m_data))
        {
            std::length_error ex("mcast_join_source_group socket option resize");
            asio::detail::throw_exception(ex);
        }
    }

private:
    group_source_req_t m_data;
};

Пользоваться как-то так:

socket.set_option(mcast_join_source_group("91.203.255.225", "239.195.1.131", 17003, "vlan389"));

Данную реализацию можно улучшить, применив абстракции Asio типа asio::ip:address и/или asio::ip::endpoint.

Для простых опций уровня Вкл/Выкл делать придётся ещё меньше:

#ifndef ASIO_OS_DEF
#  define ASIO_OS_DEF(x) x
#endif

#define ASIO_OS_DEF_SO_TIMESTAMP SO_TIMESTAMP
typedef asio::detail::socket_option::boolean<
        ASIO_OS_DEF(SOL_SOCKET), ASIO_OS_DEF(SO_TIMESTAMP)> so_timestamp;

#define ASIO_OS_DEF_IP_RECVTTL IP_RECVTTL
typedef asio::detail::socket_option::boolean<
        ASIO_OS_DEF(IPPROTO_IP), ASIO_OS_DEF(IP_RECVTTL)> ip_recvttl;

#define ASIO_OS_DEF_TCP_CORK TCP_CORK
typedef asio::detail::socket_option::boolean<
        ASIO_OS_DEF(IPPROTO_IP), ASIO_OS_DEF(TCP_CORK)> tcp_cork;

и использовать как-то так:

socket.set_option(tcp_cork(true));