Friday, July 10, 2009

Multicast Application with Multihomed Support (C# .NET Sample)

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.

image 

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.

Code Snippet
  1.     class MulticastConfiguration
  2.     {
  3.         readonly IPAddress m_SendMulticastAddress;
  4.         readonly int m_SendPort;
  5.         readonly int m_ReceivePort;
  6.         readonly IPAddress m_ReceiveMulticastAddress;
  7.         // Multihomed Support
  8.         readonly IPAddress m_SendNetworkCardAddress = IPAddress.Any;
  9.         readonly int m_SendNetworkCardPort;
  10.         readonly IPAddress m_ReceiveNetworkCardAddress = IPAddress.Any;
  11.         public MulticastConfiguration(
  12.             IPAddress sendMulticastAddress,
  13.             int sendPort,
  14.             IPAddress receiveMulticastAddress,
  15.             int receivePort)
  16.         {
  17.             m_SendMulticastAddress = sendMulticastAddress;
  18.             m_SendPort = sendPort;
  19.             m_ReceiveMulticastAddress = receiveMulticastAddress;
  20.             m_ReceivePort = receivePort;
  21.         }
  22.         public MulticastConfiguration(
  23.             IPAddress sendMulticastAddress,
  24.             int sendPort,
  25.             IPAddress receiveMulticastAddress,
  26.             int receivePort,
  27.             IPAddress sendExplicitSourceAddressm,
  28.             int sendExplicitSourcePort,
  29.             IPAddress receiveExplicitSourceAddress)
  30.             : this(
  31.             sendMulticastAddress,
  32.             sendPort,
  33.             receiveMulticastAddress,
  34.             receivePort)
  35.         {
  36.             m_SendNetworkCardAddress = sendExplicitSourceAddressm;
  37.             m_SendNetworkCardPort = sendExplicitSourcePort;
  38.             m_ReceiveNetworkCardAddress = receiveExplicitSourceAddress;
  39.         }
  40.         public static MulticastConfiguration CreateForSingleInterfaceNetwork(
  41.             IPAddress sendMulticastAddress,
  42.             int sendPort,
  43.             IPAddress receiveMulticastAddress,
  44.             int receivePort)
  45.         {
  46.             return new MulticastConfiguration(
  47.                 sendMulticastAddress,
  48.                 sendPort,
  49.                 receiveMulticastAddress,
  50.                 receivePort);
  51.         }
  52.         public static MulticastConfiguration CreateForMultiHomedNetwork(
  53.             IPAddress sendMulticastAddress,
  54.             int sendPort,
  55.             IPAddress receiveMulticastAddress,
  56.             int receivePort,
  57.             IPAddress sendExplicitSourceAddressm,
  58.             int sendExplicitSourcePort,
  59.             IPAddress receiveExplicitSourceAddress)
  60.         {
  61.             return new MulticastConfiguration(
  62.                 sendMulticastAddress,
  63.                 sendPort,
  64.                 receiveMulticastAddress,
  65.                 receivePort,
  66.                 sendExplicitSourceAddressm,
  67.                 sendExplicitSourcePort,
  68.                 receiveExplicitSourceAddress);
  69.         }
  70.         public IPAddress SendMulticastAddress
  71.         {
  72.             get { return m_SendMulticastAddress; }
  73.         }
  74.         public int SendPort
  75.         {
  76.             get { return m_SendPort; }
  77.         }
  78.         public IPAddress ReceiveMulticastAddress
  79.         {
  80.             get { return m_ReceiveMulticastAddress; }
  81.         }
  82.         public int ReceivePort
  83.         {
  84.             get { return m_ReceivePort; }
  85.         }
  86.         public IPAddress SendNetworkCardAddress
  87.         {
  88.             get { return m_SendNetworkCardAddress; }
  89.         }
  90.         public int SendNetworkCardPort
  91.         {
  92.             get { return m_SendNetworkCardPort; }
  93.         }
  94.         public IPAddress ReceiveNetworkCardAddress
  95.         {
  96.             get { return m_ReceiveNetworkCardAddress; }
  97.         }
  98.     }

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.

Code Snippet
  1.     class TransportAgent
  2.     {
  3.         static readonly IPEndPoint AnyAddress = new IPEndPoint(IPAddress.Any, 0);
  4.         private readonly byte[] m_objectStateBuffer = new byte[8192];
  5.         private readonly MulticastConfiguration m_dataEntity;
  6.         private readonly Socket m_sendSocket;
  7.         private readonly Socket m_receiveSocket;
  8.         private  bool m_opened;
  9.         public TransportAgent(
  10.             MulticastConfiguration entity,
  11.             EventHandler<BufferReceivedEventArgs> messageReceived)
  12.             : this(entity)
  13.         {
  14.             MessageReceived += messageReceived;
  15.         }
  16.         public TransportAgent(MulticastConfiguration entity)
  17.         {
  18.             m_dataEntity = entity;
  19.            
  20.             m_sendSocket = CreateUdpSocket();
  21.             EndPoint sendEndPoint = new IPEndPoint(
  22.                 m_dataEntity.SendNetworkCardAddress,
  23.                 m_dataEntity.SendNetworkCardPort);
  24.            
  25.             // define the address of the network card from
  26.             // which the multicast data will be sent. 
  27.             m_sendSocket.Bind(sendEndPoint);
  28.             m_receiveSocket = CreateUdpSocket();
  29.            
  30.             EndPoint receiveEndPoint = new IPEndPoint(
  31.                 m_dataEntity.ReceiveNetworkCardAddress,
  32.                 m_dataEntity.ReceivePort);
  33.             // define the address of the network card from
  34.             // which the multicast data will be received. 
  35.             m_receiveSocket.Bind(receiveEndPoint);
  36.            
  37.             // Send the IGMP message to the router, ask to
  38.             // goin to the multicast group
  39.             JoinMulticast(m_receiveSocket);
  40.             BeginReceive();
  41.             m_opened = true;
  42.         }
  43.         public event EventHandler<BufferReceivedEventArgs> MessageReceived = delegate { };
  44.         public void Close()
  45.         {
  46.             if (!m_opened)
  47.             {
  48.                 return;
  49.             }
  50.             DropMulticast(m_receiveSocket);
  51.             m_receiveSocket.Close();
  52.             m_sendSocket.Close();
  53.             m_opened = false;
  54.         }
  55.         public void Send(byte[] message)
  56.         {
  57.             StateObject stateObject = new StateObject(
  58.                 m_sendSocket, message);
  59.             IPEndPoint endPoint = new IPEndPoint(
  60.                 m_dataEntity.SendMulticastAddress,
  61.                 m_dataEntity.SendPort);
  62.             int offset = 0;
  63.            
  64.             m_sendSocket.BeginSendTo(
  65.                 message,
  66.                 offset,
  67.                 message.Length,
  68.                 SocketFlags.None,
  69.                 endPoint,
  70.                 OnSend,
  71.                 stateObject);           
  72.         }
  73.         private void OnSend(IAsyncResult ar)
  74.         {
  75.             int bytesCount = m_sendSocket.EndSendTo(ar);
  76.             Trace.WriteLine(
  77.                 string.Format("Messages Sent, Bytes: {0}",
  78.                 bytesCount));
  79.         }
  80.         private void BeginReceive()
  81.         {
  82.             Socket receiveSocket = m_receiveSocket;
  83.             if (receiveSocket == null) return;
  84.         
  85.             EndPoint remoteEP = AnyAddress;
  86.             StateObject stateObject = new StateObject(
  87.                 receiveSocket, m_objectStateBuffer);
  88.             int offset = 0;
  89.             receiveSocket.BeginReceiveFrom(
  90.                 stateObject.Buffer,
  91.                 offset,
  92.                 stateObject.Buffer.Length,
  93.                 SocketFlags.None,
  94.                 ref remoteEP,
  95.                 OnReceive,
  96.                 stateObject);
  97.         }
  98.         private void OnReceive(IAsyncResult result)
  99.         {
  100.             EndPoint remoteEP = AnyAddress;
  101.             try
  102.             {
  103.                 int bufferSize = m_receiveSocket.EndReceiveFrom(
  104.                     result, ref remoteEP);
  105.                 StateObject stateObject =
  106.                     (StateObject)result.AsyncState;
  107.                
  108.                 BufferReceivedEventArgs e = new BufferReceivedEventArgs(
  109.                     stateObject.Buffer, bufferSize);
  110.                
  111.                 MessageReceived(this, e);
  112.                
  113.                 BeginReceive();
  114.             }
  115.             catch
  116.             {
  117.                 Trace.WriteLine("Disconnection");
  118.             }
  119.         }
  120.         private static Socket CreateUdpSocket()
  121.         {
  122.             Socket socket = new Socket(
  123.                 AddressFamily.InterNetwork,
  124.                 SocketType.Dgram,
  125.                 ProtocolType.Udp);
  126.            
  127.             return socket;
  128.         }
  129.         private void JoinMulticast(Socket socket)
  130.         {
  131.             MulticastOption multicastOption = GetMulticastOption();
  132.            
  133.             socket.SetSocketOption(
  134.                 SocketOptionLevel.IP,
  135.                 SocketOptionName.MulticastTimeToLive,
  136.                 64);
  137.            
  138.             socket.SetSocketOption(
  139.                 SocketOptionLevel.IP,
  140.                 SocketOptionName.AddMembership,
  141.                 multicastOption);
  142.         }
  143.         private void DropMulticast(Socket socket)
  144.         {
  145.             MulticastOption multicastOption = GetMulticastOption();
  146.            
  147.             socket.SetSocketOption(
  148.                 SocketOptionLevel.IP,
  149.                 SocketOptionName.MulticastTimeToLive,
  150.                 64);
  151.            
  152.             socket.SetSocketOption(
  153.                 SocketOptionLevel.IP,
  154.                 SocketOptionName.DropMembership,
  155.                 multicastOption);
  156.         }
  157.         private MulticastOption GetMulticastOption()
  158.         {
  159.             IPAddress ip = m_dataEntity.ReceiveMulticastAddress;
  160.             // MulticastOption can be constructed with the
  161.             // overload that allows setting the address of
  162.             // the network card from which the IGMP package
  163.             // will be sent to the router.
  164.             MulticastOption mo = new MulticastOption(ip);
  165.             return mo;
  166.         }
  167.     }

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.

Code Snippet
  1.     MulticastConfiguration multicastConfiguration = ReadConfiguration();
  2.     m_TransportAgent = new TransportAgent(
  3.         multicastConfiguration, OnMessageReceived);
Sending Multicast Data
Code Snippet
  1.     ASCIIEncoding asciiEncoding = new ASCIIEncoding();
  2.     string message = m_textBoxMessage.Text;
  3.     byte[] bytes = asciiEncoding.GetBytes(message);
  4.     m_TransportAgent.Send(bytes);
Receiving Multicast Data
Code Snippet
  1.     private void OnMessageReceived(object sender, BufferReceivedEventArgs e)
  2.     {
  3.         string message = Encoding.ASCII.GetString(e.Buffer);
  4.         Print(message);
  5.     }

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.

image

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.

image

image

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’.

image

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’

image

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’.

image

image

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.

4 comments:

  1. Hi! Could you please reupload the source? The link is down :/

    ReplyDelete
  2. Please share the code.The link seems to be not accessible

    ReplyDelete
  3. I updated the link, if you still can't download let me know and I will send you a copy via email

    ReplyDelete
  4. thank you very much. your blog is very useful for me^^/

    ReplyDelete