In the previous post we’ve reviewed the multicast addressing technology and examined some of its pitfalls.
In this post we’ll dive through the implementation of a simple application that allows sending and receiving multicast data over the LAN, while enabling the selection of the network interfaces (IPEndPoint) through which the multicast traffic will be sent and received.
As illustrated in the previous post, setting the network interface is crucial when the host is connected to two or more networks or have two or more network cards installed and enabled.
The application source code can be downloaded from here.
Configuration
The MulticastConfiguration class encapsulates the configuration entered by the user. It exposes two factory methods that allow instantiating the class for single network hosts and for multi network hosts. The 1st factory method requires only the multicast address/port of the sender and the receiver, while the 2nd factory method also requires the unicast address of the network card of the sender and the receiver.
- class MulticastConfiguration
- {
- readonly IPAddress m_SendMulticastAddress;
- readonly int m_SendPort;
- readonly int m_ReceivePort;
- readonly IPAddress m_ReceiveMulticastAddress;
- // Multihomed Support
- readonly IPAddress m_SendNetworkCardAddress = IPAddress.Any;
- readonly int m_SendNetworkCardPort;
- readonly IPAddress m_ReceiveNetworkCardAddress = IPAddress.Any;
- public MulticastConfiguration(
- IPAddress sendMulticastAddress,
- int sendPort,
- IPAddress receiveMulticastAddress,
- int receivePort)
- {
- m_SendMulticastAddress = sendMulticastAddress;
- m_SendPort = sendPort;
- m_ReceiveMulticastAddress = receiveMulticastAddress;
- m_ReceivePort = receivePort;
- }
- public MulticastConfiguration(
- IPAddress sendMulticastAddress,
- int sendPort,
- IPAddress receiveMulticastAddress,
- int receivePort,
- IPAddress sendExplicitSourceAddressm,
- int sendExplicitSourcePort,
- IPAddress receiveExplicitSourceAddress)
- : this(
- sendMulticastAddress,
- sendPort,
- receiveMulticastAddress,
- receivePort)
- {
- m_SendNetworkCardAddress = sendExplicitSourceAddressm;
- m_SendNetworkCardPort = sendExplicitSourcePort;
- m_ReceiveNetworkCardAddress = receiveExplicitSourceAddress;
- }
- public static MulticastConfiguration CreateForSingleInterfaceNetwork(
- IPAddress sendMulticastAddress,
- int sendPort,
- IPAddress receiveMulticastAddress,
- int receivePort)
- {
- return new MulticastConfiguration(
- sendMulticastAddress,
- sendPort,
- receiveMulticastAddress,
- receivePort);
- }
- public static MulticastConfiguration CreateForMultiHomedNetwork(
- IPAddress sendMulticastAddress,
- int sendPort,
- IPAddress receiveMulticastAddress,
- int receivePort,
- IPAddress sendExplicitSourceAddressm,
- int sendExplicitSourcePort,
- IPAddress receiveExplicitSourceAddress)
- {
- return new MulticastConfiguration(
- sendMulticastAddress,
- sendPort,
- receiveMulticastAddress,
- receivePort,
- sendExplicitSourceAddressm,
- sendExplicitSourcePort,
- receiveExplicitSourceAddress);
- }
- public IPAddress SendMulticastAddress
- {
- get { return m_SendMulticastAddress; }
- }
- public int SendPort
- {
- get { return m_SendPort; }
- }
- public IPAddress ReceiveMulticastAddress
- {
- get { return m_ReceiveMulticastAddress; }
- }
- public int ReceivePort
- {
- get { return m_ReceivePort; }
- }
- public IPAddress SendNetworkCardAddress
- {
- get { return m_SendNetworkCardAddress; }
- }
- public int SendNetworkCardPort
- {
- get { return m_SendNetworkCardPort; }
- }
- public IPAddress ReceiveNetworkCardAddress
- {
- get { return m_ReceiveNetworkCardAddress; }
- }
- }
Transport
The TransportAgent class is in charge of creating the sockets that receive and send the data, joining to the multicast group (i.e. sending IGMP package to the router) and binding both sockets to the appropriate network interface.
- class TransportAgent
- {
- static readonly IPEndPoint AnyAddress = new IPEndPoint(IPAddress.Any, 0);
- private readonly byte[] m_objectStateBuffer = new byte[8192];
- private readonly MulticastConfiguration m_dataEntity;
- private readonly Socket m_sendSocket;
- private readonly Socket m_receiveSocket;
- private bool m_opened;
- public TransportAgent(
- MulticastConfiguration entity,
- EventHandler<BufferReceivedEventArgs> messageReceived)
- : this(entity)
- {
- MessageReceived += messageReceived;
- }
- public TransportAgent(MulticastConfiguration entity)
- {
- m_dataEntity = entity;
- m_sendSocket = CreateUdpSocket();
- EndPoint sendEndPoint = new IPEndPoint(
- m_dataEntity.SendNetworkCardAddress,
- m_dataEntity.SendNetworkCardPort);
- // define the address of the network card from
- // which the multicast data will be sent.
- m_sendSocket.Bind(sendEndPoint);
- m_receiveSocket = CreateUdpSocket();
- EndPoint receiveEndPoint = new IPEndPoint(
- m_dataEntity.ReceiveNetworkCardAddress,
- m_dataEntity.ReceivePort);
- // define the address of the network card from
- // which the multicast data will be received.
- m_receiveSocket.Bind(receiveEndPoint);
- // Send the IGMP message to the router, ask to
- // goin to the multicast group
- JoinMulticast(m_receiveSocket);
- BeginReceive();
- m_opened = true;
- }
- public event EventHandler<BufferReceivedEventArgs> MessageReceived = delegate { };
- public void Close()
- {
- if (!m_opened)
- {
- return;
- }
- DropMulticast(m_receiveSocket);
- m_receiveSocket.Close();
- m_sendSocket.Close();
- m_opened = false;
- }
- public void Send(byte[] message)
- {
- StateObject stateObject = new StateObject(
- m_sendSocket, message);
- IPEndPoint endPoint = new IPEndPoint(
- m_dataEntity.SendMulticastAddress,
- m_dataEntity.SendPort);
- int offset = 0;
- m_sendSocket.BeginSendTo(
- message,
- offset,
- message.Length,
- SocketFlags.None,
- endPoint,
- OnSend,
- stateObject);
- }
- private void OnSend(IAsyncResult ar)
- {
- int bytesCount = m_sendSocket.EndSendTo(ar);
- Trace.WriteLine(
- string.Format("Messages Sent, Bytes: {0}",
- bytesCount));
- }
- private void BeginReceive()
- {
- Socket receiveSocket = m_receiveSocket;
- if (receiveSocket == null) return;
- EndPoint remoteEP = AnyAddress;
- StateObject stateObject = new StateObject(
- receiveSocket, m_objectStateBuffer);
- int offset = 0;
- receiveSocket.BeginReceiveFrom(
- stateObject.Buffer,
- offset,
- stateObject.Buffer.Length,
- SocketFlags.None,
- ref remoteEP,
- OnReceive,
- stateObject);
- }
- private void OnReceive(IAsyncResult result)
- {
- EndPoint remoteEP = AnyAddress;
- try
- {
- int bufferSize = m_receiveSocket.EndReceiveFrom(
- result, ref remoteEP);
- StateObject stateObject =
- (StateObject)result.AsyncState;
- BufferReceivedEventArgs e = new BufferReceivedEventArgs(
- stateObject.Buffer, bufferSize);
- MessageReceived(this, e);
- BeginReceive();
- }
- catch
- {
- Trace.WriteLine("Disconnection");
- }
- }
- private static Socket CreateUdpSocket()
- {
- Socket socket = new Socket(
- AddressFamily.InterNetwork,
- SocketType.Dgram,
- ProtocolType.Udp);
- return socket;
- }
- private void JoinMulticast(Socket socket)
- {
- MulticastOption multicastOption = GetMulticastOption();
- socket.SetSocketOption(
- SocketOptionLevel.IP,
- SocketOptionName.MulticastTimeToLive,
- 64);
- socket.SetSocketOption(
- SocketOptionLevel.IP,
- SocketOptionName.AddMembership,
- multicastOption);
- }
- private void DropMulticast(Socket socket)
- {
- MulticastOption multicastOption = GetMulticastOption();
- socket.SetSocketOption(
- SocketOptionLevel.IP,
- SocketOptionName.MulticastTimeToLive,
- 64);
- socket.SetSocketOption(
- SocketOptionLevel.IP,
- SocketOptionName.DropMembership,
- multicastOption);
- }
- private MulticastOption GetMulticastOption()
- {
- IPAddress ip = m_dataEntity.ReceiveMulticastAddress;
- // MulticastOption can be constructed with the
- // overload that allows setting the address of
- // the network card from which the IGMP package
- // will be sent to the router.
- MulticastOption mo = new MulticastOption(ip);
- return mo;
- }
- }
Host
All the host has to do is to read the configuration from the UI, instantiate the configuration class, and instantiate the transport class - injecting it with the configuration instance and with delegate to OnMessageReceived callback that will be called when ever data arrive.
- MulticastConfiguration multicastConfiguration = ReadConfiguration();
- m_TransportAgent = new TransportAgent(
- multicastConfiguration, OnMessageReceived);
Sending Multicast Data
- ASCIIEncoding asciiEncoding = new ASCIIEncoding();
- string message = m_textBoxMessage.Text;
- byte[] bytes = asciiEncoding.GetBytes(message);
- m_TransportAgent.Send(bytes);
Receiving Multicast Data
- private void OnMessageReceived(object sender, BufferReceivedEventArgs e)
- {
- string message = Encoding.ASCII.GetString(e.Buffer);
- Print(message);
- }
Debugging
My choice for network protocol analyzer is Wireshark which is the best analyzer available today, it’s released under the GNU General Public License (GPL) so it can be used freely.
To get started, download the source code and run the multicast tester application, download Wireshark, run it and start a capture session,
IGMP
In order to get a feeling about the way that multicast addressing works – let’s review the IGMP message that is sent to the router when the receiver socket joins to the multicast group (see TransportAgent line 166).
Enter ‘igmp’ in Wireshark filter.
In the multicast tester application, press ‘Connect’. The ‘network card interface’ in the receiver configuration is set to my local machine upper network card address.
As a result, IGMP package was sent to the network, requesting it to add network interface with address 192.168.2.100 (which is my upper network card address) to the multicast group 224.0.0.12. Consequently, the network will deliver any multicast data sent to group 224.0.0.12 to address 192.168.2.100.
Now, In the multicast tester application, press ‘disconnect’.
As a result, IGMP package was sent to the network, requesting it to remove the network interface from the group.
Sending Multicast Data – Single Network
Enter ‘udp’ in Wireshark filter.
In the multicast tester application, leave the ‘Network Card Address’ and ‘Network Card Port’ in the sender configuration blank, press ‘Connect’, enter some string to the message text box and press ‘Send’
As a result, the string that was specified (in this case ‘aviad’) is sent form the network card 192.168.2.100, port 3704. Since we didn’t specify the network card address and port – the operation system took the liberty to select network card and port.
Sending Multicast Data – Multihomed Network
In the multicast tester application, set the sender ‘Network Card Address’ to one of your network cards address and set the ‘Network Card Port’ to 1234, press ‘Connect’, enter some string to the message text box and press ‘Send’.
As a result, the string is sent form the network card 192.168.2.100, port 1234. Since we did specify the network card address and port – the network card and the port from which the string was sent were NOT the selection of the operation system. Even though in the previous case the operation system selected the same network card that we specified, in cases where there are more than 1 enabled and active network cards in the machine - you must specify the appropriate network address or your multicast may be delivered though the wrong network.