forked from ln-dotnet/ln.type
336 lines
8.6 KiB
C#
336 lines
8.6 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using ln.type.arithmetics;
|
|
using System.Net;
|
|
|
|
namespace ln.type
|
|
{
|
|
public class IPv6
|
|
{
|
|
public static readonly IPv6 ANY = new IPv6(new byte[17]);
|
|
public static readonly IPv6 V4Space = Parse("::ffff:0:0/96");
|
|
public static readonly IPv6 Loopback = Parse("::1");
|
|
|
|
readonly UInt16[] value;
|
|
readonly UInt32 mask;
|
|
|
|
public uint Netmask => mask;
|
|
|
|
public IPv6()
|
|
{
|
|
value = new UInt16[8];
|
|
mask = 0;
|
|
}
|
|
public IPv6(uint netmask)
|
|
{
|
|
value = new UInt16[8];
|
|
mask = netmask;
|
|
}
|
|
public IPv6(IPv6 ip, uint netmask) : this(ip.value, netmask) { }
|
|
public IPv6(ushort[] words, uint netmask)
|
|
{
|
|
if (words.Length > 8)
|
|
throw new ArgumentOutOfRangeException(nameof(words));
|
|
|
|
value = words.Slice(0, 8);
|
|
mask = netmask;
|
|
}
|
|
|
|
public IPv6(byte[] bytes,uint netmask)
|
|
:this(bytes)
|
|
{
|
|
mask = netmask;
|
|
}
|
|
public IPv6(byte[] bytes)
|
|
:this()
|
|
{
|
|
if (bytes.Length == 16)
|
|
{
|
|
for (int n = 0; n < 8; n++)
|
|
{
|
|
value[n] = (ushort)((bytes[(n << 1) + 0] << 8) | bytes[(n << 1) + 1]);
|
|
}
|
|
mask = 128;
|
|
}
|
|
else if (bytes.Length == 17)
|
|
{
|
|
for (int n = 0; n < 8; n++)
|
|
{
|
|
value[n] = (ushort)((bytes[(n << 1) + 0] << 8) | bytes[(n << 1) + 1]);
|
|
}
|
|
mask = bytes[16];
|
|
}
|
|
else if (bytes.Length == 4)
|
|
{
|
|
value[5] = 0xffff;
|
|
value[6] = (ushort)((bytes[0] << 8) | bytes[1]);
|
|
value[7] = (ushort)((bytes[2] << 8) | bytes[3]);
|
|
mask = 96;
|
|
} else
|
|
throw new ArgumentOutOfRangeException(nameof(bytes));
|
|
}
|
|
|
|
public IPv6 Network
|
|
{
|
|
get
|
|
{
|
|
if (mask == 128)
|
|
return this;
|
|
|
|
ushort[] address = value.Slice(0);
|
|
uint steps = mask >> 4;
|
|
uint shift = mask & 0x0F;
|
|
|
|
address[steps] &= (ushort)(0xFFFF << (int)(16 - shift));
|
|
|
|
for (uint n = steps+1; n < 8; n++)
|
|
address[n] = 0;
|
|
|
|
return new IPv6(address, mask);
|
|
}
|
|
}
|
|
|
|
public bool Contains(IPv6 ip)
|
|
{
|
|
if (ip.mask < mask)
|
|
return false;
|
|
|
|
IPv6 ipNetwork = new IPv6(ip, mask).Network;
|
|
return Network.Equals(ipNetwork);
|
|
}
|
|
|
|
public byte[] ToBytes()
|
|
{
|
|
byte[] bytes = new byte[16];
|
|
|
|
for (int n = 0; n < 8; n++)
|
|
{
|
|
bytes[(n << 1) + 1] = (byte)((value[n] >> 0) & 0xff);
|
|
bytes[(n << 1) + 0] = (byte)((value[n] >> 8) & 0xff);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
public byte[] ToCIDRBytes()
|
|
{
|
|
byte[] bytes = new byte[17];
|
|
|
|
for (int n = 0; n < 8; n++)
|
|
{
|
|
bytes[(n << 1) + 1] = (byte)((value[n] >> 0) & 0xff);
|
|
bytes[(n << 1) + 0] = (byte)((value[n] >> 8) & 0xff);
|
|
}
|
|
bytes[16] = mask.GetBytes()[0];
|
|
|
|
return bytes;
|
|
}
|
|
public byte[] ToPackedBytes()
|
|
{
|
|
byte[] bytes = ToBytes();
|
|
if (V4Space.Contains(this))
|
|
return bytes.Slice(12);
|
|
return bytes;
|
|
|
|
}
|
|
|
|
public IEnumerable<IPv6> Split(int splitWidth)
|
|
{
|
|
List<IPv6> splitted = new List<IPv6>();
|
|
|
|
if (128 < (mask + splitWidth))
|
|
throw new ArgumentOutOfRangeException(nameof(splitWidth),"cannot split to negative host identfier length");
|
|
|
|
IPv6 ip = new IPv6(value, (uint)(mask + splitWidth));
|
|
ushort[] increment = Words.SHL(new ushort[] { 1, 0, 0, 0, 0, 0, 0, 0 }, (int)(128 - ip.mask));
|
|
|
|
for (int n=0;n<(1<<splitWidth);n++)
|
|
{
|
|
splitted.Add(ip);
|
|
ip = new IPv6(Words.Add(ip.value.BigEndian(), increment).BigEndian(), ip.mask);
|
|
|
|
}
|
|
|
|
return splitted;
|
|
}
|
|
|
|
|
|
|
|
public string ToString(bool shorten)
|
|
{
|
|
if (!shorten)
|
|
{
|
|
return string.Join(":", value.Select((w) => String.Format("{0:x4}", w)));
|
|
}
|
|
else if (V4Space.Contains(this))
|
|
{
|
|
return string.Format("::ffff:{0}.{1}.{2}.{3}",
|
|
((value[6] >> 8) & 0xFF),
|
|
((value[6] >> 0) & 0xFF),
|
|
((value[7] >> 8) & 0xFF),
|
|
((value[7] >> 0) & 0xFF)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
int zerolen = 0;
|
|
int zeropos = -1;
|
|
|
|
for (int n=0;n<8;n++)
|
|
{
|
|
if (value[n]==0)
|
|
{
|
|
int m;
|
|
for (m = 1; m < (8 - n); m++)
|
|
{
|
|
if (value[n + m] != 0)
|
|
break;
|
|
}
|
|
if (m > zerolen)
|
|
{
|
|
zerolen = m;
|
|
zeropos = n;
|
|
}
|
|
}
|
|
}
|
|
|
|
List<string> words = new List<string>();
|
|
|
|
for (int n = 0; n < 8; n++)
|
|
{
|
|
if (n == zeropos)
|
|
{
|
|
if (n == 0)
|
|
words.Add("");
|
|
if ((n + zerolen)==8)
|
|
words.Add("");
|
|
|
|
words.Add("");
|
|
n += zerolen - 1;
|
|
}
|
|
else
|
|
{
|
|
words.Add(String.Format("{0:x}", value[n]));
|
|
}
|
|
}
|
|
|
|
return string.Join(":", words);
|
|
}
|
|
}
|
|
public override string ToString() => ToString(true);
|
|
|
|
public String ToCIDR()
|
|
{
|
|
return String.Format("{0}/{1}", ToString(true), mask);
|
|
}
|
|
|
|
public static IPv6 Parse(string source)
|
|
{
|
|
int netmask = 128;
|
|
ushort[] address = new ushort[8];
|
|
|
|
int slash = source.IndexOf('/');
|
|
if (slash != -1)
|
|
{
|
|
netmask = int.Parse(source.Substring(slash + 1));
|
|
source = source.Substring(0, slash);
|
|
}
|
|
|
|
bool hasColons = source.Contains(':');
|
|
bool hasDots = source.Contains('.');
|
|
|
|
if (!hasColons && hasDots) // IPv4
|
|
{
|
|
byte[] v4bytes = source.Split('.').Select((s) => byte.Parse(s)).ToArray();
|
|
if (v4bytes.Length != 4)
|
|
throw new FormatException();
|
|
|
|
if (netmask != 128)
|
|
netmask += 96;
|
|
|
|
address[5] = 0xffff;
|
|
address[6] = v4bytes.GetUShort(true);
|
|
address[7] = v4bytes.GetUShort(2, true);
|
|
} else if (hasColons)
|
|
{
|
|
string[] words = source.Split(':');
|
|
|
|
if (string.Empty.Equals(words[0]))
|
|
words = words.Slice(1);
|
|
if (string.Empty.Equals(words[words.Length - 1]))
|
|
words = words.Slice(0, words.Length - 1);
|
|
|
|
if ((words.Length == 1) && (words[0].Equals(String.Empty)))
|
|
if (netmask == 0)
|
|
return IPv6.ANY;
|
|
|
|
if (words.Length > 8)
|
|
throw new FormatException();
|
|
|
|
if (hasDots)
|
|
{
|
|
byte[] v4bytes = words[words.Length-1].Split('.').Select((s) => byte.Parse(s)).ToArray();
|
|
if (v4bytes.Length != 4)
|
|
throw new FormatException();
|
|
|
|
address[5] = 0xffff;
|
|
address[6] = v4bytes.GetUShort(true);
|
|
address[7] = v4bytes.GetUShort(2, true);
|
|
}
|
|
else
|
|
{
|
|
int fill = 8 - words.Length;
|
|
int n = 0, m = 0;
|
|
while (n < words.Length)
|
|
{
|
|
if (String.Empty.Equals(words[n]))
|
|
{
|
|
m += fill;
|
|
}
|
|
else
|
|
{
|
|
address[m] = Convert.ToUInt16(words[n], 16);
|
|
}
|
|
m++;
|
|
n++;
|
|
}
|
|
|
|
}
|
|
}
|
|
return new IPv6(address, (uint)netmask);
|
|
}
|
|
|
|
public static IPv6 operator ++(IPv6 ip) => new IPv6(Words.Add(ip.value.BigEndian(), 1).BigEndian(), ip.mask);
|
|
public static IPv6 operator --(IPv6 ip) => new IPv6(Words.Del(ip.value.BigEndian(), 1).BigEndian(), ip.mask);
|
|
|
|
public static IPv6 operator +(IPv6 ip, int b) => new IPv6(Words.Add(ip.value.BigEndian(), b).BigEndian(), ip.mask);
|
|
public static IPv6 operator -(IPv6 ip, int b) => new IPv6(Words.Del(ip.value.BigEndian(), b).BigEndian(), ip.mask);
|
|
|
|
public static implicit operator IPAddress(IPv6 ip) => new IPAddress(ip.ToPackedBytes());
|
|
public static implicit operator IPv6(IPAddress ip) => new IPv6(ip.GetAddressBytes());
|
|
|
|
public static implicit operator IPv6(String s) => IPv6.Parse(s);
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (obj is IPAddress)
|
|
obj = (IPv6)obj;
|
|
|
|
if (obj is IPv6)
|
|
{
|
|
return value.SequenceEqual((obj as IPv6).value) && Equals(mask,(obj as IPv6).mask) ;
|
|
}
|
|
return false;
|
|
}
|
|
public override int GetHashCode()
|
|
{
|
|
int hash = 0;
|
|
for (int n = 0; n < 8; n++)
|
|
hash = (hash << 4) | value[n];
|
|
return hash;
|
|
}
|
|
|
|
}
|
|
|
|
}
|