5
votes

I'd like to write a fairly simple client-server network app. I only use a pure IPv4 network, but it would be nice to future-proof my code. I'll probably be using TcpListener/TcpClient, because preliminary investigation of WCF reveals it to be overly complex to set up and hard to grok.

For the client side, does .NET provide a facility to automatically decode a string that contains an IPv4 or IPv6 address (where the IPv4 address includes a port number)? Bonus points if it can resolve a domain name.

For the server side, I heard IPv6 doesn't use port numbers, so what's the equivalent of the port to listen on, and is there a standard way to differentiate a IPv4 port number string from the IPv6 equivalent? Never mind, IPv6 services have 16-bit port numbers just like IPv4 ones.

4

4 Answers

6
votes

Yep System.Net.IPAddress

IPAddress.Parse( "fe80::21c:42ff:fe00:8%vnic0" );
IPAddress.Parse( "127.0.0.1" );

And to test for IPv4 or v6

if( IPAddress.Parse(...).AddressFamily == AddressFamily.InterNetwork )
  // IPv4 address
2
votes

System.Net.IPAddress can be used to parse strings representing valid IPv4 and IPv6 addresses. System.Net.Dns can be used to resolve hostnames and addresses on a network.

For both,

using System.Net;
2
votes

@Qwertie said:

Since IPv6 addresses also contain colons, I wonder what the proper way is to decode a IPv6 address or IPv4 address that contains a port number.

As others have noted, a port number is not part of an IP address (either IPv4 or IPv6). IP addresses are atomic, unsigned integers (IPv4 is 32-bit, IPv6 is 128-bit). When represented in character form (such as a URI), they may be combined with a port number (and other information). Within a URI, the host and port are [can be] a part of the authority portion of a URI.

URIs may be parsed into their constituent parts using System.Uri. The Authority portion of a URI is composed of the following properties: Host, Port (and, optionally, and deprecated, the UserInfo subcomponent, consisting of username and password). The property HostNameType will tell you what kind of hostname you've got, a value of the enum UriHostNameType: Dns, Ipv4, Ipv6 or something else.

You can also parse a URI into its component parts using the regular expression defined in Appendix B of RFC 3986:

Appendix B.  Parsing a URI Reference with a Regular Expression
As the "first-match-wins" algorithm is identical to the "greedy" disambiguation method used by POSIX regular expressions, it is natural and commonplace to use a regular expression for parsing the potential five components of a URI reference.
The following line is the regular expression for breaking-down a well-formed URI reference into its components.
^(([^:/?#]+):)?(//([^/?#]))?([^?#])(\?([^#]))?(#(.))? 12 3 4 5 6 7 8 9
The numbers in the second line above are only to assist readability; they indicate the reference points for each subexpression (i.e., each paired parenthesis). We refer to the value matched for subexpression <n> as $<n>. For example, matching the above expression to
http://www.ics.uci.edu/pub/ietf/uri/#Related
results in the following subexpression matches:
$1 = http: $2 = http $3 = //www.ics.uci.edu $4 = www.ics.uci.edu $5 = /pub/ietf/uri/ $6 = $7 = $8 = #Related $9 = Related
where <undefined> indicates that the component is not present, as is the case for the query component in the above example. Therefore, we can determine the value of the five components as
scheme = $2 authority = $4 path = $5 query = $7 fragment = $9
Going in the opposite direction, we can recreate a URI reference from its components by using the algorithm of Section 5.3.

Note though that this regular expression seems to be slightly incorrect. According to the errata for RFC 3986 and its antecedent, RFC 2396:

Errata ID: 1933
Status: Verified Type: Technical Reported By: Skip Geel Date Reported: 2009-10-25 Verifier Name: Peter Saint-Andre Date Verified: 2010-11-11
Section appendix B says:
^(([^:/?#]+):)?(//([^/?#]))?([^?#])(\?([^#]))?(#(.))?
It should say:
/^(([^:\/?#]+):)?(\/\/([^\/?#]))?([^?#])(\?([^#]))?(#(.))?/
Notes:
A Regular Expression is delimited by the slash ("/"). Within it a slash should be preceded by a back-slash ("\").
Peter: This also applies to RFC 3986.
0
votes

As Paul mentioned, a plain IP Address with no port number can be parsed by IPAddress.Parse(). However, if there is a port number and/or hostname (12.34.56.78:90 or www.example.com:5555), a different approach is needed. If you want to use TcpClient to connect, this function will do so:

public static TcpClient Connect(string ipAndPort, int defaultPort)
{
    if (ipAndPort.Contains("/"))
        throw new FormatException("Input should only be an IP address and port");

    // Uri requires a prefix such as "http://", "ftp://" or even "foo://".
    // Oddly, Uri accepts the prefix "//" UNLESS there is a port number.
    Uri uri = new Uri("tcp://" + ipAndPort);

    string ipOrDomain = uri.Host;
    int port = uri.Port != -1 ? uri.Port : defaultPort;
    return new TcpClient(ipOrDomain, port);
}

The defaultPort parameter specifies the port to use if the input string doesn't have one. For example:

using (NetworkStream s = Connect("google.com", 80).GetStream())
{
    byte[] line = Encoding.UTF8.GetBytes("GET / HTTP/1.0\r\n\r\n");
    s.Write(line, 0, line.Length);

    int b;
    while ((b = s.ReadByte()) != -1)
        Console.Write((char)b);
}

To decode the address without connecting to it (e.g. to verify that it is valid, or because you are connecting via an API that requires an IP address), this method will do so (and optionally perform DNS lookup):

public static IPAddress Resolve(string ipAndPort, ref int port, bool resolveDns)
{
    if (ipAndPort.Contains("/"))
        throw new FormatException("Input address should only contain an IP address and port");

    Uri uri = new Uri("tcp://" + ipAndPort);

    if (uri.Port != -1)
        port = uri.Port;
    if (uri.HostNameType == UriHostNameType.IPv4 || uri.HostNameType == UriHostNameType.IPv6)
        return IPAddress.Parse(uri.Host);
    else if (resolveDns)
        return Dns.GetHostAddresses(uri.Host)[0];
    else
        return null;
}

Curiously, Dns.GetHostAddresses can return multiple addresses. I asked about it, and apparently it's okay to simply take the first address.

An exception will be raised if there is a syntax error or a problem resolving the domain name (FormatException or SocketException). If the user specified a domain name but resolveDns==false, this method returns null.