a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
470 lines
12 KiB
C#
470 lines
12 KiB
C#
//
|
|
// System.Net.IPv6Address.cs
|
|
//
|
|
// Author:
|
|
// Lawrence Pit (loz@cable.a2000.nl)
|
|
//
|
|
// Note I: This class is not defined in the specs of .Net
|
|
//
|
|
// Note II : The name of this class is perhaps unfortunate as it turns
|
|
// out that in ms.net there's an internal class called
|
|
// IPv6Address in namespace System.
|
|
//
|
|
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
|
|
using System;
|
|
using System.Globalization;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace System.Net {
|
|
|
|
/// <remarks>
|
|
/// Encapsulates an IPv6 Address.
|
|
/// See RFC 2373 for more info on IPv6 addresses.
|
|
/// </remarks>
|
|
[Serializable]
|
|
internal class IPv6Address {
|
|
private ushort [] address;
|
|
private int prefixLength;
|
|
private long scopeId = 0;
|
|
|
|
public static readonly IPv6Address Loopback = IPv6Address.Parse ("::1");
|
|
public static readonly IPv6Address Unspecified = IPv6Address.Parse ("::");
|
|
|
|
public IPv6Address (ushort [] addr)
|
|
{
|
|
if (addr == null)
|
|
throw new ArgumentNullException ("addr");
|
|
if (addr.Length != 8)
|
|
throw new ArgumentException ("addr");
|
|
address = addr;
|
|
}
|
|
|
|
public IPv6Address (ushort [] addr, int prefixLength) : this (addr)
|
|
{
|
|
if (prefixLength < 0 || prefixLength > 128)
|
|
throw new ArgumentException ("prefixLength");
|
|
this.prefixLength = prefixLength;
|
|
}
|
|
|
|
public IPv6Address (ushort [] addr, int prefixLength, int scopeId) : this (addr, prefixLength)
|
|
{
|
|
this.scopeId = scopeId;
|
|
}
|
|
|
|
public static IPv6Address Parse (string ipString)
|
|
{
|
|
if (ipString == null)
|
|
throw new ArgumentNullException ("ipString");
|
|
|
|
IPv6Address result;
|
|
if (TryParse (ipString, out result))
|
|
return result;
|
|
throw new FormatException ("Not a valid IPv6 address");
|
|
}
|
|
|
|
static int Fill (ushort [] addr, string ipString)
|
|
{
|
|
int p = 0;
|
|
int pdigits = 0;
|
|
int slot = 0;
|
|
|
|
if (ipString.Length == 0)
|
|
return 0;
|
|
|
|
// Catch double uses of ::
|
|
if (ipString.IndexOf ("::", StringComparison.Ordinal) != -1)
|
|
return -1;
|
|
|
|
for (int i = 0; i < ipString.Length; i++){
|
|
char c = ipString [i];
|
|
int n;
|
|
|
|
if (c == ':'){
|
|
// Leading : is not allowed.
|
|
if (i == 0)
|
|
return -1;
|
|
|
|
// Trailing : is not allowed.
|
|
if (i == ipString.Length-1)
|
|
return -1;
|
|
|
|
if (slot == 8)
|
|
return -1;
|
|
|
|
addr [slot++] = (ushort) p;
|
|
p = 0;
|
|
pdigits = 0;
|
|
continue;
|
|
}
|
|
pdigits++;
|
|
if (pdigits > 4)
|
|
return -1;
|
|
if ('0' <= c && c <= '9')
|
|
n = (int) (c - '0');
|
|
else if ('a' <= c && c <= 'f')
|
|
n = (int) (c - 'a' + 10);
|
|
else if ('A' <= c && c <= 'F')
|
|
n = (int) (c - 'A' + 10);
|
|
else
|
|
return -1;
|
|
p = (p << 4) + n;
|
|
if (p > UInt16.MaxValue)
|
|
return -1;
|
|
}
|
|
|
|
if (slot == 8)
|
|
return -1;
|
|
|
|
addr [slot++] = (ushort) p;
|
|
|
|
return slot;
|
|
}
|
|
|
|
static bool TryParse (string prefix, out int res)
|
|
{
|
|
return Int32.TryParse (prefix, NumberStyles.Integer, CultureInfo.InvariantCulture, out res);
|
|
}
|
|
|
|
public static bool TryParse (string ipString, out IPv6Address result)
|
|
{
|
|
result = null;
|
|
if (ipString == null)
|
|
return false;
|
|
|
|
if (ipString.Length > 2 &&
|
|
ipString [0] == '[' &&
|
|
ipString [ipString.Length - 1] == ']')
|
|
ipString = ipString.Substring (1, ipString.Length - 2);
|
|
|
|
if (ipString.Length < 2)
|
|
return false;
|
|
|
|
int prefixLen = 0;
|
|
int scopeId = 0;
|
|
int pos = ipString.LastIndexOf ('/');
|
|
if (pos != -1) {
|
|
string prefix = ipString.Substring (pos + 1);
|
|
if (!TryParse (prefix , out prefixLen))
|
|
prefixLen = -1;
|
|
if (prefixLen < 0 || prefixLen > 128)
|
|
return false;
|
|
ipString = ipString.Substring (0, pos);
|
|
} else {
|
|
pos = ipString.LastIndexOf ('%');
|
|
if (pos != -1) {
|
|
string prefix = ipString.Substring (pos + 1);
|
|
if (!TryParse (prefix, out scopeId))
|
|
scopeId = 0;
|
|
ipString = ipString.Substring (0, pos);
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point the prefix/suffixes have been removed
|
|
// and we only have to deal with the ipv4 or ipv6 addressed
|
|
//
|
|
ushort [] addr = new ushort [8];
|
|
|
|
//
|
|
// Is there an ipv4 address at the end?
|
|
//
|
|
int pos2 = ipString.LastIndexOf (':');
|
|
if (pos2 == -1)
|
|
return false;
|
|
|
|
int slots = 0;
|
|
if (pos2 < (ipString.Length - 1)) {
|
|
string ipv4Str = ipString.Substring (pos2 + 1);
|
|
if (ipv4Str.IndexOf ('.') != -1) {
|
|
IPAddress ip;
|
|
|
|
if (!IPAddress.TryParse (ipv4Str, out ip))
|
|
return false;
|
|
|
|
long a = ip.InternalIPv4Address;
|
|
addr [6] = (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff)));
|
|
addr [7] = (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff)));
|
|
if (pos2 > 0 && ipString [pos2 - 1] == ':')
|
|
ipString = ipString.Substring (0, pos2 + 1);
|
|
else
|
|
ipString = ipString.Substring (0, pos2);
|
|
slots = 2;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Only an ipv6 block remains, either:
|
|
// "hexnumbers::hexnumbers", "hexnumbers::" or "hexnumbers"
|
|
//
|
|
int c = ipString.IndexOf ("::", StringComparison.Ordinal);
|
|
if (c != -1){
|
|
int right_slots = Fill (addr, ipString.Substring (c+2));
|
|
if (right_slots == -1){
|
|
return false;
|
|
}
|
|
|
|
if (right_slots + slots > 8){
|
|
return false;
|
|
}
|
|
|
|
int d = 8-slots-right_slots;
|
|
for (int i = right_slots; i > 0; i--){
|
|
addr [i+d-1] = addr [i-1];
|
|
addr [i-1] = 0;
|
|
}
|
|
|
|
int left_slots = Fill (addr, ipString.Substring (0, c));
|
|
if (left_slots == -1)
|
|
return false;
|
|
|
|
if (left_slots + right_slots + slots > 7)
|
|
return false;
|
|
} else {
|
|
if (Fill (addr, ipString) != 8-slots)
|
|
return false;
|
|
}
|
|
|
|
result = new IPv6Address (addr, prefixLen, scopeId);
|
|
return true;
|
|
}
|
|
|
|
public ushort [] Address {
|
|
get { return address; }
|
|
}
|
|
|
|
public int PrefixLength {
|
|
get { return this.prefixLength; }
|
|
}
|
|
|
|
public long ScopeId {
|
|
get {
|
|
return scopeId;
|
|
}
|
|
set {
|
|
scopeId = value;
|
|
}
|
|
}
|
|
|
|
public ushort this [int index] {
|
|
get { return address [index]; }
|
|
}
|
|
|
|
public AddressFamily AddressFamily {
|
|
get { return AddressFamily.InterNetworkV6; }
|
|
}
|
|
|
|
public static bool IsLoopback (IPv6Address addr)
|
|
{
|
|
if (addr.address [7] != 1)
|
|
return false;
|
|
|
|
int x = addr.address [6] >> 8;
|
|
if (x != 0x7f && x != 0)
|
|
return false;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
if (addr.address [i] != 0)
|
|
return false;
|
|
}
|
|
|
|
if (addr.address [5] != 0 && addr.address [5] != 0xffff)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private static ushort SwapUShort (ushort number)
|
|
{
|
|
return (ushort) ( ((number >> 8) & 0xFF) + ((number << 8) & 0xFF00) );
|
|
}
|
|
|
|
// Convert the address into a format expected by the IPAddress (long) ctor
|
|
// This needs to be unsigned to satisfy the '> 1' test in IsIPv4Compatible()
|
|
private uint AsIPv4Int ()
|
|
{
|
|
return (uint)(SwapUShort (address [7]) << 16) + SwapUShort (address [6]);
|
|
}
|
|
|
|
public bool IsIPv4Compatible ()
|
|
{
|
|
for (int i = 0; i < 6; i++)
|
|
if (address [i] != 0)
|
|
return false;
|
|
/* MS .net only seems to format the last 4
|
|
* bytes as an IPv4 address if address[6] is
|
|
* non-zero
|
|
*/
|
|
if (address[6] == 0)
|
|
return false;
|
|
return (AsIPv4Int () > 1);
|
|
}
|
|
|
|
public bool IsIPv4Mapped ()
|
|
{
|
|
for (int i = 0; i < 5; i++)
|
|
if (address [i] != 0)
|
|
return false;
|
|
/* MS .net only seems to format the last 4
|
|
* bytes as an IPv4 address if address[6] is
|
|
* non-zero
|
|
*/
|
|
if (address[6] == 0)
|
|
return false;
|
|
|
|
return address [5] == 0xffff;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overrides System.Object.ToString to return
|
|
/// this object rendered in a canonicalized notation
|
|
/// </summary>
|
|
public override string ToString ()
|
|
{
|
|
StringBuilder s = new StringBuilder ();
|
|
|
|
|
|
if(IsIPv4Compatible() || IsIPv4Mapped())
|
|
{
|
|
s.Append("::");
|
|
|
|
if(IsIPv4Mapped())
|
|
s.Append("ffff:");
|
|
|
|
s.Append(new IPAddress( AsIPv4Int ()).ToString ());
|
|
|
|
return s.ToString ();
|
|
}
|
|
else
|
|
{
|
|
int bestChStart = -1; // Best chain start
|
|
int bestChLen = 0; // Best chain length
|
|
int currChLen = 0; // Current chain length
|
|
|
|
// Looks for the longest zero chain
|
|
for (int i=0; i<8; i++)
|
|
{
|
|
if (address[i] != 0)
|
|
{
|
|
if ((currChLen > bestChLen)
|
|
&& (currChLen > 1))
|
|
{
|
|
bestChLen = currChLen;
|
|
bestChStart = i - currChLen;
|
|
}
|
|
currChLen = 0;
|
|
}
|
|
else
|
|
currChLen++;
|
|
}
|
|
if ((currChLen > bestChLen)
|
|
&& (currChLen > 1))
|
|
{
|
|
bestChLen = currChLen;
|
|
bestChStart = 8 - currChLen;
|
|
}
|
|
|
|
// makes the string
|
|
if (bestChStart == 0)
|
|
s.Append(":");
|
|
for (int i=0; i<8; i++)
|
|
{
|
|
if (i == bestChStart)
|
|
{
|
|
s.Append (":");
|
|
i += (bestChLen - 1);
|
|
continue;
|
|
}
|
|
s.AppendFormat("{0:x}", address [i]);
|
|
if (i < 7) s.Append (':');
|
|
}
|
|
}
|
|
if (scopeId != 0)
|
|
s.Append ('%').Append (scopeId);
|
|
return s.ToString ();
|
|
}
|
|
|
|
public string ToString (bool fullLength)
|
|
{
|
|
if (!fullLength)
|
|
return ToString ();
|
|
|
|
StringBuilder sb = new StringBuilder ();
|
|
for (int i=0; i < address.Length - 1; i++) {
|
|
sb.AppendFormat ("{0:X4}:", address [i]);
|
|
}
|
|
sb.AppendFormat ("{0:X4}", address [address.Length - 1]);
|
|
return sb.ToString ();
|
|
}
|
|
|
|
/// <returns>
|
|
/// Whether both objects are equal.
|
|
/// </returns>
|
|
public override bool Equals (object other)
|
|
{
|
|
System.Net.IPv6Address ipv6 = other as System.Net.IPv6Address;
|
|
if (ipv6 != null) {
|
|
for (int i = 0; i < 8; i++)
|
|
if (this.address [i] != ipv6.address [i])
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
System.Net.IPAddress ipv4 = other as System.Net.IPAddress;
|
|
if (ipv4 != null) {
|
|
for (int i = 0; i < 5; i++)
|
|
if (address [i] != 0)
|
|
return false;
|
|
|
|
if (address [5] != 0 && address [5] != 0xffff)
|
|
return false;
|
|
|
|
long a = ipv4.InternalIPv4Address;
|
|
if (address [6] != (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff))) ||
|
|
address [7] != (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff))))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override int GetHashCode ()
|
|
{
|
|
return Hash (((((int) address [0]) << 16) + address [1]),
|
|
((((int) address [2]) << 16) + address [3]),
|
|
((((int) address [4]) << 16) + address [5]),
|
|
((((int) address [6]) << 16) + address [7]));
|
|
}
|
|
|
|
private static int Hash (int i, int j, int k, int l)
|
|
{
|
|
return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25);
|
|
}
|
|
}
|
|
}
|