LINUX.ORG.RU

Узнать Multicast группу из входящей дейтаграммы

 , , ,


0

1

И так, у меня есть проблема. Сервер вещает данные в UDP Multicast группу. Клиент подключается к нем и получает данные. Изменяем адрес группы (меняем IP, но не меняем порт) и клиент все равно получает данные.

Написал простенький receiver и пытаюсь в нем узнать из полученной дейтаграмма группу в которую он была отправлена. Что бы воочию увидеть что у меня сервер что то не правильно шлет или еще что то.

Не получается. Создаю сокет и настраиваю на UDP Multicast.

sockfd = socket(AF_INET, SOCK_DGRAM, 0)
устанавливаю ему опцию
setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &optval, sizeof(optval)
заполняю структуру msghdr
char msg_buf[300];
	
    struct iovec iov;			// структура сообщения
    iov.iov_base = msg_buf;			// указываем buf в качестве буфера сообщения для iov
    iov.iov_len = sizeof(msg_buf);	// указываем размер буфера
	
	struct cmsghdr *cmsgptr;
	struct sockaddr_in cliaddr;
	msghdr msg;
	msg.msg_name = &cliaddr;					// задаем имя - структуру локального адреса
	msg.msg_namelen = sizeof(cliaddr);
	msg.msg_iov = &iov;						// указываем вектор данных сообщения
    msg.msg_iovlen = 1;
получаю Дейтаграмму
recvmsg(receiver, &msg, 0)
Пытаюсь извлечь нужные данные
			for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr))
			{
				std::cout << "Ohhh...\n";			
				struct in_pktinfo *pi = (struct  in_pktinfo* )CMSG_DATA(cmsgptr);
				// at this point, peeraddr is the source sockaddr
				// pi->ipi_spec_dst is the destination in_addr
				// pi->ipi_addr is the receiving interface in_addr
				std::cout << inet_ntoa(pi->ipi_spec_dst) << std::endl;
			}
Но CMSG_FIRSTHDR(&msg) возвращает NULL. В чем может быть беда?

Подумал, что может быть при отправке не указывается нужные данные. Но Wariesshark под виндой для моих дейтаграм корректно указывает Destination.



Последнее исправление: Cupper (всего исправлений: 1)
Ответ на: комментарий от Cupper

И так, у меня есть проблема

И вот как она воспроизводится

запускаем receiver 239.192.100.100 9998

запускаем его еще раз receiver 239.192.100.200 9998

запускаем sender 239.192.100.100 9998

оба ресивера получат дынные.

Вот вам код sender

#include <iostream>
#include <sstream>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

namespace
{
	void err(const char* err)
	{
		std::cerr << "Err: " << err << std::endl;
		std::cerr << strerror(errno) << std::endl;
	}
}

int create_sender(struct sockaddr_in* to, const char* ip_addr, const int port)
{
	int sockfd;
	
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		err("create socket");
		return -1;
	}
	
	bzero(to, sizeof(*to));
	to->sin_family = AF_INET;
	to->sin_port = htons(port);
	if(inet_aton(ip_addr, &(to->sin_addr)) == 0)
	{
		err("do inet_aton");
		close(sockfd);
		return -1;
	}

	return sockfd;
}

int main(int argc, char* argv[])
{	
	struct sockaddr_in to;
	int sender;

	if (argc != 3)
    {
      std::cerr << "Usage: sender <multicast_address>\n";
      std::cerr << "  For IPv4, try:\n";
      std::cerr << "    sender 239.255.0.1 9999\n";
      std::cerr << "  For IPv6, try:\n";
      std::cerr << "    sender ff31::8000:1234 9999\n";
      return 1;
    }
	
	short port = atoi(argv[2]);
	std::cout << "Send to: " << argv[1] << ":" << port << std::endl;
	
	std::cout << "Create sender" << std::endl;
	if((sender = create_sender(&to, argv[1], port)) < 0)
		return 1;
	
	std::cout << "Sender was created\n";
	
	std::stringstream sstr;
	sstr << "The UDP datagram was sent from " << argv[1] << ":" << port;
	const char* msg_to_send = sstr.str().c_str();
	size_t msg_to_send_size = sstr.str().length();
	
    int bytesrecv;
	errno = 0;
	for(int i = 0; i < 5; i++)
	{
		std::cout << "Send to: " << argv[1] << ":" << port << std::endl;
		sendto(sender, msg_to_send, msg_to_send_size, 0, (sockaddr *)&to, sizeof(to));
	}
	close(sender);
	
}
receiver
#include <iostream>
#include <sstream>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

namespace
{
	void err(const char* err)
	{
		std::cerr << "Err: " << err << std::endl;
		std::cerr << strerror(errno) << std::endl;
	}
}

int create_receiver(const char* ip_addr, const int port)
{
	int sockfd;
	struct sockaddr_in real_addr;
	struct sockaddr_in* addr = &real_addr;
	
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		err("create socket");
		return -1;
	}
	
	const int optval = 1;
	if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) != 0)
	{
		err("do setsockopt SO_REUSEADDR");
		close(sockfd);
		return -1;
	}
	
	struct ip_mreq mreq;
	if(inet_aton(ip_addr, &(mreq.imr_multiaddr)) == 0)
	{
		err("do inet_aton");
		close(sockfd);
		return -1;
	}
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);
	
	if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0)
	{
		err("do setsockopt IP_ADD_MEMBERSHIP");
		close(sockfd);
		return -1;
	}
	
	if(setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &optval, sizeof(optval)) != 0)
	{
		err("do setsockopt IP_PKTINFO");
		close(sockfd);
		return -1;
	}
	
	bzero(addr, sizeof(*addr));
	addr->sin_family = AF_INET;
	addr->sin_port = htons(port);
	addr->sin_addr.s_addr = htonl(INADDR_ANY);
	
	if(bind(sockfd, (sockaddr *)addr, sizeof(*addr)) < 0)
	{
		err("cannot bind");
		close(sockfd);
		return -1;
	}

	return sockfd;
}

int main(int argc, char* argv[])
{	
	int receiver;

	if (argc != 3)
    {
      std::cerr << "Usage: sender <multicast_address>\n";
      std::cerr << "  For IPv4, try:\n";
      std::cerr << "    sender 239.255.0.1 9999\n";
      std::cerr << "  For IPv6, try:\n";
      std::cerr << "    sender ff31::8000:1234 9999\n";
      return 1;
    }
	
	short port = atoi(argv[2]);
	std::cout << "Send to: " << argv[1] << ":" << port << std::endl;
	
	std::cout << "Create receiver" << std::endl;
	if((receiver = create_receiver(argv[1], port)) < 0)
		return 1;
	
	std::cout << "Receiver was created\n";
	
	char msg_buf[300];
	
    int bytesrecv;
	errno = 0;
	for(int i = 0; i < 5; i++)
	{
		std::cout << "Try to receive from: " << argv[1] << ":" << port << std::endl;
		bytesrecv = recv(receiver, msg_buf, sizeof(msg_buf), 0);
		std::cout << std::string(msg_buf, bytesrecv) << std::endl;
	}
	
	close(receiver);
}

Я шоке.

Cupper
() автор топика
Ответ на: комментарий от Cupper

это ж бубль-гум!
а как ядро определяет, какому приложению отдавать пришедшие дейтаграммы?

anonymous
()
Ответ на: комментарий от anonymous

Два клиента подписываются на разные группы, но на один порт.

Сендер отправляет в определенную группу на тот же порт.

Соответственно ядро должно адресовать дейтаграмму только тому кто подписался именно на ту группу куда отправил сендер. На практике получается что ядро отдает всем кто подписался (сделал SO_REUSEADDR) на тот порт. Без учета самой мультикаст группы.

Это не бубль-гум.

Cupper
() автор топика
Ответ на: комментарий от Cupper

подписка на группу приводит только к тому, что отправляется IGMP запрос на добавление адреса в группу на роутер, от которого будет идти поток;
а какому приложению отдавать пакет, пришедший так или иначе на хост, ядро решает по биндингу, поэтому в ресивере надо биндить не только порт, но и адрес группы, а не INADDR_ANY

anonymous
()
Ответ на: комментарий от anonymous

поэтому в ресивере надо биндить не только порт, но и адрес группы, а не INADDR_ANY

биндить на адрес группы ? Хм... может быть. Хотя, я был уверен что бинднг больше отвечает за то от кого мы будем получать пакетики. Т.е. по идеи мы могли бы забиндиться на адрес отправителя. Но я его по сути не знаю. Поэтому и используется INADDR_ANY.

Полистаю завтра еще раз книжку. Ну и на практике тоже проверю.

Cupper
() автор топика
Ответ на: комментарий от Cupper

Хотя, я был уверен что бинднг больше отвечает за то от кого мы будем получать пакетики

за локализацию удаленной точки коммуникации отвечают connect()/sendto()/recvfrom(), а за локализацию локальной, пардон за тавтологию, точки - bind()

anonymous
()
Ответ на: комментарий от anonymous

пардон, ступил с bind.

Но мне тогда совсем не понятно зачем процесс подключения к группе внесено в опции сокета ?

setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)

я могу долго фантазировать на эту тему, но лучше услышать мнения того кто знает зачем при подключении в группе нужен сам сокет sockfd.

И еще, тогда совсем не маловажным является перед закрытием сокета (апликуха) отключаться от группы. Иначе - сокет ядро закрыло, я роутер продолжит слать пакеты хотя они нам и не нужны более.

Cupper
() автор топика
Ответ на: комментарий от Cupper

зачем при подключении в группе нужен сам сокет sockfd

И еще, тогда совсем не маловажным является перед закрытием сокета (апликуха) отключаться от группы. Иначе - сокет ядро закрыло, я роутер продолжит слать пакеты хотя они нам и не нужны более.

какой ты молодец! сам спросил, сам ответил - ну, почти; осталось микроусилие совершить и анигилировать два сомнения для получения проСВЕТительского фотона :)

anonymous
()
Ответ на: комментарий от anonymous

ядро на столько умное что запоминает все сокеты подключившиеся к группе, и при отключении от группы, пока не отключиться последний из подключившихся не пошлет запрос на роутер для отключения ?

Но это мало похоже на правду так как сложно. И проще уж вести счетчик подключений к группе. И делать как в смарт поинтерах.

Не хватает моих знаний что бы додумать :(

Cupper
() автор топика
Ответ на: комментарий от Cupper

И проще уж вести счетчик подключений к группе

гы-гы
допустим, ядро обслуживает сто сокетов и текущий счетчик подключений к некоторой группе равен 3; вдруг какой-то сокет закрывается;
как ядро должно решить нужно уменьшать счетчик или нет?

anonymous
()
Ответ на: комментарий от anonymous

так получается мы можем сами не отключатся от группы, а это сделает ядро ? Тогда понятно.

А теперь, я вас попрошу проявить всю вашу телепатическую силу и глянув этот пример

http://www.boost.org/doc/libs/1_41_0/doc/html/boost_asio/example/multicast/re...

подсказать, нафига они в примере биндятся к 0.0.0.0

receiver 0.0.0.0 239.255.0.1

Cupper
() автор топика
Ответ на: комментарий от Cupper

мы можем сами не отключатся от группы, а это сделает ядро ?

угу, ядро много чего делает; например, можно и сокет не закрывать :)

подсказать, нафига они в примере биндятся к 0.0.0.0

ну, этож просто бубль-гум пример :)
подозреваю, что

socket_.bind(listen_endpoint);
нужен для определения ip-адреса (в примере 0.0.0.0, т.е. ядро может само выбрить, но для программиста обозначен путь на случай, если ему важно какой адрес хоста из многих будет запоминать ближайший роутер), который нужно добавить в группу в последующем (заметь, здесь бинд вызывается перед присоединением к группе, а у тебя после)
socket_.set_option(
        boost::asio::ip::multicast::join_group(multicast_address));
не исключаю, что внутри join_group() делается еще один бинд уже на групповой адрес, хотя с бустом не знаком - ручаться не могу;
впрочем, в простом случае (один ресивер) для примера достаточно привязки только к номеру порта

anonymous
()
Ответ на: комментарий от anonymous

кстати, может быть вызов

setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))
все-таки привязывает сокет к групповому адресу (в ядре), но ты после этого насильно его биндишь опять к INADDR_ANY, и поэтому твой код работает не как ожидаешь; попробуй переставить

ну и да, отпишись о результатах - что покажет практика, ибо я только теоретически рассуждаю; мне тоже интересно, как на самом деле :)

anonymous
()
Ответ на: комментарий от anonymous

не исключаю, что внутри join_group() делается еще один бинд уже на групповой адрес, хотя с бустом не знаком - ручаться не могу;

Не делает. Исключительно только setsockopt.

все-таки привязывает сокет к групповому адресу (в ядре), но ты после этого насильно его биндишь опять к INADDR_ANY, и поэтому твой код работает не как ожидаешь; попробуй переставить

поменял, не сработало.

Напрягает меня эта околосеца с bind и IP_ADD_MEMBERSHIP. Как то оно через жопу выглядит.

По идеи должно быть большими буквами написано, что тогда когда вы делаете IP_ADD_MEMBERSHIP вы должны биндится к тому же адресу. Иначе вы будете получать данные то только с ваще Multicast группы.

И кстати, на винде у меня такой проблемы нету (вроде бы). Это вот такая особенность линукса.

Cupper
() автор топика
Ответ на: комментарий от Cupper

И кстати я пробовал биндиться именно на адрес группы. И да, это работает именно так как надо.

Cupper
() автор топика
Ответ на: комментарий от Cupper

Вот здесь впринцепи не явно но указано как правильно работать с Multicast

http://www.linuxfocus.org/Russian/January2001/article144.shtml Из кода клиента видно что биндится и джоиниться к одному адресу

bzero(&srv, sizeof(srv));
  srv.sin_family = AF_INET;
  srv.sin_port = htons(PUERTO);
  if (inet_aton(GRUPO, &srv.sin_addr) < 0) {
    perror("inet_aton");
    return 1;
  }

  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    perror("socket");
    return 1;
  }

  if (bind(s, (struct sockaddr *)&srv, sizeof(srv)) < 0) {
    perror("bind");
    return 1;
  }

  if (inet_aton(GRUPO, &mreq.imr_multiaddr) < 0) {
    perror("inet_aton");
    return 1;
  }
  mreq.imr_interface.s_addr = htonl(INADDR_ANY);

  if (setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))
      < 0) {
    perror("setsockopt");
    return 1;
  }

Cupper
() автор топика
Ответ на: комментарий от Cupper

Unix Network Programming 3 издание. глава 21.6 страница 599

---------------------------------------------------------------------------------------------------

Чтобы получить дейтаграмму многоадресной передачи, процесс должен присоединиться к группе, а также связать при помощи функции bind сокет UDP с номером порта, который будет использоваться как номер порта получателя для дейтаграмм, отсылаемых данной группе. ...

Связывая порт, приложение указывает UDP, что требуется получать отправляемые на этот порт дейтаграммы. Некоторые приложения в дополнения к связыванию порта также связывают при помощи функции bind адрес многоадресной передачи с сокетом. Это предотвращает доставку сокету любых других дейтаграмм, которые могли быть получены для этого порта.

---------------------------------------------------------------------------------------------------

Эх, не постичь мне философии линукса... не постичь :(

Cupper
() автор топика
Ответ на: комментарий от Cupper

И финальный аккорд под дых. На винде нельзя сделать bind к мультикаст адресу.

Берем чуть чуть модифицированный пример с стайта boost

//
// receiver.cpp
// ~~~~~~~~~~~~
//
// Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include "boost/bind.hpp"

const short multicast_port = 9999;

class receiver
{
public:
  receiver(boost::asio::io_service& io_service,
      const boost::asio::ip::address& listen_address,
      const boost::asio::ip::address& multicast_address)
    : socket_(io_service)
  {
    // Create the socket so that multiple may be bound to the same address.
    boost::asio::ip::udp::endpoint listen_endpoint(
        listen_address, multicast_port);
    socket_.open(listen_endpoint.protocol());
    socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
    socket_.bind(listen_endpoint);

    // Join the multicast group.
    socket_.set_option(
        boost::asio::ip::multicast::join_group(multicast_address));

    socket_.async_receive_from(
        boost::asio::buffer(data_, max_length), sender_endpoint_,
        boost::bind(&receiver::handle_receive_from, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_receive_from(const boost::system::error_code& error,
      size_t bytes_recvd)
  {
    if (!error)
    {
      std::cout.write(data_, bytes_recvd);
      std::cout << std::endl;

      socket_.async_receive_from(
          boost::asio::buffer(data_, max_length), sender_endpoint_,
          boost::bind(&receiver::handle_receive_from, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
  }

private:
  boost::asio::ip::udp::socket socket_;
  boost::asio::ip::udp::endpoint sender_endpoint_;
  enum { max_length = 1024 };
  char data_[max_length];
};

int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: receiver <listen_address> <multicast_address>\n";
      std::cerr << "  For IPv4, try:\n";
      std::cerr << "    receiver 239.255.0.1\n";
      std::cerr << "  For IPv6, try:\n";
      std::cerr << "    receiver ff31::8000:1234\n";
      return 1;
    }

    boost::asio::io_service io_service;
    receiver r(io_service,
        boost::asio::ip::address::from_string(argv[1]),
        boost::asio::ip::address::from_string(argv[1]));
    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

Отличие в том что мы передаем только Multicast адрес и на него делаем Bind и multicast::join_group. На винде это вылетает с ошибокй

Exception: The requested address is not valid in its context
На линуксе это работает. Выводы делаем сами.

Cupper
() автор топика
Ответ на: комментарий от Cupper

Эх, не постичь мне философии линукса... не постичь :(

unix-way - это предоставить максимально гибкий и обширный интерфейс к тысяче разных отдельных модулей/сущностей/абстракций/и.т.д, умеющих выполнять какую-то одну задачу, но очень хорошо и гибко; потом программист/пользователь берет нужные кубики и строит из них то что ему требуется, по-разному их комбинируя, и получая 100500 всевозможных сочетаний, конечно, не все из них соответствуют какой-то пользе в реальном мире, но то что соответствует составляет очень широкую область применения, некоторые юзкейсы даже нельзя было представить при разработке архитектуры 50 лет назад, но тем не менее это становится возможным благодаря концепции мелких низкоуровневых деталек; да, это требует хорошего понимания как технических аспектов поставленной задачи так и свойств конструктора; да, не всегда есть хорошая документация по конструктору, но есть исходный код

в винде же все заточено на удобство выполнения трех-пяти стандартных действий, которые виндо-программисты мышой легко вставляют в свои программы и получают тонны одинакового стандартного говнософта; если же требуется что-то неординарное, то сделать это можно только через жуткие костыли либо вообще никак

На линуксе это работает. Выводы делаем сами.

угу, мы сделали :)

anonymous
()
Ответ на: комментарий от anonymous

в винде же .... сделать это можно только через жуткие костыли либо вообще никак

И так, мы выяснили что под линукс мы должны биндиться к той же мультикаст группе к которой подключаемся. Но что если нам надо на один сокет получать дынные с нескольких мультикст группы (но не со всех) ? Нужно сделать следующее:

1. биндиться на 0.0.0.0

2. далее либо фильтровать все полученные дейтаграммы в ручную определяя их destination адрес

3. либо использовать IGMPv3 и для каждой группы специфировать хост отправителя.

Но при этом если один хост вещает в несколько групп но на один порт, нас это не спасет. Так если мы подключимся к некоторым группам, а какой то другой софт еще каким то другим с того же хоста то оба будут получать дейтаграммы со всех подключенных групп на этом порту.

Cupper
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.