Best way to create IPEndpoint from string - c#

Since IPEndpoint contains a ToString() method that outputs:
10.10.10.10:1010
There should also be Parse() and/or TryParse() method but there isn't.
I can split the string on the : and parse an IP address and a port.
But is there a more elegant way?

This is one solution...
public static IPEndPoint CreateIPEndPoint(string endPoint)
{
string[] ep = endPoint.Split(':');
if(ep.Length != 2) throw new FormatException("Invalid endpoint format");
IPAddress ip;
if(!IPAddress.TryParse(ep[0], out ip))
{
throw new FormatException("Invalid ip-adress");
}
int port;
if(!int.TryParse(ep[1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
{
throw new FormatException("Invalid port");
}
return new IPEndPoint(ip, port);
}
Edit: Added a version that will handle IPv4 and IPv6 the previous one only handles IPv4.
// Handles IPv4 and IPv6 notation.
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);
}

I had the requirement of parsing an IPEndpoint with IPv6, v4 and hostnames. The solution I wrote is listed below:
public static IPEndPoint Parse(string endpointstring)
{
return Parse(endpointstring, -1);
}
public static IPEndPoint Parse(string endpointstring, int defaultport)
{
if (string.IsNullOrEmpty(endpointstring)
|| endpointstring.Trim().Length == 0)
{
throw new ArgumentException("Endpoint descriptor may not be empty.");
}
if (defaultport != -1 &&
(defaultport < IPEndPoint.MinPort
|| defaultport > IPEndPoint.MaxPort))
{
throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
}
string[] values = endpointstring.Split(new char[] { ':' });
IPAddress ipaddy;
int port = -1;
//check if we have an IPv6 or ports
if (values.Length <= 2) // ipv4 or hostname
{
if (values.Length == 1)
//no port is specified, default
port = defaultport;
else
port = getPort(values[1]);
//try to use the address as IPv4, otherwise get hostname
if (!IPAddress.TryParse(values[0], out ipaddy))
ipaddy = getIPfromHost(values[0]);
}
else if (values.Length > 2) //ipv6
{
//could [a:b:c]:d
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
{
string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
ipaddy = IPAddress.Parse(ipaddressstring);
port = getPort(values[values.Length - 1]);
}
else //[a:b:c] or a:b:c
{
ipaddy = IPAddress.Parse(endpointstring);
port = defaultport;
}
}
else
{
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
}
if (port == -1)
throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
return new IPEndPoint(ipaddy, port);
}
private static int getPort(string p)
{
int port;
if (!int.TryParse(p, out port)
|| port < IPEndPoint.MinPort
|| port > IPEndPoint.MaxPort)
{
throw new FormatException(string.Format("Invalid end point port '{0}'", p));
}
return port;
}
private static IPAddress getIPfromHost(string p)
{
var hosts = Dns.GetHostAddresses(p);
if (hosts == null || hosts.Length == 0)
throw new ArgumentException(string.Format("Host not found: {0}", p));
return hosts[0];
}
This has been tested to work with the following examples:
0.0.0.0:100
0.0.0.0
[::1]:100
[::1]
::1
[a:b:c:d]
[a:b:c:d]:100
example.org
example.org:100

It looks like there is already a built in Parse method that handles ip4 and ip6 addresses
http://msdn.microsoft.com/en-us/library/system.net.ipaddress.parse%28v=vs.110%29.aspx
// serverIP can be in ip4 or ip6 format
string serverIP = "192.168.0.1";
int port = 8000;
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(serverIP), port);

Here is my version of parsing text to IPEndPoint:
private static IPEndPoint ParseIPEndPoint(string text)
{
Uri uri;
if (Uri.TryCreate(text, UriKind.Absolute, out uri))
return new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port < 0 ? 0 : uri.Port);
if (Uri.TryCreate(String.Concat("tcp://", text), UriKind.Absolute, out uri))
return new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port < 0 ? 0 : uri.Port);
if (Uri.TryCreate(String.Concat("tcp://", String.Concat("[", text, "]")), UriKind.Absolute, out uri))
return new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port < 0 ? 0 : uri.Port);
throw new FormatException("Failed to parse text to IPEndPoint");
}
Tested with:
0.0.0.0
0.0.0.0:100
[::1]:100
[::1]:0
::1
[2001:db8:85a3:8d3:1319:8a2e:370:7348]
[2001:db8:85a3:8d3:1319:8a2e:370:7348]:100
http://0.0.0.0
http://0.0.0.0:100
http://[::1]
http://[::1]:100
https://0.0.0.0
https://[::1]

Apparently, IPEndPoint.Parse and IPEndPoint.TryParse were added in .NET Core 3.0.
In case you're targeting it, give those methods a try! The implementation is seen in the link above.

Here is a very simple solution, it handles both IPv4 and IPv6.
public class IPEndPoint : System.Net.IPEndPoint
{
public IPEndPoint(long address, int port) : base(address, port) { }
public IPEndPoint(IPAddress address, int port) : base(address, port) { }
public static bool TryParse(string value, out IPEndPoint result)
{
if (!Uri.TryCreate($"tcp://{value}", UriKind.Absolute, out Uri uri) ||
!IPAddress.TryParse(uri.Host, out IPAddress ipAddress) ||
uri.Port < 0 || uri.Port > 65535)
{
result = default(IPEndPoint);
return false;
}
result = new IPEndPoint(ipAddress, uri.Port);
return true;
}
}
Simply use the TryParse the way you would normally.
IPEndPoint.TryParse("192.168.1.10:80", out IPEndPoint ipv4Result);
IPEndPoint.TryParse("[fd00::]:8080", out IPEndPoint ipv6Result);

This will do IPv4 and IPv6. An extension method for this functionality would be on System.string. Not sure I want this option for every string I have in the project.
private static IPEndPoint IPEndPointParse(string endpointstring)
{
string[] values = endpointstring.Split(new char[] {':'});
if (2 > values.Length)
{
throw new FormatException("Invalid endpoint format");
}
IPAddress ipaddress;
string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
if (!IPAddress.TryParse(ipaddressstring, out ipaddress))
{
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", ipaddressstring));
}
int port;
if (!int.TryParse(values[values.Length - 1], out port)
|| port < IPEndPoint.MinPort
|| port > IPEndPoint.MaxPort)
{
throw new FormatException(string.Format("Invalid end point port '{0}'", values[values.Length - 1]));
}
return new IPEndPoint(ipaddress, port);
}

Create an extension method Parse and TryParse. I guess that is more elegant.

The parsing code is simple for an IPv4 endpoint, but IPEndPoint.ToString() on an IPv6 address also uses the same colon notation, but conflicts with the IPv6 address's colon notation. I was hoping Microsoft would spend the effort writing this ugly parsing code instead, but I guess I'll have to...

This is my take on the parsing of an IPEndPoint. Using the Uri class avoids having to handle the specifics of IPv4/6, and the presence or not of the port. You could can modify the default port for your application.
public static bool TryParseEndPoint(string ipPort, out System.Net.IPEndPoint result)
{
result = null;
string scheme = "iiiiiiiiiaigaig";
GenericUriParserOptions options =
GenericUriParserOptions.AllowEmptyAuthority |
GenericUriParserOptions.NoQuery |
GenericUriParserOptions.NoUserInfo |
GenericUriParserOptions.NoFragment |
GenericUriParserOptions.DontCompressPath |
GenericUriParserOptions.DontConvertPathBackslashes |
GenericUriParserOptions.DontUnescapePathDotsAndSlashes;
UriParser.Register(new GenericUriParser(options), scheme, 1337);
Uri parsedUri;
if (!Uri.TryCreate(scheme + "://" + ipPort, UriKind.Absolute, out parsedUri))
return false;
System.Net.IPAddress parsedIP;
if (!System.Net.IPAddress.TryParse(parsedUri.Host, out parsedIP))
return false;
result = new System.Net.IPEndPoint(parsedIP, parsedUri.Port);
return true;
}

If the port number is always provided after a ':', the following method may be a more elegant option (in code length instead of efficiency).
public static IPEndpoint ParseIPEndpoint(string ipEndPoint) {
int ipAddressLength = ipEndPoint.LastIndexOf(':');
return new IPEndPoint(
IPAddress.Parse(ipEndPoint.Substring(0, ipAddressLength)),
Convert.ToInt32(ipEndPoint.Substring(ipAddressLength + 1)));
}
It works fine for my simple application without considering complex IP address format.

A rough conversion of the .NET 3 code (for .NET 4.7) would be this:
// ReSharper disable once InconsistentNaming
public static class IPEndPointExtensions
{
public static bool TryParse(string s, out IPEndPoint result)
{
int addressLength = s.Length; // If there's no port then send the entire string to the address parser
int lastColonPos = s.LastIndexOf(':');
// Look to see if this is an IPv6 address with a port.
if (lastColonPos > 0)
{
if (s[lastColonPos - 1] == ']')
{
addressLength = lastColonPos;
}
// Look to see if this is IPv4 with a port (IPv6 will have another colon)
else if (s.Substring(0, lastColonPos).LastIndexOf(':') == -1)
{
addressLength = lastColonPos;
}
}
if (IPAddress.TryParse(s.Substring(0, addressLength), out IPAddress address))
{
uint port = 0;
if (addressLength == s.Length ||
(uint.TryParse(s.Substring(addressLength + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= IPEndPoint.MaxPort))
{
result = new IPEndPoint(address, (int)port);
return true;
}
}
result = null;
return false;
}
public static IPEndPoint Parse(string s)
{
if (s == null)
{
throw new ArgumentNullException(nameof(s));
}
if (TryParse(s, out IPEndPoint result))
{
return result;
}
throw new FormatException(#"An invalid IPEndPoint was specified.");
}
}

using System;
using System.Net;
static class Helper {
public static IPEndPoint ToIPEndPoint(this string value, int port = IPEndPoint.MinPort) {
if (string.IsNullOrEmpty(value) || ! IPAddress.TryParse(value, out var address))
return null;
var offset = (value = value.Replace(address.ToString(), string.Empty)).LastIndexOf(':');
if (offset >= 0)
if (! int.TryParse(value.Substring(offset + 1), out port) || port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
return null;
return new IPEndPoint(address, port);
}
}
class Program {
static void Main() {
foreach (var sample in new [] {
// See https://docops.ca.com/ca-data-protection-15/en/implementing/platform-deployment/technical-information/ipv6-address-and-port-formats
"192.168.0.3",
"fe80::214:c2ff:fec8:c920",
"10.0.1.53-10.0.1.80",
"10.0",
"10/7",
"2001:0db8:85a3/48",
"192.168.0.5:10",
"[fe80::e828:209d:20e:c0ae]:375",
":137-139",
"192.168:1024-65535",
"[fe80::]-[fe81::]:80"
}) {
var point = sample.ToIPEndPoint();
var report = point == null ? "NULL" : $#"IPEndPoint {{
Address: {point.Address}
AddressFamily: {point.AddressFamily}
Port: {point.Port}
}}";
Console.WriteLine($#"""{sample}"" to IPEndPoint is {report}
");
}
}
}

IPAddress ipAddress = IPAddress.Parse(yourIPAddress);
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, Convert.ToInt16(yourPortAddress));

Related

how to get header from SSL response and parse it in Proxy Server in c#?

i created a proxy server in c# to replace my file on local
for example :
i get http header with HttpListener and parse it to take file url and then i build a fake Response to download the file from my local , but is it possible to do that with HTTPS too ?
i tried to do it but i get only CONNECT request type.
if is it possible , how can i do this ?
url is like this : https://sgst.prod.dl.playstation.net/sgst/prod/00/PPSA01876_00/app/info/62/f_c2855f23a9de280972d5dc1c1fae07240bb76193a65393a6e304588ffb111b8a/UP0001-PPSA01876_00-FARCRY6GAME00000.crc
and in fiddler :
This is Tunnel with CONNECT request type
and what i want to get is :
This is HTTPS GET request type with URL that i need
so this is my code but i get host only (Host: sgst.prod.dl.playstation.net)
private void QueryHandle(string query)
{
HeaderFields = ParseQuery(query);
if ((HeaderFields == null) || !HeaderFields.ContainsKey("Host"))
SendBadRequest();
else
{
int num;
string requestedPath;
int index;
if (HttpRequestType.ToUpper().Equals("CONNECT"))
{
index = RequestedPath.IndexOf(":");
if (index >= 0)
{
requestedPath = RequestedPath.Substring(0, index);
num = RequestedPath.Length > (index + 1) ? int.Parse(RequestedPath.Substring(index + 1)) : 443;
}
else
{
requestedPath = RequestedPath;
num = 80;
}
}
else
{
index = HeaderFields["Host"].IndexOf(":");
if (index > 0)
{
requestedPath = HeaderFields["Host"].Substring(0, index);
num = int.Parse(HeaderFields["Host"].Substring(index + 1));
}
else
{
requestedPath = HeaderFields["Host"];
num = 80;
}
if (HttpRequestType.ToUpper().Equals("POST"))
{
int tempnum = query.IndexOf("\r\n\r\n");
_mHttpPost = query.Substring(tempnum + 4);
}
}
var localFile = String.Empty;
if (MonitorLog.RegexUrl(RequestedUrl))
localFile = UrlOperate.MatchFile(RequestedUrl);
_uinfo.PsnUrl = string.IsNullOrEmpty(_uinfo.PsnUrl) ? RequestedUrl : _uinfo.PsnUrl;
if (!HttpRequestType.ToUpper().Equals("CONNECT") && localFile != string.Empty && File.Exists(localFile))
{
_uinfo.ReplacePath = localFile;
_updataUrlLog(_uinfo);
SendLocalFile(localFile, HeaderFields.ContainsKey("Range") ? HeaderFields["Range"] : null, HeaderFields.ContainsKey("Proxy-Connection") ? HeaderFields["Proxy-Connection"] : null);
}
if (HttpRequestType.ToUpper().Equals("CONNECT"))
{
IPHostEntry hostEntry;
hostEntry = Dns.GetHostEntry(requestedPath);
string server = hostEntry.AddressList[0].ToString();
IPAddress hostIp = IPAddress.Parse(server);
var remoteEp = new IPEndPoint(hostIp, num);
DestinationSocket = new Socket(remoteEp.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
DestinationSocket.BeginConnect(remoteEp, OnConnected, DestinationSocket);
_uinfo.Host = hostEntry.AddressList[0].ToString();
_uinfo.ReplacePath = string.Empty;
_updataUrlLog(_uinfo);
}
private void OnConnected(IAsyncResult ar)
{
try
{
if (DestinationSocket == null)
{
return;
}
string str;
DestinationSocket.EndConnect(ar);
if (HttpRequestType.ToUpper().Equals("CONNECT"))
{
if (ClientSocket != null)
{
str = HttpVersion + " 200 Connection Established\r\n\r\n";
ClientSocket.BeginSend(Encoding.ASCII.GetBytes(str), 0, str.Length, SocketFlags.Partial,
OnOkSent, ClientSocket);
}
}
else
{
str = RebuildQuery();
DestinationSocket.BeginSend(Encoding.ASCII.GetBytes(str), 0, str.Length, SocketFlags.None,
OnQuerySent, DestinationSocket);
}
}
catch
{
Dispose();
}
}
private void OnOkSent(IAsyncResult ar)
{
try
{
if (ClientSocket != null && ClientSocket.EndSend(ar) == -1)
{
Dispose();
}
else
{
StartRelay();
}
}
catch
{
Dispose();
}
}
so i am not familiar with socket , ssl , protocols ...
i will thank you for your help and sorry for my bad english

How to UDP Hole Punching On C# [duplicate]

I am trying to accomplish UDP hole punching. I am basing my theory on this article and this WIKI page, but I am facing some issues with the C# coding of it. Here is my problem:
Using the code that was posted here I am now able to connect to a remote machine and listen on the same port for incoming connections (Bind 2 UDP clients to the same port).
For some reason the two bindings to the same port block each other from receiving any data.
I have a UDP server that responds to my connection so if I connect to it first before binding any other client to the port I get its responses back.
If I bind another client to the port no data will be received on either clients.
Following are 2 code pieces that show my problem. The first connects to a remote server to create the rule on the NAT device and then a listener is started on a different thread to capture the incoming packets. The code then sends packets to the local IP so that the listener will get it. The second only sends packets to the local IP to make sure this works. I know this is not the actual hole punching as I am sending the packets to myself without living the NAT device at all. I am facing a problem at this point, and I don't imagine this will be any different if I use a computer out side the NAT device to connect.
[EDIT] 2/4/2012
I tried using another computer on my network and WireShark (packet sniffer) to test the listener. I see the packets incoming from the other computer but are not received by the listener UDP client (udpServer) or the sender UDP client (client).
[EDIT] 2/5/2010
I have now added a function call to close the first UDP client after the initial sending and receiving of packets only living the second UDP client to listen on the port. This works and I can receive packets from inside the network on that port. I will now try to send and receive packets from outside the network. I will post my findings as soon as I find something.
Using this code I get data on the listening client:
static void Main(string[] args)
{
IPEndPoint localpt = new IPEndPoint(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
ThreadPool.QueueUserWorkItem(delegate
{
UdpClient udpServer = new UdpClient();
udpServer.ExclusiveAddressUse = false;
udpServer.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpServer.Client.Bind(localpt);
IPEndPoint inEndPoint = new IPEndPoint(IPAddress.Any, 0);
Console.WriteLine("Listening on " + localpt + ".");
byte[] buffer = udpServer.Receive(ref inEndPoint); //this line will block forever
Console.WriteLine("Receive from " + inEndPoint + " " + Encoding.ASCII.GetString(buffer) + ".");
});
Thread.Sleep(1000);
UdpClient udpServer2 = new UdpClient(6000);
// the following lines work and the data is received
udpServer2.Connect(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
udpServer2.Send(new byte[] { 0x41 }, 1);
Console.Read();
}
If I use the following code, after the connection and data transfer between my client and server, the listening UDP client will not receive anything:
static void Main(string[] args)
{
IPEndPoint localpt = new IPEndPoint(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
//if the following lines up until serverConnect(); are removed all packets are received correctly
client = new UdpClient();
client.ExclusiveAddressUse = false;
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
client.Client.Bind(localpt);
remoteServerConnect(); //connection to remote server is done here
//response is received correctly and printed to the console
ThreadPool.QueueUserWorkItem(delegate
{
UdpClient udpServer = new UdpClient();
udpServer.ExclusiveAddressUse = false;
udpServer.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpServer.Client.Bind(localpt);
IPEndPoint inEndPoint = new IPEndPoint(IPAddress.Any, 0);
Console.WriteLine("Listening on " + localpt + ".");
byte[] buffer = udpServer.Receive(ref inEndPoint); //this line will block forever
Console.WriteLine("Receive from " + inEndPoint + " " + Encoding.ASCII.GetString(buffer) + ".");
});
Thread.Sleep(1000);
UdpClient udpServer2 = new UdpClient(6000);
// I expected the following line to work and to receive this as well
udpServer2.Connect(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
udpServer2.Send(new byte[] { 0x41 }, 1);
Console.Read();
}
If i understand correctly, you are trying to communicate peer-to-peer between 2 clients each behind a different NAT, using a mediation server for hole punching?
Few years ago i did the exact same thing in c#, i haven't found the code yet, but ill give you some pointers if you like:
First, I wouldn't use the Connect() function on the udpclient, since UDP is a connectionless protocol, all this function really does is hide the functionality of a UDP socket.
You should perfrom the following steps:
Open a UDP socket on a server with it's ports not blocked by a firewall, at a specific port (eg Bind this socket to a chosen port for example 23000)
Create a UDP socket on the first client, and send something to the server at 23000. Do not bind this socket. When a udp is used to send a packet, windows will automatically assign a free port to the socket
Do the same from the other client
The server has now received 2 packets from 2 clients at 2 different adresses with 2 different ports. Test if the server can send packets back on the same address and port. (If this doesn't work you did something wrong or your NAT isn't working. You know its working if you can play games without opening ports :D)
The server should now send the address and port of the other clients to each connected client.
A client should now be able to send packets using UDP to the adresses received from the server.
You should note that the port used on the nat is probably not the same port as on your client pc!! The server should distribute this external port to clients. You must use the external adresses and the external ports to send to!
Also note that your NAT might not support this kind of port forwarding. Some NAT's forward all incoming traffic on a assigned port to you client, which is what you want. But some nats do filtering on the incoming packets adresses so it might block the other clients packets. This is unlikely though when using a standard personal user router.
Edit: After a lot more testing this doesn't seem to work at all for me unless I enable UPnP. So a lot of the things I wrote here you may find useful but many people don't have UPnP enabled (because it is a security risk) so it will not work for them.
Here is some code using PubNub as a relay server :). I don't recommend using this code without testing because it is not perfect (I'm not sure if it is even secure or the right way to do things? idk I'm not a networking expert) but it should give you an idea of what to do. It at least has worked for me so far in a hobby project. The things it is missing are:
Testing if the client is on your LAN. I just send to both which works for your LAN and a device on another network but that is very inefficient.
Testing when the client stops listening if, for example, they closed the program. Because this is UDP it is stateless so it doesn't matter if we are sending messages into the void but we probably shouldn't do that if noone is getting them
I use Open.NAT to do port forwarding programatically but this might not work on some devices. Specifically, it uses UPnP which is a little insecure and requires UDP port 1900 to be port forwarded manually. Once they do this it is supported on most routers but many have not done this yet.
So first of all, you need a way to get your external and local IPs. Here is code for getting your local IP:
// From http://stackoverflow.com/questions/6803073/get-local-ip-address
public string GetLocalIp()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip.ToString();
}
}
throw new Exception("Failed to get local IP");
}
And here is some code for getting your external IP via trying a few websites that are designed to return your external IP
public string GetExternalIp()
{
for (int i = 0; i < 2; i++)
{
string res = GetExternalIpWithTimeout(400);
if (res != "")
{
return res;
}
}
throw new Exception("Failed to get external IP");
}
private static string GetExternalIpWithTimeout(int timeoutMillis)
{
string[] sites = new string[] {
"http://ipinfo.io/ip",
"http://icanhazip.com/",
"http://ipof.in/txt",
"http://ifconfig.me/ip",
"http://ipecho.net/plain"
};
foreach (string site in sites)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(site);
request.Timeout = timeoutMillis;
using (var webResponse = (HttpWebResponse)request.GetResponse())
{
using (Stream responseStream = webResponse.GetResponseStream())
{
using (StreamReader responseReader = new System.IO.StreamReader(responseStream, Encoding.UTF8))
{
return responseReader.ReadToEnd().Trim();
}
}
}
}
catch
{
continue;
}
}
return "";
}
Now we need to find an open port and forward it to an external port. As mentioned above I used Open.NAT. First, you put together a list of ports that you think would be reasonable for your application to use after looking at registered UDP ports. Here are a few for example:
public static int[] ports = new int[]
{
5283,
5284,
5285,
5286,
5287,
5288,
5289,
5290,
5291,
5292,
5293,
5294,
5295,
5296,
5297
};
Now we can loop through them and hopefully find one that is not in use to use port forwarding on:
public UdpClient GetUDPClientFromPorts(out Socket portHolder, out string localIp, out int localPort, out string externalIp, out int externalPort)
{
localIp = GetLocalIp();
externalIp = GetExternalIp();
var discoverer = new Open.Nat.NatDiscoverer();
var device = discoverer.DiscoverDeviceAsync().Result;
IPAddress localAddr = IPAddress.Parse(localIp);
int workingPort = -1;
for (int i = 0; i < ports.Length; i++)
{
try
{
// You can alternatively test tcp with nc -vz externalip 5293 in linux and
// udp with nc -vz -u externalip 5293 in linux
Socket tempServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
tempServer.Bind(new IPEndPoint(localAddr, ports[i]));
tempServer.Close();
workingPort = ports[i];
break;
}
catch
{
// Binding failed, port is in use, try next one
}
}
if (workingPort == -1)
{
throw new Exception("Failed to connect to a port");
}
int localPort = workingPort;
// You could try a different external port if the below code doesn't work
externalPort = workingPort;
// Mapping ports
device.CreatePortMapAsync(new Open.Nat.Mapping(Open.Nat.Protocol.Udp, localPort, externalPort));
// Bind a socket to our port to "claim" it or cry if someone else is now using it
try
{
portHolder = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
portHolder.Bind(new IPEndPoint(localAddr, localPort));
}
catch
{
throw new Exception("Failed, someone is now using local port: " + localPort);
}
// Make a UDP Client that will use that port
UdpClient udpClient = new UdpClient(localPort);
return udpClient;
}
Now for the PubNub relay server code (P2PPeer will be defined later below). There is a lot here so I'm not really gonna explain it but hopefully the code is clear enough to help you understand what is going on
public delegate void NewPeerCallback(P2PPeer newPeer);
public event NewPeerCallback OnNewPeerConnection;
public Pubnub pubnub;
public string pubnubChannelName;
public string localIp;
public string externalIp;
public int localPort;
public int externalPort;
public UdpClient udpClient;
HashSet<string> uniqueIdsPubNubSeen;
object peerLock = new object();
Dictionary<string, P2PPeer> connectedPeers;
string myPeerDataString;
public void InitPubnub(string pubnubPublishKey, string pubnubSubscribeKey, string pubnubChannelName)
{
uniqueIdsPubNubSeen = new HashSet<string>();
connectedPeers = new Dictionary<string, P2PPeer>;
pubnub = new Pubnub(pubnubPublishKey, pubnubSubscribeKey);
myPeerDataString = localIp + " " + externalIp + " " + localPort + " " + externalPort + " " + pubnub.SessionUUID;
this.pubnubChannelName = pubnubChannelName;
pubnub.Subscribe<string>(
pubnubChannelName,
OnPubNubMessage,
OnPubNubConnect,
OnPubNubError);
return pubnub;
}
//// Subscribe callbacks
void OnPubNubConnect(string res)
{
pubnub.Publish<string>(pubnubChannelName, connectionDataString, OnPubNubTheyGotMessage, OnPubNubMessageFailed);
}
void OnPubNubError(PubnubClientError clientError)
{
throw new Exception("PubNub error on subscribe: " + clientError.Message);
}
void OnPubNubMessage(string message)
{
// The message will be the string ["localIp externalIp localPort externalPort","messageId","channelName"]
string[] splitMessage = message.Trim().Substring(1, message.Length - 2).Split(new char[] { ',' });
string peerDataString = splitMessage[0].Trim().Substring(1, splitMessage[0].Trim().Length - 2);
// If you want these, I don't need them
//string peerMessageId = splitMessage[1].Trim().Substring(1, splitMessage[1].Trim().Length - 2);
//string channelName = splitMessage[2].Trim().Substring(1, splitMessage[2].Trim().Length - 2);
string[] pieces = peerDataString.Split(new char[] { ' ', '\t' });
string peerLocalIp = pieces[0].Trim();
string peerExternalIp = pieces[1].Trim();
string peerLocalPort = int.Parse(pieces[2].Trim());
string peerExternalPort = int.Parse(pieces[3].Trim());
string peerPubnubUniqueId = pieces[4].Trim();
pubNubUniqueId = pieces[4].Trim();
// If you are on the same device then you have to do this for it to work idk why
if (peerLocalIp == localIp && peerExternalIp == externalIp)
{
peerLocalIp = "127.0.0.1";
}
// From me, ignore
if (peerPubnubUniqueId == pubnub.SessionUUID)
{
return;
}
// We haven't set up our connection yet, what are we doing
if (udpClient == null)
{
return;
}
// From someone else
IPEndPoint peerEndPoint = new IPEndPoint(IPAddress.Parse(peerExternalIp), peerExternalPort);
IPEndPoint peerEndPointLocal = new IPEndPoint(IPAddress.Parse(peerLocalIp), peerLocalPort);
// First time we have heard from them
if (!uniqueIdsPubNubSeen.Contains(peerPubnubUniqueId))
{
uniqueIdsPubNubSeen.Add(peerPubnubUniqueId);
// Dummy messages to do UDP hole punching, these may or may not go through and that is fine
udpClient.Send(new byte[10], 10, peerEndPoint);
udpClient.Send(new byte[10], 10, peerEndPointLocal); // This is if they are on a LAN, we will try both
pubnub.Publish<string>(pubnubChannelName, myPeerDataString, OnPubNubTheyGotMessage, OnPubNubMessageFailed);
}
// Second time we have heard from them, after then we don't care because we are connected
else if (!connectedPeers.ContainsKey(peerPubnubUniqueId))
{
//bool isOnLan = IsOnLan(IPAddress.Parse(peerExternalIp)); TODO, this would be nice to test for
bool isOnLan = false; // For now we will just do things for both
P2PPeer peer = new P2PPeer(peerLocalIp, peerExternalIp, peerLocalPort, peerExternalPort, this, isOnLan);
lock (peerLock)
{
connectedPeers.Add(peerPubnubUniqueId, peer);
}
// More dummy messages because why not
udpClient.Send(new byte[10], 10, peerEndPoint);
udpClient.Send(new byte[10], 10, peerEndPointLocal);
pubnub.Publish<string>(pubnubChannelName, connectionDataString, OnPubNubTheyGotMessage, OnPubNubMessageFailed);
if (OnNewPeerConnection != null)
{
OnNewPeerConnection(peer);
}
}
}
//// Publish callbacks
void OnPubNubTheyGotMessage(object result)
{
}
void OnPubNubMessageFailed(PubnubClientError clientError)
{
throw new Exception("PubNub error on publish: " + clientError.Message);
}
And here is a P2PPeer
public class P2PPeer
{
public string localIp;
public string externalIp;
public int localPort;
public int externalPort;
public bool isOnLan;
P2PClient client;
public delegate void ReceivedBytesFromPeerCallback(byte[] bytes);
public event ReceivedBytesFromPeerCallback OnReceivedBytesFromPeer;
public P2PPeer(string localIp, string externalIp, int localPort, int externalPort, P2PClient client, bool isOnLan)
{
this.localIp = localIp;
this.externalIp = externalIp;
this.localPort = localPort;
this.externalPort = externalPort;
this.client = client;
this.isOnLan = isOnLan;
if (isOnLan)
{
IPEndPoint endPointLocal = new IPEndPoint(IPAddress.Parse(localIp), localPort);
Thread localListener = new Thread(() => ReceiveMessage(endPointLocal));
localListener.IsBackground = true;
localListener.Start();
}
else
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(externalIp), externalPort);
Thread externalListener = new Thread(() => ReceiveMessage(endPoint));
externalListener.IsBackground = true;
externalListener.Start();
}
}
public void SendBytes(byte[] data)
{
if (client.udpClient == null)
{
throw new Exception("P2PClient doesn't have a udpSocket open anymore");
}
//if (isOnLan) // This would work but I'm not sure how to test if they are on LAN so I'll just use both for now
{
client.udpClient.Send(data, data.Length, new IPEndPoint(IPAddress.Parse(localIp), localPort));
}
//else
{
client.udpClient.Send(data, data.Length, new IPEndPoint(IPAddress.Parse(externalIp), externalPort));
}
}
// Encoded in UTF8
public void SendString(string str)
{
SendBytes(System.Text.Encoding.UTF8.GetBytes(str));
}
void ReceiveMessage(IPEndPoint endPoint)
{
while (client.udpClient != null)
{
byte[] message = client.udpClient.Receive(ref endPoint);
if (OnReceivedBytesFromPeer != null)
{
OnReceivedBytesFromPeer(message);
}
//string receiveString = Encoding.UTF8.GetString(message);
//Console.Log("got: " + receiveString);
}
}
}
Finally, here are all my usings:
using PubNubMessaging.Core; // Get from PubNub GitHub for C#, I used the Unity3D library
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
I'm open to comments and questions, feel free to give feedback if something here is bad practice or doesn't work. A few bugs were introduced in translation from my code that I'll fix here eventually but this should at least give you the idea of what to do.
Have you tried using the Async functions, here is a example of how you might get it to work it may need a bit of work to make it 100% functional:
public void HolePunch(String ServerIp, Int32 Port)
{
IPEndPoint LocalPt = new IPEndPoint(Dns.GetHostEntry(Dns.GetHostName()).AddressList[0], Port);
UdpClient Client = new UdpClient();
Client.ExclusiveAddressUse = false;
Client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
Client.Client.Bind(LocalPt);
IPEndPoint RemotePt = new IPEndPoint(IPAddress.Parse(ServerIp), Port);
// This Part Sends your local endpoint to the server so if the two peers are on the same nat they can bypass it, you can omit this if you wish to just use the remote endpoint.
byte[] IPBuffer = System.Text.Encoding.UTF8.GetBytes(Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString());
byte[] LengthBuffer = BitConverter.GetBytes(IPBuffer.Length);
byte[] PortBuffer = BitConverter.GetBytes(Port);
byte[] Buffer = new byte[IPBuffer.Length + LengthBuffer.Length + PortBuffer.Length];
LengthBuffer.CopyTo(Buffer,0);
IPBuffer.CopyTo(Buffer, LengthBuffer.Length);
PortBuffer.CopyTo(Buffer, IPBuffer.Length + LengthBuffer.Length);
Client.BeginSend(Buffer, Buffer.Length, RemotePt, new AsyncCallback(SendCallback), Client);
// Wait to receve something
BeginReceive(Client, Port);
// you may want to use a auto or manual ResetEvent here and have the server send back a confirmation, the server should have now stored your local (you sent it) and remote endpoint.
// you now need to work out who you need to connect to then ask the server for there remote and local end point then need to try to connect to the local first then the remote.
// if the server knows who you need to connect to you could just have it send you the endpoints as the confirmation.
// you may also need to keep this open with a keepalive packet untill it is time to connect to the peer or peers.
// once you have the endpoints of the peer you can close this connection unless you need to keep asking the server for other endpoints
Client.Close();
}
public void ConnectToPeer(String PeerIp, Int32 Port)
{
IPEndPoint LocalPt = new IPEndPoint(Dns.GetHostEntry(Dns.GetHostName()).AddressList[0], Port);
UdpClient Client = new UdpClient();
Client.ExclusiveAddressUse = false;
Client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
Client.Client.Bind(LocalPt);
IPEndPoint RemotePt = new IPEndPoint(IPAddress.Parse(PeerIp), Port);
Client.Connect(RemotePt);
//you may want to keep the peer client connections in a list.
BeginReceive(Client, Port);
}
public void SendCallback(IAsyncResult ar)
{
UdpClient Client = (UdpClient)ar.AsyncState;
Client.EndSend(ar);
}
public void BeginReceive(UdpClient Client, Int32 Port)
{
IPEndPoint ListenPt = new IPEndPoint(IPAddress.Any, Port);
Object[] State = new Object[] { Client, ListenPt };
Client.BeginReceive(new AsyncCallback(ReceiveCallback), State);
}
public void ReceiveCallback(IAsyncResult ar)
{
UdpClient Client = (UdpClient)((Object[])ar.AsyncState)[0];
IPEndPoint ListenPt = (IPEndPoint)((Object[])ar.AsyncState)[0];
Byte[] receiveBytes = Client.EndReceive(ar, ref ListenPt);
}
I hope this helps.
Update:
Whichever of the UdpClients binds first is the one that will be sent incoming packets by Windows. In your example try moving the code block that sets up the listening thread to the top.
Are you sure the problem is not just that the receive thread is only written to handle a single receive? Try replacing the receive thread with as below.
ThreadPool.QueueUserWorkItem(delegate
{
UdpClient udpServer = new UdpClient();
udpServer.ExclusiveAddressUse = false;
udpServer.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpServer.Client.Bind(localpt);
IPEndPoint inEndPoint = new IPEndPoint(IPAddress.Any, 0);
Console.WriteLine("Listening on " + localpt + ".");
while (inEndPoint != null)
{
byte[] buffer = udpServer.Receive(ref inEndPoint);
Console.WriteLine("Bytes received from " + inEndPoint + " " + Encoding.ASCII.GetString(buffer) + ".");
}
});
Sorry for uploading such a huge piece of code, but i guess this is very clearly explains how things work, and may be really useful. If you will have issues with this code, please let me know.
Note:
this is just a draft
(important) you MUST inform server with your local endpoint. if you will not do it you will not be able to communicate between two peers behind one NAT (for example, on one local machine) even if the server is out of NAT
you must close "puncher" client (at least, i did not manage to receive any packets until i did it). later you will be able to communicate with server using other UdpClient's
of cource it will not work with symmetric NAT
if you find that something in this code is "terrible practice", please tell me, i'm not network expert :)
Server.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using HolePunching.Common;
namespace HolePunching.Server
{
class Server
{
private static bool _isRunning;
private static UdpClient _udpClient;
private static readonly Dictionary<byte, PeerContext> Contexts = new Dictionary<byte, PeerContext>();
private static readonly Dictionary<byte, byte> Mappings = new Dictionary<byte, byte>
{
{1, 2},
{2, 1},
};
static void Main()
{
_udpClient = new UdpClient( Consts.UdpPort );
ListenUdp();
Console.ReadLine();
_isRunning = false;
}
private static async void ListenUdp()
{
_isRunning = true;
while ( _isRunning )
{
try
{
var receivedResults = await _udpClient.ReceiveAsync();
if ( !_isRunning )
{
break;
}
ProcessUdpMessage( receivedResults.Buffer, receivedResults.RemoteEndPoint );
}
catch ( Exception ex )
{
Console.WriteLine( $"Error: {ex.Message}" );
}
}
}
private static void ProcessUdpMessage( byte[] buffer, IPEndPoint remoteEndPoint )
{
if ( !UdpProtocol.UdpInfoMessage.TryParse( buffer, out UdpProtocol.UdpInfoMessage message ) )
{
Console.WriteLine( $" >>> Got shitty UDP [ {remoteEndPoint.Address} : {remoteEndPoint.Port} ]" );
_udpClient.Send( new byte[] { 1 }, 1, remoteEndPoint );
return;
}
Console.WriteLine( $" >>> Got UDP from {message.Id}. [ {remoteEndPoint.Address} : {remoteEndPoint.Port} ]" );
if ( !Contexts.TryGetValue( message.Id, out PeerContext context ) )
{
context = new PeerContext
{
PeerId = message.Id,
PublicUdpEndPoint = remoteEndPoint,
LocalUdpEndPoint = new IPEndPoint( message.LocalIp, message.LocalPort ),
};
Contexts.Add( context.PeerId, context );
}
byte partnerId = Mappings[context.PeerId];
if ( !Contexts.TryGetValue( partnerId, out context ) )
{
_udpClient.Send( new byte[] { 1 }, 1, remoteEndPoint );
return;
}
var response = UdpProtocol.PeerAddressMessage.GetMessage(
partnerId,
context.PublicUdpEndPoint.Address,
context.PublicUdpEndPoint.Port,
context.LocalUdpEndPoint.Address,
context.LocalUdpEndPoint.Port );
_udpClient.Send( response.Data, response.Data.Length, remoteEndPoint );
Console.WriteLine( $" <<< Responsed to {message.Id}" );
}
}
public class PeerContext
{
public byte PeerId { get; set; }
public IPEndPoint PublicUdpEndPoint { get; set; }
public IPEndPoint LocalUdpEndPoint { get; set; }
}
}
Client.cs
using System;
namespace HolePunching.Client
{
class Client
{
public const string ServerIp = "your.server.public.address";
static void Main()
{
byte id = ReadIdFromConsole();
// you need some smarter :)
int localPort = id == 1 ? 61043 : 59912;
var x = new Demo( ServerIp, id, localPort );
x.Start();
}
private static byte ReadIdFromConsole()
{
Console.Write( "Peer id (1 or 2): " );
var id = byte.Parse( Console.ReadLine() );
Console.Title = $"Peer {id}";
return id;
}
}
}
Demo.cs
using HolePunching.Common;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace HolePunching.Client
{
public class Demo
{
private static bool _isRunning;
private static UdpClient _udpPuncher;
private static UdpClient _udpClient;
private static UdpClient _extraUdpClient;
private static bool _extraUdpClientConnected;
private static byte _id;
private static IPEndPoint _localEndPoint;
private static IPEndPoint _serverUdpEndPoint;
private static IPEndPoint _partnerPublicUdpEndPoint;
private static IPEndPoint _partnerLocalUdpEndPoint;
private static string GetLocalIp()
{
var host = Dns.GetHostEntry( Dns.GetHostName() );
foreach ( var ip in host.AddressList )
{
if ( ip.AddressFamily == AddressFamily.InterNetwork )
{
return ip.ToString();
}
}
throw new Exception( "Failed to get local IP" );
}
public Demo( string serverIp, byte id, int localPort )
{
_serverUdpEndPoint = new IPEndPoint( IPAddress.Parse( serverIp ), Consts.UdpPort );
_id = id;
// we have to bind all our UdpClients to this endpoint
_localEndPoint = new IPEndPoint( IPAddress.Parse( GetLocalIp() ), localPort );
}
public void Start( )
{
_udpPuncher = new UdpClient(); // this guy is just for punching
_udpClient = new UdpClient(); // this will keep hole alive, and also can send data
_extraUdpClient = new UdpClient(); // i think, this guy is the best option for sending data (explained below)
InitUdpClients( new[] { _udpPuncher, _udpClient, _extraUdpClient }, _localEndPoint );
Task.Run( (Action) SendUdpMessages );
Task.Run( (Action) ListenUdp );
Console.ReadLine();
_isRunning = false;
}
private void InitUdpClients(IEnumerable<UdpClient> clients, EndPoint localEndPoint)
{
// if you don't want to use explicit localPort, you should create here one more UdpClient (X) and send something to server (it will automatically bind X to free port). then bind all clients to this port and close X
foreach ( var udpClient in clients )
{
udpClient.ExclusiveAddressUse = false;
udpClient.Client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true );
udpClient.Client.Bind( localEndPoint );
}
}
private void SendUdpMessages()
{
_isRunning = true;
var messageToServer = UdpProtocol.UdpInfoMessage.GetMessage( _id, _localEndPoint.Address, _localEndPoint.Port );
var messageToPeer = UdpProtocol.P2PKeepAliveMessage.GetMessage();
while ( _isRunning )
{
// while we dont have partner's address, we will send messages to server
if ( _partnerPublicUdpEndPoint == null && _partnerLocalUdpEndPoint == null )
{
_udpPuncher.Send( messageToServer.Data, messageToServer.Data.Length, _serverUdpEndPoint );
Console.WriteLine( $" >>> Sent UDP to server [ {_serverUdpEndPoint.Address} : {_serverUdpEndPoint.Port} ]" );
}
else
{
// you can skip it. just demonstration, that you still can send messages to server
_udpClient.Send( messageToServer.Data, messageToServer.Data.Length, _serverUdpEndPoint );
Console.WriteLine( $" >>> Sent UDP to server [ {_serverUdpEndPoint.Address} : {_serverUdpEndPoint.Port} ]" );
// THIS is how we punching hole! very first this message should be dropped by partner's NAT, but it's ok.
// i suppose that this is good idea to send this "keep-alive" messages to peer even if you are connected already,
// because AFAIK "hole" for UDP lives ~2 minutes on NAT. so "we will let it die? NEVER!" (c)
_udpClient.Send( messageToPeer.Data, messageToPeer.Data.Length, _partnerPublicUdpEndPoint );
_udpClient.Send( messageToPeer.Data, messageToPeer.Data.Length, _partnerLocalUdpEndPoint );
Console.WriteLine( $" >>> Sent UDP to peer.public [ {_partnerPublicUdpEndPoint.Address} : {_partnerPublicUdpEndPoint.Port} ]" );
Console.WriteLine( $" >>> Sent UDP to peer.local [ {_partnerLocalUdpEndPoint.Address} : {_partnerLocalUdpEndPoint.Port} ]" );
// "connected" UdpClient sends data much faster,
// so if you have something that your partner cant wait for (voice, for example), send it this way
if ( _extraUdpClientConnected )
{
_extraUdpClient.Send( messageToPeer.Data, messageToPeer.Data.Length );
Console.WriteLine( $" >>> Sent UDP to peer.received EP" );
}
}
Thread.Sleep( 3000 );
}
}
private async void ListenUdp()
{
_isRunning = true;
while ( _isRunning )
{
try
{
// also important thing!
// when you did not punched hole yet, you must listen incoming packets using "puncher" (later we will close it).
// where you already have p2p connection (and "puncher" closed), use "non-puncher"
UdpClient udpClient = _partnerPublicUdpEndPoint == null ? _udpPuncher : _udpClient;
var receivedResults = await udpClient.ReceiveAsync();
if ( !_isRunning )
{
break;
}
ProcessUdpMessage( receivedResults.Buffer, receivedResults.RemoteEndPoint );
}
catch ( SocketException ex )
{
// do something here...
}
catch ( Exception ex )
{
Console.WriteLine( $"Error: {ex.Message}" );
}
}
}
private static void ProcessUdpMessage( byte[] buffer, IPEndPoint remoteEndPoint )
{
// if server sent partner's endpoinps, we will store it and (IMPORTANT) close "puncher"
if ( UdpProtocol.PeerAddressMessage.TryParse( buffer, out UdpProtocol.PeerAddressMessage peerAddressMessage ) )
{
Console.WriteLine( " <<< Got response from server" );
_partnerPublicUdpEndPoint = new IPEndPoint( peerAddressMessage.PublicIp, peerAddressMessage.PublicPort );
_partnerLocalUdpEndPoint = new IPEndPoint( peerAddressMessage.LocalIp, peerAddressMessage.LocalPort );
_udpPuncher.Close();
}
// since we got this message we know partner's endpoint for sure,
// and we can "connect" UdpClient to it, so it will work faster
else if ( UdpProtocol.P2PKeepAliveMessage.TryParse( buffer ) )
{
Console.WriteLine( $" IT WORKS!!! WOW!!! [ {remoteEndPoint.Address} : {remoteEndPoint.Port} ]" );
_extraUdpClientConnected = true;
_extraUdpClient.Connect( remoteEndPoint );
}
else
{
Console.WriteLine( "???" );
}
}
}
}
Protocol.cs
I'm not sure how good this approach, maybe something like protobuf can do it better
using System;
using System.Linq;
using System.Net;
using System.Text;
namespace HolePunching.Common
{
public static class UdpProtocol
{
public static readonly int GuidLength = 16;
public static readonly int PeerIdLength = 1;
public static readonly int IpLength = 4;
public static readonly int IntLength = 4;
public static readonly byte[] Prefix = { 12, 23, 34, 45 };
private static byte[] JoinBytes( params byte[][] bytes )
{
var result = new byte[bytes.Sum( x => x.Length )];
int pos = 0;
for ( int i = 0; i < bytes.Length; i++ )
{
for ( int j = 0; j < bytes[i].Length; j++, pos++ )
{
result[pos] = bytes[i][j];
}
}
return result;
}
#region Helper extensions
private static bool StartsWith( this byte[] #this, byte[] value, int offset = 0 )
{
if ( #this == null || value == null || #this.Length < offset + value.Length )
{
return false;
}
for ( int i = 0; i < value.Length; i++ )
{
if ( #this[i + offset] < value[i] )
{
return false;
}
}
return true;
}
private static byte[] ToUnicodeBytes( this string #this )
{
return Encoding.Unicode.GetBytes( #this );
}
private static byte[] Take( this byte[] #this, int offset, int length )
{
return #this.Skip( offset ).Take( length ).ToArray();
}
public static bool IsSuitableUdpMessage( this byte[] #this )
{
return #this.StartsWith( Prefix );
}
public static int GetInt( this byte[] #this )
{
if ( #this.Length != 4 )
throw new ArgumentException( "Byte array must be exactly 4 bytes to be convertible to uint." );
return ( ( ( #this[0] << 8 ) + #this[1] << 8 ) + #this[2] << 8 ) + #this[3];
}
public static byte[] ToByteArray( this int value )
{
return new[]
{
(byte)(value >> 24),
(byte)(value >> 16),
(byte)(value >> 8),
(byte)value
};
}
#endregion
#region Messages
public abstract class UdpMessage
{
public byte[] Data { get; }
protected UdpMessage( byte[] data )
{
Data = data;
}
}
public class UdpInfoMessage : UdpMessage
{
private static readonly byte[] MessagePrefix = { 41, 57 };
private static readonly int MessageLength = Prefix.Length + MessagePrefix.Length + PeerIdLength + IpLength + IntLength;
public byte Id { get; }
public IPAddress LocalIp { get; }
public int LocalPort { get; }
private UdpInfoMessage( byte[] data, byte id, IPAddress localIp, int localPort )
: base( data )
{
Id = id;
LocalIp = localIp;
LocalPort = localPort;
}
public static UdpInfoMessage GetMessage( byte id, IPAddress localIp, int localPort )
{
var data = JoinBytes( Prefix, MessagePrefix, new[] { id }, localIp.GetAddressBytes(), localPort.ToByteArray() );
return new UdpInfoMessage( data, id, localIp, localPort );
}
public static bool TryParse( byte[] data, out UdpInfoMessage message )
{
message = null;
if ( !data.StartsWith( Prefix ) )
return false;
if ( !data.StartsWith( MessagePrefix, Prefix.Length ) )
return false;
if ( data.Length != MessageLength )
return false;
int index = Prefix.Length + MessagePrefix.Length;
byte id = data[index];
index += PeerIdLength;
byte[] localIpBytes = data.Take( index, IpLength );
var localIp = new IPAddress( localIpBytes );
index += IpLength;
byte[] localPortBytes = data.Take( index, IntLength );
int localPort = localPortBytes.GetInt();
message = new UdpInfoMessage( data, id, localIp, localPort );
return true;
}
}
public class PeerAddressMessage : UdpMessage
{
private static readonly byte[] MessagePrefix = { 36, 49 };
private static readonly int MessageLength = Prefix.Length + MessagePrefix.Length + PeerIdLength + ( IpLength + IntLength ) * 2;
public byte Id { get; }
public IPAddress PublicIp { get; }
public int PublicPort { get; }
public IPAddress LocalIp { get; }
public int LocalPort { get; }
private PeerAddressMessage( byte[] data, byte id, IPAddress publicIp, int publicPort, IPAddress localIp, int localPort )
: base( data )
{
Id = id;
PublicIp = publicIp;
PublicPort = publicPort;
LocalIp = localIp;
LocalPort = localPort;
}
public static PeerAddressMessage GetMessage( byte id, IPAddress publicIp, int publicPort, IPAddress localIp, int localPort )
{
var data = JoinBytes( Prefix, MessagePrefix, new[] { id },
publicIp.GetAddressBytes(), publicPort.ToByteArray(),
localIp.GetAddressBytes(), localPort.ToByteArray() );
return new PeerAddressMessage( data, id, publicIp, publicPort, localIp, localPort );
}
public static bool TryParse( byte[] data, out PeerAddressMessage message )
{
message = null;
if ( !data.StartsWith( Prefix ) )
return false;
if ( !data.StartsWith( MessagePrefix, Prefix.Length ) )
return false;
if ( data.Length != MessageLength )
return false;
int index = Prefix.Length + MessagePrefix.Length;
byte id = data[index];
index += PeerIdLength;
byte[] publicIpBytes = data.Take( index, IpLength );
var publicIp = new IPAddress( publicIpBytes );
index += IpLength;
byte[] publicPortBytes = data.Take( index, IntLength );
int publicPort = publicPortBytes.GetInt();
index += IntLength;
byte[] localIpBytes = data.Take( index, IpLength );
var localIp = new IPAddress( localIpBytes );
index += IpLength;
byte[] localPortBytes = data.Take( index, IntLength );
int localPort = localPortBytes.GetInt();
message = new PeerAddressMessage( data, id, publicIp, publicPort, localIp, localPort );
return true;
}
}
public class P2PKeepAliveMessage : UdpMessage
{
private static readonly byte[] MessagePrefix = { 11, 19 };
private static P2PKeepAliveMessage _message;
private P2PKeepAliveMessage( byte[] data )
: base( data )
{
}
public static bool TryParse( byte[] data )
{
if ( !data.StartsWith( Prefix ) )
return false;
if ( !data.StartsWith( MessagePrefix, Prefix.Length ) )
return false;
return true;
}
public static P2PKeepAliveMessage GetMessage()
{
if ( _message == null )
{
var data = JoinBytes( Prefix, MessagePrefix );
_message = new P2PKeepAliveMessage( data );
}
return _message;
}
}
#endregion
}
}

parsing an url in c# without protocol instead with port number

When I use Uri class to parse a url like e.g. client-lb.dropbox.com:443, Uri class couldn't parse the value and get the correct results such as url.Port : 443, url.Host = client-lb.dropbox.com.
var urlValue = "client-lb.dropbox.com:443";
var url = new Uri(urlValue);
Console.WriteLine("Host : {0}, Port {1} ", url.Host, url.Port);
Result :
Host : , Port -1
How can i fix this using Uri class ? Any suggestions are appreciated ..
var urlValue = "http://client-lb.dropbox.com:443";
var url = new Uri(urlValue);
Console.WriteLine("Host : {0}, Port {1} ", url.Host, url.Port);
Response:
Host : client-lb.dropbox.com, Port 443
Url should look like [protocol]://hostname:[port], by default for https port is 443 and for http port is 80.
Protocols have information about their default port. But port cannot associate them with a protocol.
I have re-written this parser and thanks to Trotinet project for the base implementation.
private static void ParseUrl(string url)
{
string host = null;
var port = 123;
var prefix = 0;
if (url.Contains("://"))
{
if (url.StartsWith("http://"))
prefix = 7; // length of "http://"
else if (url.StartsWith("https://"))
{
prefix = 8; // length of "https://"
port = 443;
}
else
{
throw new Exception("Expected scheme missing or unsupported");
}
}
var slash = url.IndexOf('/', prefix);
string authority = null;
if (slash == -1)
{
// case 1
authority = url.Substring(prefix);
}
else
if (slash > 0)
// case 2
authority = url.Substring(prefix, slash - prefix);
if (authority != null)
{
Console.WriteLine("Auth is " + authority);
// authority is either:
// a) hostname
// b) hostname:
// c) hostname:port
var c = authority.IndexOf(':');
if (c < 0)
// case a)
host = authority;
else
if (c == authority.Length - 1)
// case b)
host = authority.TrimEnd('/');
else
{
// case c)
host = authority.Substring(0, c);
port = int.Parse(authority.Substring(c + 1));
}
}
Console.WriteLine("The Host {0} and Port : {1}", host, port);
}

c# : how to implement server proxy support https?

I am trying to redirect all requests from specific port (all of request come from Web Browser) to hostname of request as code
1 - read all receive data from request
while (recvRequest)
{
this.clientSocket.Receive(requestBuffer);
string fromByte = ASCIIEncoding.ASCII.GetString(requestBuffer);
requestPayload += fromByte;
if (requestPayload.EndsWith(EOL + EOL))
{
recvRequest = false;
}
}
2 - parse Query ( get host and Port and request type )
HeaderFields = ParseQuery(requestPayload);
if (HeaderFields == null || !HeaderFields.ContainsKey("Host"))
{
return;
}
int Port;
string Host;
int Ret;
3 - if type equal CONNECT
if (HttpRequestType.ToUpper().Equals("CONNECT"))
{ //HTTPS
Ret = RequestedPath.IndexOf(":");
if (Ret >= 0)
{
Host = RequestedPath.Substring(0, Ret);
if (RequestedPath.Length > Ret + 1)
Port = int.Parse(RequestedPath.Substring(Ret + 1));
else
Port = 443;
}
else
{
Host = RequestedPath;
Port = 443;
}
}
4 - if type equal post or get
else
{ //Normal HTTP
Ret = ((string)HeaderFields["Host"]).IndexOf(":");
if (Ret > 0)
{
Host = ((string)HeaderFields["Host"]).Substring(0, Ret);
Port = int.Parse(((string)HeaderFields["Host"]).Substring(Ret + 1));
}
else
{
Host = (string)HeaderFields["Host"];
Port = 80;
}
if (HttpRequestType.ToUpper().Equals("POST"))
{
int index = requestPayload.IndexOf("\r\n\r\n");
m_HttpPost = requestPayload.Substring(index + 4);
}
}
requestPayload = "";
foreach (string line in requestLines)
{
requestPayload += line;
requestPayload += EOL;
}
5 - connect to host
Socket destServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
destServerSocket.Connect(Host, Port);
//State 2: Sending New Request Information to Destination Server and Relay Response to Client
destServerSocket.Send(ASCIIEncoding.ASCII.GetBytes(requestPayload));
//Console.WriteLine("Begin Receiving Response...");
6 - send Receiving Response to client
while (destServerSocket.Receive(responseBuffer) != 0)
{
Console.Write(ASCIIEncoding.ASCII.GetString(responseBuffer));
this.clientSocket.Send(responseBuffer);
}
destServerSocket.Disconnect(false);
destServerSocket.Dispose();
this.clientSocket.Disconnect(false);
this.clientSocket.Dispose();
but when i open email do not work correctly

Find the next TCP port in .NET

I want to create a new net.tcp://localhost:x/Service endpoint for a WCF service call, with a dynamically assigned new open TCP port.
I know that TcpClient will assign a new client side port when I open a connection to a given server.
Is there a simple way to find the next open TCP port in .NET?
I need the actual number, so that I can build the string above. 0 does not work, since I need to pass that string to another process, so that I can call back on that new channel.
Here is what I was looking for:
static int FreeTcpPort()
{
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
int port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
Use a port number of 0. The TCP stack will allocate the next free one.
It's a solution comparable to the accepted answer of TheSeeker. Though I think it's more readable:
using System;
using System.Net;
using System.Net.Sockets;
private static readonly IPEndPoint DefaultLoopbackEndpoint = new IPEndPoint(IPAddress.Loopback, port: 0);
public static int GetAvailablePort()
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Bind(DefaultLoopbackEndpoint);
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
}
I found the following code from Selenium.WebDriver DLL
Namespace: OpenQA.Selenium.Internal
Class: PortUtility
public static int FindFreePort()
{
int port = 0;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 0);
socket.Bind(localEP);
localEP = (IPEndPoint)socket.LocalEndPoint;
port = localEP.Port;
}
finally
{
socket.Close();
}
return port;
}
If you just want to give a starting port, and let it return to you the next TCP port available, use code like this:
public static int GetAvailablePort(int startingPort)
{
var portArray = new List<int>();
var properties = IPGlobalProperties.GetIPGlobalProperties();
// Ignore active connections
var connections = properties.GetActiveTcpConnections();
portArray.AddRange(from n in connections
where n.LocalEndPoint.Port >= startingPort
select n.LocalEndPoint.Port);
// Ignore active tcp listners
var endPoints = properties.GetActiveTcpListeners();
portArray.AddRange(from n in endPoints
where n.Port >= startingPort
select n.Port);
// Ignore active UDP listeners
endPoints = properties.GetActiveUdpListeners();
portArray.AddRange(from n in endPoints
where n.Port >= startingPort
select n.Port);
portArray.Sort();
for (var i = startingPort; i < UInt16.MaxValue; i++)
if (!portArray.Contains(i))
return i;
return 0;
}
First open the port, then give the correct port number to the other process.
Otherwise it is still possible that some other process opens the port first and you still have a different one.
Here's a more abbreviated way to implement this if you want to find the next available TCP port within a given range:
private int GetNextUnusedPort(int min, int max)
{
if (max < min)
throw new ArgumentException("Max cannot be less than min.");
var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
var usedPorts =
ipProperties.GetActiveTcpConnections()
.Where(connection => connection.State != TcpState.Closed)
.Select(connection => connection.LocalEndPoint)
.Concat(ipProperties.GetActiveTcpListeners())
.Concat(ipProperties.GetActiveUdpListeners())
.Select(endpoint => endpoint.Port)
.ToArray();
var firstUnused =
Enumerable.Range(min, max - min)
.Where(port => !usedPorts.Contains(port))
.Select(port => new int?(port))
.FirstOrDefault();
if (!firstUnused.HasValue)
throw new Exception($"All local TCP ports between {min} and {max} are currently in use.");
return firstUnused.Value;
}
If you want to get a free port in a specific range in order to use it as local port / end point:
private int GetFreePortInRange(int PortStartIndex, int PortEndIndex)
{
DevUtils.LogDebugMessage(string.Format("GetFreePortInRange, PortStartIndex: {0} PortEndIndex: {1}", PortStartIndex, PortEndIndex));
try
{
IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
IPEndPoint[] tcpEndPoints = ipGlobalProperties.GetActiveTcpListeners();
List<int> usedServerTCpPorts = tcpEndPoints.Select(p => p.Port).ToList<int>();
IPEndPoint[] udpEndPoints = ipGlobalProperties.GetActiveUdpListeners();
List<int> usedServerUdpPorts = udpEndPoints.Select(p => p.Port).ToList<int>();
TcpConnectionInformation[] tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections();
List<int> usedPorts = tcpConnInfoArray.Where(p=> p.State != TcpState.Closed).Select(p => p.LocalEndPoint.Port).ToList<int>();
usedPorts.AddRange(usedServerTCpPorts.ToArray());
usedPorts.AddRange(usedServerUdpPorts.ToArray());
int unusedPort = 0;
for (int port = PortStartIndex; port < PortEndIndex; port++)
{
if (!usedPorts.Contains(port))
{
unusedPort = port;
break;
}
}
DevUtils.LogDebugMessage(string.Format("Local unused Port:{0}", unusedPort.ToString()));
if (unusedPort == 0)
{
DevUtils.LogErrorMessage("Out of ports");
throw new ApplicationException("GetFreePortInRange, Out of ports");
}
return unusedPort;
}
catch (Exception ex)
{
string errorMessage = ex.Message;
DevUtils.LogErrorMessage(errorMessage);
throw;
}
}
private int GetLocalFreePort()
{
int hemoStartLocalPort = int.Parse(DBConfig.GetField("Site.Config.hemoStartLocalPort"));
int hemoEndLocalPort = int.Parse(DBConfig.GetField("Site.Config.hemoEndLocalPort"));
int localPort = GetFreePortInRange(hemoStartLocalPort, hemoEndLocalPort);
DevUtils.LogDebugMessage(string.Format("Local Free Port:{0}", localPort.ToString()));
return localPort;
}
public void Connect(string host, int port)
{
try
{
// Create socket
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
var localPort = GetLocalFreePort();
// Create an endpoint for the specified IP on any port
IPEndPoint bindEndPoint = new IPEndPoint(IPAddress.Any, localPort);
// Bind the socket to the endpoint
socket.Bind(bindEndPoint);
// Connect to host
socket.Connect(IPAddress.Parse(host), port);
socket.Dispose();
}
catch (SocketException ex)
{
// Get the error message
string errorMessage = ex.Message;
DevUtils.LogErrorMessage(errorMessage);
}
}
public void Connect2(string host, int port)
{
try
{
// Create socket
var localPort = GetLocalFreePort();
// Create an endpoint for the specified IP on any port
IPEndPoint bindEndPoint = new IPEndPoint(IPAddress.Any, localPort);
var client = new TcpClient(bindEndPoint);
//client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); //will release port when done
// Connect to the host
client.Connect(IPAddress.Parse(host), port);
client.Close();
}
catch (SocketException ex)
{
// Get the error message
string errorMessage = ex.Message;
DevUtils.LogErrorMessage(errorMessage);
}
}

Categories