To test my server/client application, where each client is known by its IP address, I created several network adapters (see How to Create a Virtual Network Adapter in .NET?). Both 192.168.0.10 and 11 now correspond to local ethernet adaptors (10 being the "real" one, 11 being a loopback adapter).
The client can Connect itself to the server as long as it doesn't Bind its socket to a specific address. But if it does, the server doesn't notice anything and a timeout occurs in the client (I want to use Bind as for security reasons the server automatically detects which client is connecting itself by looking at the IP address of the remote end point of the new connection: the server will drop the connection at once if it doesn't know the IP address - previously I was using several virtual machines, but it uses a lot more RAM and is less practical to use).
Here's the code in my server, listening eg on 192.168.0.10:1234
IPEndPoint myEP = new IPEndPoint(myAddress, myPort);
Socket listeningSocket = new Socket(myAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listeningSocket.Bind(myEP);
listeningSocket.Listen(50);
Socket acceptedSocket = listeningSocket.Accept();
Here's the code in my client, binding eg to 192.168.0.11 (any port) and connecting to 192.168.0.10:1234
Socket socket = new Socket(svrAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(myAddress, 0)); // Bind to local address using automatic port
socket.Connect(new IPEndPoint(svrAddress, svrPort)); // Works fine without Bind, timeout with Bind
I've tried the same using the corresponding IPv6 addresses but I get the exact same result.
If I Bind the client on the same address (using a different port than the server), it works fine.
Any idea what I'm doing wrong?
EDIT Here is my test projects (it might be useful to someone)
Server part:
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Server
{
class Program
{
static void Main(string[] args)
{
IPAddress[] ips = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
string line = string.Empty;
while (line != "q")
{
// Gets the IP address to listen on.
Console.WriteLine("IP to listen on:");
int count = 0;
foreach (IPAddress ip in ips)
Console.WriteLine("{0}: {1}", ++count, ip.ToString());
string numString = Console.ReadLine();
int pos = Convert.ToInt32(numString) - 1;
IPAddress myAddress = ips[pos]; // Removing or not the scope ID doesn't change anything as "localEndPoint" below will contain it no matter what
// Binds and starts listening.
IPEndPoint myEP = new IPEndPoint(myAddress, 12345);
Socket listeningSocket = new Socket(myAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listeningSocket.Bind(myEP);
listeningSocket.Listen(50);
IPEndPoint localEndPoint = (IPEndPoint)listeningSocket.LocalEndPoint;
Console.WriteLine("Listening on {0}:{1}", localEndPoint.Address, localEndPoint.Port);
Task.Factory.StartNew(() =>
{
try
{
// Accepts new connections and sends some dummy byte array, then closes the socket.
Socket acceptedSocket = listeningSocket.Accept();
IPEndPoint remoteEndPoint = (IPEndPoint)acceptedSocket.RemoteEndPoint;
Console.WriteLine("Accepted connection from {0}:{1}.", remoteEndPoint.Address, remoteEndPoint.Port);
acceptedSocket.Send(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
acceptedSocket.Close(5000);
Console.WriteLine("-= FINISHED =- Type q to quit, anything else to continue");
}
catch (Exception ex)
{ }
});
line = Console.ReadLine();
// Closes the listening socket.
listeningSocket.Close();
}
}
}
}
Client part
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Client
{
class Program
{
static void Main(string[] args)
{
IPAddress[] ips = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
string line = string.Empty;
while (line != "q")
{
// Gets the IP address to connect to (removes the "scope ID" if it's an IPv6).
Console.WriteLine("IP to connect to:");
int count = 0;
foreach (IPAddress ip in ips)
Console.WriteLine("{0}: {1}", ++count, ip.ToString());
string numString = Console.ReadLine();
int pos = Convert.ToInt32(numString) - 1;
IPAddress svrAddress = ips[pos].AddressFamily == AddressFamily.InterNetworkV6
? new IPAddress(ips[pos].GetAddressBytes())
: ips[pos];
Console.WriteLine("Connecting to " + svrAddress);
// Gets the IP address to bind on (can chose "none" - also removes the "scope ID" if it's an IPv6).
Console.WriteLine("IP to bind to:");
Console.WriteLine("0: none");
count = 0;
IPAddress[] filteredIps = ips.Where(i => i.AddressFamily == svrAddress.AddressFamily).ToArray();
foreach (IPAddress ip in filteredIps)
Console.WriteLine("{0}: {1}", ++count, ip.ToString());
numString = Console.ReadLine();
pos = Convert.ToInt32(numString) - 1;
IPEndPoint localEndPoint = (pos == -1)
? null
: new IPEndPoint(
filteredIps[pos].AddressFamily == AddressFamily.InterNetworkV6
? new IPAddress(filteredIps[pos].GetAddressBytes())
: filteredIps[pos]
, 0);
Console.WriteLine("Binding to " + (localEndPoint == null ? "none" : localEndPoint.Address.ToString()));
// Binds to an address if we chose to.
Socket socket = new Socket(svrAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
if (localEndPoint != null)
socket.Bind(localEndPoint);
Task.Factory.StartNew(() =>
{
try
{
// Connects to the server and receives the dummy byte array, then closes the socket.
socket.Connect(new IPEndPoint(svrAddress, 12345));
IPEndPoint remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint;
Console.WriteLine("Connected to {0}:{1}", remoteEndPoint.Address, remoteEndPoint.Port);
byte[] buffer = new byte[10];
Console.WriteLine((socket.Receive(buffer) == buffer.Length) ? "Received message" : "Incorrect message");
socket.Close();
}
catch (Exception ex)
{
// An exception occured: should be a SocketException due to a timeout if we chose to bind to an address.
Console.WriteLine("ERROR: " + ex.ToString());
}
Console.WriteLine("-= FINISHED =- Type q to quit, anything else to continue");
});
line = Console.ReadLine();
}
}
}
}
Actually it was a configuration issue with my network adapters and it has to do with "Weak and Strong Host model".
From what I've read ( Using a specific network interface for a socket in windows ) binding on Windows previous to Vista would only work for incoming traffic, and it wouldn't do anything for outgoing traffic.
Starting with Vista it's possible but by default it won't work: you need to allow the "weak host model" using
netsh interface ipv4 set interface "loopback" weakhostreceive=enabled
netsh interface ipv4 set interface "loopback" weakhostsend=enabled
See https://web.archive.org/web/20150402200610/http://blog.loadbalancer.org/direct-server-return-on-windows-2008-using-loopback-adpter/ for more info.
EDIT
Actually, instead of creating several loopback adapters and changing their host model, it's a lot better and easier to just create one loopback adapter, give it several IP addresses on a different network than your real IP, and then only use those IPs for your test. That way there's no routing issue, and you're sure everything stays local (as there's no routing between the real and loopback adapter).
Use below code in the server for binding the connection on all the interface on the same port.
// Binds and starts listening.
IPEndPoint myEP = new IPEndPoint(IPAddress.Any, 12345);
Related
I want to connect android with windows so that i can send my custom data.
so far i have only seen localhost examples.
but i want to connect my android which is on Sim-internet with windows connected through Ethernet.
this is android's code so far(client)
socket = Socket(address, port)
outputStream = socket.getOutputStream()
outputStream.write(data)
and this is C# windows(server)
IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("localhost");//localServer
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint ipEndPoint = new(ipAddress, 10090);
Console.WriteLine("initiating " + ipEndPoint.Port + ", " + ipAddress.ToString());
using Socket listener = new(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(ipEndPoint);
listener.Listen(100);
var handler = await listener.AcceptAsync();
while (true)
{
// Receive message.
var buffer = new byte[1_024];
var received = await handler.ReceiveAsync(buffer, SocketFlags.None);
var response = Encoding.UTF8.GetString(buffer, 0, received);
var eom = "<|EOM|>";
if (response.IndexOf(eom) > -1 /* is end of message */)
{
Console.WriteLine(
$"Socket server received message: \"{response.Replace(eom, "")}\"");
break;
}
}
as you can see it is localhost which can only be accessed by localNetworked devices
but i want to connect it over internet from different networked devices
How can i implement that .?
Your socket should bind to all addresses, not the local (new IPEndPoint(IPAddress.Any, ...)).
Your server is most likely behind a firewall/NAT so you need to setup port forwarding so your server can be accessed from the internet.
If the servers public IP address is static then you can use that. Otherwise you have to purchase a domain and configure a DNS server or use a free dynamic DNS service like No-IP or FreeDNS...
I'm writing a very simple .NET TCP Server and very simple TCP Client that should both run on the same machine (Window 10 Home PC) and connect each other (for testing purposes only).
In the server I'm waiting for the connection in this way:
public static void StartListening()
{
string hostname = Dns.GetHostName();
IPHostEntry ipHostInfo = Dns.GetHostEntry(hostname);
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, Properties.Settings.Default.Port);
Socket listener = new Socket(
ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
listener.Bind(localEndPoint);
listener.Listen(Properties.Settings.Default.Port);
while (true)
{
allDone.Reset();
Console.WriteLine("Waiting for a connection...");
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener);
allDone.WaitOne();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("\nPress ENTER to continue...");
Console.Read();
}
public static void AcceptCallback(IAsyncResult ar)
{
allDone.Set();
Socket listener = (Socket)ar.AsyncState;
Socket handler = listener.EndAccept(ar);
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
var frames = Directory.EnumerateFiles(Properties.Settings.Default.FramesPath);
foreach (string filename in frames)
{
Console.Write("Sending frame: {}...", filename);
SendFile(handler, filename);
Thread.Sleep(Properties.Settings.Default.FrameDelay);
}
}
In the client I'm creating a connection in this way:
private void startClient(string host, int port)
{
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
ClientTCP = new TcpClient();
ClientTCP.Connect(serverEndPoint);
Reader = new StreamReader(ClientTCP.GetStream());
Listen = true;
listner = Listener();
}
startClient is called in this way.
startClient(txtAddr.Text, (int)int.Parse(txtPort.Text));
When I run the client setting host variable to the current machine name (the same the server retrieve trough Dns.GetHostName() I got this exception:
An invalid ip address was specified.
I tried using 127.0.0.1 and I got:
Connection could not be established. Persistent rejection of the target computer 127.0.0.1:5002
I tried with localhost and I got
An invalid ip address was specified
again. I tried with the IP address assigned to WiFi and I got again
Connection could not be established. Persistent rejection of the target computer 192.168.10.11:5002
I'm sure the computer network works since I'm using it for many other things including connecting to local TCP services and I'm sure both client and server code works since on a different PC I'm able to connect them setting localhost as client host variable value. Why I'm unable to use loopback connections in my code?
Where did I fail?
P.S. I allowed connection to server binary in Windows firewall rules, I also allowed outgoing connection for client binary also.
For anyone willing to inspect the server code here it is:
Server code
You are using the constructor with the "IPEndPoint" parameter. This constructor does not connect to the server automatically and you must call the "Connect" method before using the socket. That is why you are getting the error message "Operation not allowed on unconnected sockets". Also, you have provided a wrong IPEndPoint for client.
Please try this on client:
private void startClient(string host, int port)
{
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
ClientTCP = new TcpClient();
ClientTCP.Connect(serverEndPoint);
Reader = new StreamReader(ClientTCP.GetStream());
Listen = true;
listner = Listener();
}
You may need some exception handling here, but it should work now.
UPDATE:
The ipHostInfo.AddressList may have more than one addresses and just one of them is what you wanted. It is not necessarily the first one. I specified this manually and it works:
//string hostname = Dns.GetHostName();
//IPHostEntry ipHostInfo = Dns.GetHostEntry("127.0.0.1");
//IPAddress ipAddress = ipHostInfo.AddressList[0];
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 5002);
I'm trying to send a UDP command to a device and receive a UDP response from that same device. The sending works fine. I can see the datagram depart (via WireShark). I can also see the datagram return from the device (again, via WireShark). The turnaround time between command departure and response reception is about 15 milliseconds.
Code
Byte[] button_click(Byte[] command)
{
// Device exists at a particular IP address and listens for UDP commands on a particular port
IPEndPoint SendingEndpoint = new IPEndPoint(DEVICE_IP, DEVICE_PORT);
// Device always sends from port 32795 to whatever port the command originated from on my machine
IPEndPoint ReceivingEndpoint = new IPEndPoint(DEVICE_IP, 32795);
// Sending client
sendingClient = new UdpClient();
sendingClient.Connect(SendingEndpoint);
// Receiving client
receivingClient = new UdpClient();
receivingClient.Client.ReceiveTimeout = RECEIVE_TIMEOUT; // timeout after 4 seconds
receivingClient.Connect(receivingEndpoint);
// Send command and wait for response
Byte[] response = null;
try
{
sendingClient.Connect(DEVICE_IP, DEVICE_PORT);
sendingClient.Send(command, command.Length);
response = receivingClient.Receive(ref receivingEndpoint);
}
catch (SocketException e)
{
// If we timeout, discard SocketException and return null response
}
return response;
}
Problem
I cannot capture the received datagram in my application. When I run the above code, I get the following exception:
"A connection attempt failed because the connected party did not
properly respond after a period of time, or established connection
failed because connected host has failed to respond."
There are similar posts on StackOverflow, but none of them appear to address my situation. And I've verified that my packets are not being swept up in my firewall.
What am I doing wrong?
I solved the problem. The solution required two things:
The sending and receiving clients had to use the same local port
The sending client had to use an IPEndPoint declared with IPAddress.Any and the receiving client had to use an IPEndPoint declared with the exact IP address of my local machine
Code
// Create endpoints
IPEndPoint DeviceEndPoint = new IPEndPoint(DEVICE_IP, DEVICE_PORT);
IPEndPoint localEndPointAny = new IPEndPoint(IPAddress.Any, LOCAL_PORT); // helps satisfy point 2
IPEndPoint localEndPointExplicit = new IPEndPoint(IPAddress.Parse(GetLocalIPAddress()), LOCAL_PORT); // helps satisfy point 2
IPEndPoint incomingEndPoint = null; // Later populated with remote sender's info
// Create sending client
UdpClient sendingClient = new UdpClient();
sendingClient.ExclusiveAddressUse = false; // Going to use same port for outgoing and incoming (helps satisfy point 1)
sendingClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); // helps satisfy point 1
sendingClient.Client.Bind(localEndPointAny); // Any outgoing IP address will do
// Create receiving client
UdpClient receivingClient = new UdpClient();
receivingClient.Client.ReceiveTimeout = RECEIVE_TIMEOUT; // 4000 milliseconds
receivingClient.ExclusiveAddressUse = false; // Going to use same port for outgoing and incoming (helps satisfy point 1)
receivingClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); // helps satisfy point 1
receivingClient.Client.Bind(localEndPointExplicit); // Must explicitly give machine's outgoing IP address
The code for getting a local IP address can be found here.
If you use the sendingClient to receive, then you can get the proper message. The reason is that the IP is consisted of Host+Port+Protocol, when the sending point connect to the device and send the message, the device receive the Endpoint and the UDP paired with the sending Endpoint. When receive client try to receive the message, there is nothing happen since UDP is Peer to Peer, and the receive client's port must be different to the send client, as a result, receive client get nothing. The following is my sample code for your reference.
IPAddress address;
IPAddress.TryParse("127.0.0.1", out address);
IPEndPoint recPoint = new IPEndPoint(address, 13154);
// IPEndPoint sendPoint = new IPEndPoint(address, 9999);
UdpClient send = new UdpClient(9999);
send.Connect(recPoint);
Byte[] response = null;
Byte[] command = System.Text.Encoding.Default.GetBytes("NO one");
try
{
send.Send(command, command.Length);
response = send.Receive(ref recPoint);
}
catch(Exception ex) {
Console.WriteLine(ex.ToString());
}
According to Alex's answer, I update a full example code for reference.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Net;
namespace console
{
class Program
{
static void Main(string[] args)
{
IPAddress address;
IPAddress.TryParse("192.168.14.173", out address);
IPEndPoint recPoint = new IPEndPoint(address, 13154);
IPEndPoint recAnyPoint = new IPEndPoint(IPAddress.Any, 13154);
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("192.168.14.174"), 13154);
// IPEndPoint sendPoint = new IPEndPoint(address, 9999);
UdpClient send = new UdpClient();
send.ExclusiveAddressUse = false;
// no need to use the low level socketoption
// send.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
send.Client.Bind(recAnyPoint);
send.Connect(ipPoint);
UdpClient receive = new UdpClient();
receive.ExclusiveAddressUse = false;
// receive.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
receive.Client.Bind(recPoint);
receive.Connect(ipPoint);
Byte[] response = null;
Byte[] command = System.Text.Encoding.Default.GetBytes("NO one");
try
{
send.Send(command, command.Length);
response = receive.Receive(ref ipPoint);
Console.WriteLine(System.Text.Encoding.Default.GetString(response));
}
catch(Exception ex) {
Console.WriteLine(ex.ToString());
}
}
}
}
I'm writing application which is based on UDP Hole Punching. I have a problem with establishing connection between clients. After each client sends something to server and server responses to each other with their IPs, clients aren't able to send anything to each other. Am I missing anything? Or my understanding of UDP Hole Punching is wrong? Yes, I've external IP for PC where server is.
server code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.IO;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
IPEndPoint localEP = new IPEndPoint(IP, 80);
UdpClient server = new UdpClient();
server.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
server.ExclusiveAddressUse = false;
server.Client.Bind(localEP);
IPEndPoint temp;
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 80);
Console.WriteLine("Dane servera : " + localEP);
byte[] buffer = server.Receive(ref remoteEP);
Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer));
temp = remoteEP;
remoteEP = new IPEndPoint(IPAddress.Any, 80);
byte[] buffer2 = server.Receive(ref remoteEP);
Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer2));
byte[] response = Encoding.ASCII.GetBytes(temp.ToString());
server.Send(response, response.Length, remoteEP);
byte[] response2 = Encoding.ASCII.GetBytes(remoteEP.ToString());
server.Send(response2, response2.Length,temp );
}
}
}
client 1:
namespace ConsoleApplication1
{
class Program
{
public static IPEndPoint CreateIPEndPoint(string endPoint)
{
string[] ep = endPoint.Split(':');
if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
IPAddress ip;
if (ep.Length > 2)
{
if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
{
throw new FormatException("Invalid ip-adress");
}
}
else
{
if (!IPAddress.TryParse(ep[0], out ip))
{
throw new FormatException("Invalid ip-adress");
}
}
int port;
if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
{
throw new FormatException("Invalid port");
}
return new IPEndPoint(ip, port);
}
static void Main(string[] args)
{
IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
IPEndPoint localpt = new IPEndPoint(IP, 80);
UdpClient client = new UdpClient();
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
client.ExclusiveAddressUse = false;
string powitanie = "ASUS";
byte[] buffer = new byte[100];
buffer = Encoding.ASCII.GetBytes(powitanie);
// client.Connect(localpt);
client.Send(buffer, buffer.Length,localpt);
byte[] otrzymane = client.Receive(ref localpt);
Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
Console.Read();
IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));
byte[] buffer2 = client.Receive(ref TV);
Console.WriteLine("Odpowiedz klienta : " + Encoding.ASCII.GetString(buffer2));
}
}
}
client 2:
namespace ConsoleApplication1
{
class Program
{
public static IPEndPoint CreateIPEndPoint(string endPoint)
{
string[] ep = endPoint.Split(':');
if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
IPAddress ip;
if (ep.Length > 2)
{
if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
{
throw new FormatException("Invalid ip-adress");
}
}
else
{
if (!IPAddress.TryParse(ep[0], out ip))
{
throw new FormatException("Invalid ip-adress");
}
}
int port;
if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
{
throw new FormatException("Invalid port");
}
return new IPEndPoint(ip, port);
}
static void Main(string[] args)
{
IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
IPEndPoint localpt = new IPEndPoint(IP, 80);
UdpClient client = new UdpClient();
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
client.ExclusiveAddressUse = false;
string powitanie = "Samsung";
byte[] buffer = new byte[100];
buffer = Encoding.ASCII.GetBytes(powitanie);
// client.Connect(localpt);
client.Send(buffer, buffer.Length,localpt);
byte[] otrzymane = client.Receive(ref localpt);
Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
Console.Read();
IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));
client.Send(buffer, buffer.Length, TV);
}
}
}
Without access to your exact environment and network, I am not convinced it will be possible to offer an answer with any confidence that it would be assured of addressing the issue. But here are something things to keep in mind:
First and foremost, "hole punching" is not a well-defined feature with a technical specification or industry standard for support. There is never any guarantee that it will work, though of course many routers work in a way that allows it to. If you are sure your code is correct but is for some reason still not working, it is always possible that you are using one or more routers that simply won't work with the technique.
Depending on the router behavior, it may or may not be sufficient for a client to have sent a datagram from a specific endpoint. A router may not route foreign datagrams to that endpoint until the recipient of the original outbound datagram has replied, i.e. two-way communication has in fact been established. Your current implementation appears to include code to allow the user testing the code to wait for both server replies before attempting to send to the other client, but do make sure you're taking advantage of that. I.e. that you don't try to send from one client to the other until both clients have received the server response.
In addition to the above, it may not be sufficient for the server to have sent datagrams to each client. A router may still discard datagrams received from an unknown endpoint. So a variation on the technique that is often required is that one client attempts to send a datagram to the other client, but also notifies that client via the server that it has done so (i.e. sends the server a datagram reporting this, and then the server sends a datagram to the intended recipient client to notify it).This datagram will be discarded, but the sending client's router doesn't know this, so when the other client replies directly to the sending client (having been notified by the server that it should), now the original sending client's router will pass the datagram to the that client. It saw the previous datagram that was intended for that other client, so it treats the inbound datagram from that other client as valid. From that point forward, both clients should be able to send to each other directly. Naturally, implementing this requires a more complicated application protocol than just forwarding IP addresses as your example here does.
Your code examples use SocketOptionName.ReuseAddress, which is almost always wrong. It appears to me that only your server socket binds to an explicit address, so using this option probably wouldn't affect the outcome of the test (i.e. you still only have one socket on any given address, even if you are testing in a single machine). But if there is more to your testing environment, such that the socket address really is being reused, that can easily interfere with the correct operation of the code.
Finally, you ask in a comment (please put relevant information and questions in the question itself): "should I use udp.connect to the server and then udp.send between clients". The answer is "no". First of all, using Connect() on a UDP socket is merely a convenience; UDP itself is connectionless, and so connecting a UDP socket is handled entirely within the framework. Second, in .NET when you "connect" a UDP socket, the framework will filter datagrams, restricting them to those received from the "connected" endpoint. This is exactly the opposite of what you want with hole-punching; i.e. you want to be able to receive datagrams from both the server and the other client.
I'm trying to make a IP version-agnostic client/server. I've been playing around with this in C++ and came up with something that works using the IN6ADDR_SETV4MAPPED macro (as Microsoft so kindly recommends). I followed the code sample here to accomplish this; my code that converts the address is no different from the example and everything works. I can connect to the server from the client by typing in both an IPv4 and IPv6 address (application does the mapping accordingly).
Now I'm looking for a solution in C# to upgrade a simple chat server I made and I've been unable to find any resources on how to use mapped addresses. I haven't found a function that provides the equivalent of IN6ADDR_SETV4MAPPED or any other facility in .net. My question is: how can I go about using a IPv4-mapped IPv6 address in C# (client-side)?
What I've tried:
Prepend string "::ffff:" to dotted IPv4 notation, call Socket.Connect using this address. Resulting address string looks like ::ffff:127.0.0.1.
Prepend string "::ffff:". Convert each octect from dotted format into hex and separate with colons, call Socket.Connect. Resulting address string looks like ::ffff:7f:0:0:1.
Neither of these approaches have worked so far.
Code snippet for server:
this.m_endPoint = new IPEndPoint(IPAddress.IPv6Any, 1337);
this.m_server.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
this.m_server.Bind(this.m_endPoint);
this.m_server.Listen(10);
Code snippet for client:
public ClientNet(string host, short port)
{
IPAddress ip;
if(IPAddress.TryParse(host, out ip))
{
string[] octs = host.Split(new char[] { '.' });
host = "::ffff:";
for(int i = 0; i < octs.Length; ++i)
{
host += string.Format("{0:x}", int.Parse(octs[i]));
if(i + 1 != octs.Length)
{
host += ":";
}
}
}
else
{
throw new ClientCreateException("[in ClientNet.Constructor] Unable to create client; use IPv4 address");
}
Socket client = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
client.Connect(host, port);
. . . //More initialization
}
Came back to this today thinking I might be able to figure it out. And I did! The answer is quite easy and I feel like a fool for not getting it for a year.
Two things about the code I posted:
Should have used IPAddress.MaptoIPv6 (see MSDN link) instead of that silly, contrived loop I wrote that's more prone to errors.
a. I realized later while working in .NET 4.0 that the convenience functions I used in my sample are not available until .NET 4.5. A quick code sample I threw together is at the bottom of this post, in case anyone else is stuck in an earlier version of .NET.
Real solution: Needed to call client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0); prior to calling client.Connect().
Here is a full code example of a sample application I wrote today to test it out. I was able to make a connection using both ::1 and 127.0.0.1 as addresses. Note that the server Socket is created for IPv6, and that the SocketOptionName.IPv6Only option is set to 0 on both the client and the server.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace sixsharp
{
class Program
{
static void Main(string[] args)
{
if(args.Length <= 0) //run as server
RunServer();
else
RunClient(args);
Console.WriteLine("Press enter to close.");
Console.ReadLine();
}
static void RunServer()
{
using(Socket serv = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp))
{
serv.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
serv.Bind(new IPEndPoint(IPAddress.IPv6Any, 1337));
serv.Listen(5);
Console.Write("Listening for client connection...");
using(Socket client = serv.Accept())
{
Console.WriteLine("Client connection accepted from {0}", client.RemoteEndPoint.ToString());
byte[] buf = new byte[128];
client.Receive(buf, 128, SocketFlags.None);
Console.WriteLine("Got \'{0}\' from client", Encoding.ASCII.GetString(buf));
Console.WriteLine("Echoing response");
client.Send(buf);
client.Shutdown(SocketShutdown.Both);
}
}
Console.WriteLine("Done.");
}
static void RunClient(string[] args)
{
using(Socket client = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp))
{
client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
Console.WriteLine("Setting up address, input is {0}", args[0]);
IPEndPoint ep;
try
{
ep = new IPEndPoint(IPAddress.Parse(args[0]), 1337);
}
catch(FormatException fe)
{
Console.WriteLine("IP address was improperly formatted and not parsed.");
Console.WriteLine("Detail: {0}", fe.Message);
return;
}
if(ep.AddressFamily == AddressFamily.InterNetwork)
{
ep = new IPEndPoint(ep.Address.MapToIPv6(), ep.Port);
if(!ep.Address.IsIPv4MappedToIPv6 || ep.Address.AddressFamily != AddressFamily.InterNetworkV6)
{
Console.WriteLine("Error mapping IPv4 address to IPv6");
return;
}
}
Console.WriteLine("Connecting to server {0} ...", ep.ToString());
try
{
client.Connect(ep);
}
catch(Exception ex)
{
Console.WriteLine("Unable to connect.\n Detail: {0}", ex.Message);
return;
}
client.Send(Encoding.ASCII.GetBytes("This is a test message. Hello!"));
byte[] buf = new byte[128];
client.Receive(buf);
Console.WriteLine("Got back from server: {0}", Encoding.ASCII.GetString(buf));
client.Shutdown(SocketShutdown.Both);
}
Console.WriteLine("Done.");
}
}
}
Client output:
Setting up address, input is 10.2.6.179
Connecting to server [::ffff:10.2.6.179]:1337 ...
Got back from server: This is a test message. Hello!
Done.
Press enter to close.
Server output:
Listening for client connection...Client connection accepted from [::ffff:10.2.6.179]:56275
Got 'This is a test message. Hello!
' from client
Echoing response
Done.
Press enter to close.
Sample extension methods providing the missing convenience functions in earlier versions of .NET:
static class IPAddressExtensions
{
public static IPAddress MapToIPv6(this IPAddress addr)
{
if(addr.AddressFamily != AddressFamily.InterNetwork)
throw new ArgumentException("Must pass an IPv4 address to MapToIPv6");
string ipv4str = addr.ToString();
return IPAddress.Parse("::ffff:" + ipv4str);
}
public static bool IsIPv4MappedToIPv6(this IPAddress addr)
{
bool pass1 = addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6, pass2;
try
{
pass2 = (addr.ToString().StartsWith("0000:0000:0000:0000:0000:ffff:") ||
addr.ToString().StartsWith("0:0:0:0:0:ffff:") ||
addr.ToString().StartsWith("::ffff:")) &&
IPAddress.Parse(addr.ToString().Substring(addr.ToString().LastIndexOf(":") + 1)).AddressFamily == AddressFamily.InterNetwork;
}
catch
{
return false;
}
return pass1 && pass2;
}
}