├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── multicast-test.sln ├── multicast-test ├── Program.cs ├── favicon-circle.ico └── multicast-test.csproj ├── receiving.png ├── select.png └── sending.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | .vs/ 80 | 81 | # ReSharper is a .NET coding add-in 82 | _ReSharper* 83 | 84 | # Installshield output folder 85 | [Ee]xpress 86 | 87 | # DocProject is a documentation generator add-in 88 | DocProject/buildhelp/ 89 | DocProject/Help/*.HxT 90 | DocProject/Help/*.HxC 91 | DocProject/Help/*.hhc 92 | DocProject/Help/*.hhk 93 | DocProject/Help/*.hhp 94 | DocProject/Help/Html2 95 | DocProject/Help/html 96 | 97 | # Click-Once directory 98 | publish 99 | 100 | # Others 101 | [Bb]in 102 | [Oo]bj 103 | sql 104 | TestResults 105 | *.Cache 106 | ClientBin 107 | stylecop.* 108 | ~$* 109 | *.dbmdl 110 | Generated_Code #added for RIA/Silverlight projects 111 | .vs/ 112 | 113 | # Backup & report files from converting an old project file to a newer 114 | # Visual Studio version. Backup files are not needed, because we have git ;-) 115 | _UpgradeReport_Files/ 116 | Backup*/ 117 | UpgradeLog*.XML 118 | 119 | 120 | 121 | ############ 122 | ## Windows 123 | ############ 124 | 125 | # Windows image file caches 126 | Thumbs.db 127 | 128 | # Folder config file 129 | Desktop.ini 130 | 131 | 132 | ############# 133 | ## Python 134 | ############# 135 | 136 | *.py[co] 137 | 138 | # Packages 139 | *.egg 140 | *.egg-info 141 | dist 142 | build 143 | eggs 144 | parts 145 | bin 146 | var 147 | sdist 148 | develop-eggs 149 | .installed.cfg 150 | 151 | # Installer logs 152 | pip-log.txt 153 | 154 | # Unit test / coverage reports 155 | .coverage 156 | .tox 157 | 158 | #Translations 159 | *.mo 160 | 161 | #Mr Developer 162 | .mr.developer.cfg 163 | 164 | # Mac crap 165 | .DS_Store 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Westgate Cyber Security Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Command Line Multicast Testing Tool 2 | 3 | **Download for Windows and Linux:** [https://github.com/enclave-networks/multicast-test/releases/tag/multicast-test-v1.0.1.0](https://github.com/enclave-networks/multicast-test/releases/tag/multicast-test-v1.0.1.0) 4 | 5 | Testing multicast traffic can be challenging and tends to involve running an application on two systems, phsical or virtual connected to the network. 6 | 7 | Usually we might reach for iPerf and/or multicast video streaming in VLC. Both are useful but, iPerf3 has removed support for multicast traffic, and sometimes its not as obvious as it could be whether the tools are doing what we think they are. iPerf can be complicated, and VLC multicast streaming can be buggy. 8 | 9 | ![select an interface](https://github.com/enclave-networks/multicast-test/raw/master/select.png) 10 | 11 | This is a simpler command-line tool that runs on both Linux and Windows which you can use to validate multicast connectivity. Run the tool on two or more different machines. Choose the relevant interface on both systems and then select action option 1 to transmit data, and action option 2 to recieve. 12 | 13 | > Note. don't try using interface 0 (any) to send, it won't work. Pick the speicifc interface you want to test instead. 14 | 15 | On the sending host you'll see output like this (option 1): 16 | 17 | ![sending data](https://github.com/enclave-networks/multicast-test/raw/master/sending.png) 18 | 19 | ![receiving data](https://github.com/enclave-networks/multicast-test/raw/master/receiving.png) 20 | 21 | See also the [Singlewire Multicast Testing Tool](https://support.singlewire.com/s/software-downloads/a17C0000008Dg7AIAS/ictestermulticastzip) discussed [here](https://salmannaqvi.com/2016/11/14/simple-multicast-testing-tool-for-windows/) by Salman Naqvi – 2 x CCIE. The Singlewire tool is perfectly adequate if you have a single network interface, but if you're working on systems with multiple network interfaces, this version should be quite useful. 22 | -------------------------------------------------------------------------------- /multicast-test.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2035 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "multicast-test", "multicast-test\multicast-test.csproj", "{2FD990DB-29FA-42FE-872A-76B5BC6F2132}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {2FD990DB-29FA-42FE-872A-76B5BC6F2132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2FD990DB-29FA-42FE-872A-76B5BC6F2132}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {2FD990DB-29FA-42FE-872A-76B5BC6F2132}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {2FD990DB-29FA-42FE-872A-76B5BC6F2132}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {79A7AB49-6BC0-4840-B5E3-5EBBF2A539CB} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /multicast-test/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.NetworkInformation; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | 6 | namespace multicast_test 7 | { 8 | public class Program 9 | { 10 | public static int TTL = 64; 11 | 12 | public static void Main(string[] args) 13 | { 14 | Console.WriteLine("enclave.io - Simple Multicast Testing Tool"); 15 | Console.WriteLine("==========================================\n"); 16 | Console.WriteLine("Interface list:\n"); 17 | Console.WriteLine($" 0: {"0.0.0.0",-40} Any"); 18 | 19 | AddressDictionary.Add(0, IPAddress.Any); 20 | 21 | // enumerate available interfaces 22 | var i = 1; 23 | foreach (var iface in NetworkInterface.GetAllNetworkInterfaces().Where(n => n.SupportsMulticast && n.OperationalStatus == OperationalStatus.Up)) 24 | { 25 | foreach (var ip in iface.GetIPProperties().UnicastAddresses) 26 | { 27 | Console.WriteLine($" {i,2}: {ip.Address,-40} {iface.Name} ({iface.Description})"); 28 | AddressDictionary.Add(i, ip.Address); 29 | i++; 30 | } 31 | } 32 | 33 | // prompt user to select an interface 34 | var selection = -1; 35 | while (selection == -1) 36 | { 37 | try 38 | { 39 | Console.Write("\nSelect interface: "); 40 | selection = Convert.ToInt32(Console.ReadLine()); 41 | 42 | // prevent user selecting a number beyond the range of display interfaces 43 | if (selection > i - 1) selection = -1; 44 | 45 | // select binding address from the interface dictionary 46 | _bindingAddress = AddressDictionary[selection]; 47 | } 48 | catch (Exception) 49 | { 50 | // swallow 51 | } 52 | } 53 | 54 | // prompt to select a multicast address 55 | Console.WriteLine(); 56 | while (true) 57 | { 58 | Console.Write($"Enter multicast address (224.0.0.0 to 239.255.255.255) to use [default: {MulticastAddress}]: "); 59 | string enteredMc = Console.ReadLine(); 60 | if(enteredMc == null || enteredMc == string.Empty) break; // Use default multicast address 61 | if(IPAddress.TryParse(enteredMc, out IPAddress multicastAddress)) 62 | { 63 | if(IsMulticast(multicastAddress)) 64 | { 65 | MulticastAddress = multicastAddress; 66 | break; 67 | } 68 | Console.WriteLine("A multicast IP addresses must be between 224.0.0.0 to 239.255.255.255."); 69 | continue; 70 | } 71 | Console.WriteLine("Not a valid IP address"); 72 | } 73 | 74 | // prompt to select a multicast port 75 | Console.WriteLine(); 76 | while (true) 77 | { 78 | Console.Write($"Enter multicast port to use (between 1 and 65535) [default: {MulticastPort}]: "); 79 | string enteredPortString = Console.ReadLine(); 80 | if(string.IsNullOrEmpty(enteredPortString)) break; // Use default port 81 | if(!int.TryParse(enteredPortString, out int enteredPort)) 82 | { 83 | Console.WriteLine("Not a valid number"); 84 | continue; 85 | } 86 | if(enteredPort < 0 || enteredPort > 65535) 87 | { 88 | Console.WriteLine("Port must be between 1 and 65535"); 89 | continue; 90 | } 91 | MulticastPort = enteredPort; 92 | break; 93 | } 94 | 95 | // reset selection variable 96 | selection = -1; 97 | 98 | Console.WriteLine(); 99 | Console.WriteLine(" 1: Multicast sender (transmit data)"); 100 | Console.WriteLine(" 2: Multicast subscriber (listen socket, receive data)"); 101 | Console.WriteLine(" 9: Exit"); 102 | Console.WriteLine(); 103 | 104 | // prompt to select an action 105 | while (selection == -1) 106 | { 107 | try 108 | { 109 | Console.Write("Select action: "); 110 | selection = Convert.ToInt32(Console.ReadLine()); 111 | 112 | switch (selection) 113 | { 114 | case 9: 115 | { 116 | return; 117 | } 118 | case 1: 119 | { 120 | try 121 | { 122 | using (var client = new UdpClient()) 123 | { 124 | client.EnableBroadcast = true; 125 | client.Client.SetSocketOption((_bindingAddress.AddressFamily == AddressFamily.InterNetwork) ? SocketOptionLevel.IP : SocketOptionLevel.IPv6, SocketOptionName.MulticastTimeToLive, TTL); 126 | 127 | Console.WriteLine($"\nBound udp client to {_bindingAddress}. Sending data to multicast group address {MulticastAddress}"); 128 | Console.WriteLine(); 129 | 130 | ulong n = 0; 131 | while (true) 132 | { 133 | SendMessage(client, $"Simple Multicast Testing Tool @ {DateTime.Now.ToLongTimeString()}"); 134 | 135 | Console.WriteLine($"Message {n,-5} sent to {MulticastAddress}:{MulticastPort} TTL: {client.Ttl}"); 136 | Thread.Sleep(1000); 137 | n++; 138 | } 139 | } 140 | } 141 | catch (Exception e) 142 | { 143 | Console.WriteLine(e.Message); 144 | } 145 | 146 | break; 147 | } 148 | case 2: 149 | { 150 | Listen(); 151 | break; 152 | } 153 | default: 154 | { 155 | selection = -1; 156 | break; 157 | } 158 | } 159 | } 160 | catch (Exception e) 161 | { 162 | Console.WriteLine(e); 163 | } 164 | } 165 | } 166 | 167 | public static void Listen() 168 | { 169 | _udpClient = new UdpClient(MulticastPort); 170 | 171 | _udpClient.EnableBroadcast = true; 172 | _udpClient.JoinMulticastGroup(MulticastAddress, _bindingAddress); 173 | _udpClient.Client.SetSocketOption((_bindingAddress.AddressFamily == AddressFamily.InterNetwork) ? SocketOptionLevel.IP : SocketOptionLevel.IPv6, SocketOptionName.MulticastTimeToLive, TTL); 174 | 175 | var receiveThread = new Thread(Receive); 176 | receiveThread.Start(); 177 | 178 | Console.WriteLine($"\nBound udp listener on {_bindingAddress}. Joined multicast group {MulticastAddress}. Port {MulticastPort}. Waiting to receive data...\n"); 179 | } 180 | 181 | public static void Receive() 182 | { 183 | while (true) 184 | { 185 | var ipEndPoint = new IPEndPoint(IPAddress.Any, 0); 186 | 187 | var data = _udpClient.Receive(ref ipEndPoint); 188 | var message = Encoding.Default.GetString(data); 189 | 190 | Console.WriteLine($"Received {message.Length} bytes from {ipEndPoint}: \"{message}\""); 191 | } 192 | } 193 | 194 | public static void SendMessage(UdpClient client, string message) 195 | { 196 | var data = Encoding.Default.GetBytes(message); 197 | 198 | var ipEndPoint = new IPEndPoint(MulticastAddress, MulticastPort); 199 | 200 | client.Send(data, data.Length, ipEndPoint); 201 | } 202 | 203 | private static bool IsMulticast(IPAddress ipAddress) 204 | { 205 | byte addressFirstOctet = ipAddress.GetAddressBytes()[0]; 206 | return addressFirstOctet >= 224 && addressFirstOctet <= 239; 207 | } 208 | 209 | private static IPAddress _bindingAddress; 210 | 211 | private static IPAddress MulticastAddress = IPAddress.Parse("239.0.1.2"); 212 | 213 | private static int MulticastPort = 20480; 214 | 215 | private static readonly Dictionary AddressDictionary = new Dictionary(); 216 | 217 | private static UdpClient _udpClient; 218 | } 219 | } -------------------------------------------------------------------------------- /multicast-test/favicon-circle.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enclave-networks/multicast-test/0ec1996ce7160b263122579843436939e68604c4/multicast-test/favicon-circle.ico -------------------------------------------------------------------------------- /multicast-test/multicast-test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | multicast_test 7 | enable 8 | disable 9 | 10 | true 11 | true 12 | true 13 | full 14 | true 15 | none 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /receiving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enclave-networks/multicast-test/0ec1996ce7160b263122579843436939e68604c4/receiving.png -------------------------------------------------------------------------------- /select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enclave-networks/multicast-test/0ec1996ce7160b263122579843436939e68604c4/select.png -------------------------------------------------------------------------------- /sending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enclave-networks/multicast-test/0ec1996ce7160b263122579843436939e68604c4/sending.png --------------------------------------------------------------------------------