Discussion:
[asio-users] Boost.Asio Multicast on a host with multiple interfaces - filtering/knowing on incoming interface
Ramon Casellas
2008-09-03 11:35:02 UTC
Permalink
All,

I have been suggested to submit the question here. Apologies if this
question is answered. I found a related thread in 2007 covering these issues
(including particular Linux Multicast implementation details), but I do not
think the thread answers this question (
http://osdir.com/ml/lib.boost.asio.user/2007-04/msg00086.html )

Short Form question:
How can I force a socket to receive multicast udp packets only from a given
network interface (ideally portably with Asio) or, given a socket that
received a multicast packet, know the incoming interface?

Long Form question:
Consider a network node A, B... with several network interfaces, each
interfaces attached to a link. At the remote end of the link, there is a
neighbor. An application running in that node, which uses boost asio,
implements a given network protocol. The purpose of the protocol is to
manage control_channels (IP data communication channels to simplify) between
neighbor nodes. The protocol specifies that a node starts sending multicast
UDP messages over the interface, and when a node gets a multicast packet,
starts sending packets to the unicast address "from" the received multicast.

Current design is as follows: a class control_channel models a protocol
control channel. There exists a control_channel instance for each active
network interface (eth, to simplify), and there is a socket member. The
socket is used to send and receive multicast frames *over that interface*. I
cannot figure out how to use Boost.Asio to receive multicast frames that
were received on that specific interface. Either I do not receive multicast
packets or I receive a multicast packet on every socket, regardless of the
interface it was physically received on.




Node A Node B
gre21 gre23
o-----------------------------o-----------
10.0.5.12 10.0.5.21


On Node B
===================
tshark -i any -f"udp"

2.206040 10.0.5.12 -> 224.0.0.1 LMP Config Message.


2008-Sep-03 15:17:04.490236
----------------------------------------------------------------------------
-----------
- void lmp::node::control_channel::parse_message(size_t)
CONTROL_CHANNEL: Received Message on gre23 - type: 1 rcvd bytes: 40
declared size: 40
MESSAGE: CONFIG Length: 40

2008-Sep-03 15:17:04.490407
----------------------------------------------------------------------------
-----------
- void lmp::node::control_channel::parse_message(size_t)
CONTROL_CHANNEL: Received Message on gre21 - type: 1 rcvd bytes: 40
declared size: 40
MESSAGE: CONFIG Length: 40



namespace aio = boost::asio;

/// Socket for the control_channel.
aio::ip::udp::socket socket_;

/// if_addr_str is the nework address of the interface (IFA_LOCAL e.g.
10.0.5.21)

/// port_ is the same for all sockets, fixed by the protocol.

Code:
----------------------------

aio::ip::address if_address = aio::ip::address::from_string
(if_address_str);
aio::ip::address multicast_address = aio::ip::address::from_string
("224.0.0.1");
aio::ip::address listen_address = multicast_address;

// I.- Bind the UDP socket to this interface - discards packets not sent to
the interface,
// *including* multicast packets.
// ip::udp::endpoint listen_endpoint(if_address, port_);
//
// II.- Binding the UDP socket to the any (0.0.0.0) address receives
multicast packets arriving on other interfaces
// and supposedly other multicast addresses / groups too
// ip::udp::endpoint listen_endpoint(anyaddr, port_);

// III.- Binding the UDP socket to the multicast address, works, but we
receive packets sent to other interfaces too
// ip::udp::endpoint listen_endpoint(listen_address, port_);

aio::ip::udp::endpoint listen_endpoint(listen_address, port_);

socket_.open (listen_endpoint.protocol());
socket_.set_option (aio::ip::udp::socket::reuse_address(true));

socket_.bind (listen_endpoint);

// disable loopback (no copies of our packets)
socket_.set_option(aio::ip::multicast::enable_loopback(false));

// set oif - the socket will use this interface as outgoing interface
socket_.set_option(aio::ip::multicast::outbound_interface(if_address.to_v4()
));

// set mcast group - join group - If the network interface (the second
constructor parameter) is not
// specified when joining a multicast group, the system may select an
appropriate interface based on
// the routing table, probably the default route.

// socket_.set_option(aio::ip::multicast::join_group(multicast_address));
socket_.set_option(aio::ip::multicast::join_group(multicast_address.to_v4(),
if_address.to_v4()));

---------------------------------------------
// RX is scheduled to the io_service with:

socket_.async_receive_from (
aio::buffer(buffer_), recv_endpoint_,
boost::bind (&control_channel::handle_read,
shared_from_this(),
aio::placeholders::error,

aio::placeholders::bytes_transferred));

---------------------------------------------
// Transmission works, also with outbound_interface option.

remote_endpoint_(aio::ip::address::from_string("224.0.0.1"), port_)
socket_.send_to (
boost::asio::buffer (message_stream.str().c_str(),
msg->message_length()), remote_endpoint_);

When a node A sends a mcast packet to node B, the mcast packet is received
by all sockets open by node B. I would like to receive it *only* on the
socket "associated to" interface that is connected to A, but other than
bind, I don't know how. It seems that in Linux, bind does simply makes the
kernel drop packets not directed to the interface address. Can I do this
with asio?

I considered having a single multicast socket, I am not sure how to get the
real incoming interface portably, so I can map the source address of the
received packet to the neighbor at the other side of the link/interface. The
target system is a debian GNU/Linux 4.0, with a 2.6.24 kernel. The non
portable way would be to use IP_PKTINFO, I guess, or IP_RECVIF in other BSD
systems?


Thanks in advance for your comments or suggestions.

----
Ramon Casellas
Ramon Casellas
2008-09-03 12:47:22 UTC
Permalink
Re,

Answering my own question, this seems to work and is slightly more portable
than IP_PKTINFO. I am not sure that SO_BINDTODEVICE is available in all
targeted systems in order to suggest the option to be included in a new
release :)

Apologies for the inconvenience this may have caused.

Let me thank Christoff for the excellent library that Boost.Asio is.

Ramon.



aio::ip::address if_address = aio::ip::address::from_string
(if_address_str);
aio::ip::address multicast_address = aio::ip::address::from_string
("224.0.0.1");

// bind address
aio::ip::address listen_address = multicast_address;
aio::ip::udp::endpoint listen_endpoint(listen_address, port_);

// open the socket
socket_.open (listen_endpoint.protocol());

// SO_BINDTODEVICE
if ((::setsockopt (socket_.native(), SOL_SOCKET, SO_BINDTODEVICE,
ifp_->name().c_str(), ifp_->name().size() + 1) == -1))
{
throw std::runtime_error("setsockopt() SO_BINDTODEVICE");

}

// bind
socket_.set_option (aio::ip::udp::socket::reuse_address(true));

socket_.bind (listen_endpoint);

// disable loopback
socket_.set_option(aio::ip::multicast::enable_loopback(false));

// set oif - the socket will use this interface as outgoing interface
socket_.set_option(aio::ip::multicast::outbound_interface(if_address.to_v4()
));

// set mcast group - join group -
socket_.set_option(aio::ip::multicast::join_group(multicast_address.to_v4(),
if_address.to_v4()));

Loading...