Files
linker/linker.tun/LinkerLinuxTunDevice.cs
snltty 7c89853e41 sync
2024-08-19 17:11:27 +08:00

329 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using linker.libs;
using linker.libs.extends;
using Microsoft.Win32.SafeHandles;
using System.Net;
using System.Runtime.InteropServices;
namespace linker.tun
{
internal sealed class LinkerLinuxTunDevice : ILinkerTunDevice
{
private string name = string.Empty;
public string Name => name;
public bool Running => fs != null;
private string interfaceLinux = string.Empty;
private FileStream fs = null;
private SafeFileHandle safeFileHandle;
private IPAddress address;
private byte prefixLength = 24;
public LinkerLinuxTunDevice(string name)
{
this.name = name;
}
public bool Setup(IPAddress address, IPAddress gateway, byte prefixLength, out string error)
{
error = string.Empty;
this.address = address;
this.prefixLength = prefixLength;
if (Running)
{
error = ($"Adapter already exists");
return false;
}
if (Create(out error) == false)
{
return false;
}
if (Open(out error) == false)
{
Shutdown();
return false;
}
fs = new FileStream(safeFileHandle, FileAccess.ReadWrite, 1500);
interfaceLinux = GetLinuxInterfaceNum();
return true;
}
private bool Create(out string error)
{
error = string.Empty;
byte[] ipv6 = IPAddress.Parse("fe80::1818:1818:1818:1818").GetAddressBytes();
address.GetAddressBytes().CopyTo(ipv6, ipv6.Length - 4);
CommandHelper.Linux(string.Empty, new string[] {
$"ip tuntap add mode tun dev {Name}",
$"ip addr add {address}/{prefixLength} dev {Name}",
$"ip addr add {new IPAddress(ipv6)}/64 dev {Name}",
$"ip link set dev {Name} up"
});
string str = CommandHelper.Linux(string.Empty, new string[] { $"ifconfig" });
if (str.Contains(Name) == false)
{
error = CommandHelper.Linux(string.Empty, new string[] { $"ip tuntap add mode tun dev {Name}" });
return false;
}
return true;
}
private bool Open(out string error)
{
error = string.Empty;
SafeFileHandle _safeFileHandle = File.OpenHandle("/dev/net/tun", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.Asynchronous);
if (_safeFileHandle.IsInvalid)
{
_safeFileHandle?.Dispose();
Shutdown();
error = $"open file /dev/net/tun fail {Marshal.GetLastWin32Error()}";
return false;
}
int ioctl = LinuxAPI.Ioctl(Name, _safeFileHandle, 1074025674);
if (ioctl != 0)
{
_safeFileHandle?.Dispose();
Shutdown();
error = $"Ioctl fail : {ioctl}{Marshal.GetLastWin32Error()}";
return false;
}
safeFileHandle = _safeFileHandle;
return true;
}
public void Shutdown()
{
try
{
safeFileHandle?.Dispose();
safeFileHandle = null;
try
{
fs?.Flush();
}
catch (Exception)
{
}
fs?.Close();
fs?.Dispose();
}
catch (Exception)
{
}
fs = null;
interfaceLinux = string.Empty;
CommandHelper.Linux(string.Empty, new string[] { $"ip link del {Name}", $"ip tuntap del mode tun dev {Name}" });
}
public void SetMtu(int value)
{
CommandHelper.Linux(string.Empty, new string[] { $"ip link set dev {Name} mtu {value}" });
}
public void SetNat(out string error)
{
error = string.Empty;
try
{
IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.GetPrefixIP(prefixLength));
CommandHelper.Linux(string.Empty, new string[] {
$"sysctl -w net.ipv4.ip_forward=1",
$"iptables -A FORWARD -i {Name} -j ACCEPT",
$"iptables -A FORWARD -o {Name} -m state --state ESTABLISHED,RELATED -j ACCEPT",
$"iptables -t nat -A POSTROUTING ! -o {Name} -s {network}/{prefixLength} -j MASQUERADE",
});
}
catch (Exception ex)
{
error = ex.Message;
}
}
public void RemoveNat(out string error)
{
error = string.Empty;
try
{
CommandHelper.Linux(string.Empty, new string[] {
$"iptables -D FORWARD -i {Name} -j ACCEPT",
$"iptables -D FORWARD -o {Name} -m state --state ESTABLISHED,RELATED -j ACCEPT"
});
IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.GetPrefixIP(prefixLength));
string iptableLineNumbers = CommandHelper.Linux(string.Empty, new string[] { $"iptables -t nat -L --line-numbers | grep {network}/{prefixLength} | cut -d' ' -f1" });
if (string.IsNullOrWhiteSpace(iptableLineNumbers) == false)
{
string[] commands = iptableLineNumbers.Split(Environment.NewLine)
.Where(c => string.IsNullOrWhiteSpace(c) == false)
.Select(c => $"iptables -t nat -D POSTROUTING {c}").ToArray();
CommandHelper.Linux(string.Empty, commands);
}
}
catch (Exception ex)
{
error = ex.Message;
}
}
public void AddForward(List<LinkerTunDeviceForwardItem> forwards)
{
string[] commands = forwards.Where(c => c != null && c.Enable).SelectMany(c =>
{
return new string[] {
$"sysctl -w net.ipv4.ip_forward=1",
$"iptables -t nat -A PREROUTING -p tcp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -A POSTROUTING -p tcp --dport {c.ConnectPort} -j MASQUERADE",
$"iptables -t nat -A PREROUTING -p udp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -A POSTROUTING -p udp --dport {c.ConnectPort} -j MASQUERADE",
};
}).ToArray();
CommandHelper.Linux(string.Empty, commands);
}
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards)
{
string[] commands = forwards.Where(c => c != null && c.Enable).SelectMany(c =>
{
return new string[] {
$"sysctl -w net.ipv4.ip_forward=1",
$"iptables -t nat -D PREROUTING -p tcp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -D POSTROUTING -p tcp --dport {c.ConnectPort} -j MASQUERADE",
$"iptables -t nat -D PREROUTING -p udp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -D POSTROUTING -p udp --dport {c.ConnectPort} -j MASQUERADE"
};
}).ToArray();
CommandHelper.Linux(string.Empty, commands);
}
public void AddRoute(LinkerTunDeviceRouteItem[] ips, IPAddress ip, bool gateway)
{
if (gateway)
{
var commands = ips.Select(c =>
{
uint prefixValue = NetworkHelper.GetPrefixIP(c.PrefixLength);
IPAddress network = NetworkHelper.ToNetworkIp(c.Address, prefixValue);
return $"iptables -t nat -A POSTROUTING -o {Name} -s {network}/{c.PrefixLength} -j MASQUERADE";
}).ToList();
commands.Insert(0, "sysctl -w net.ipv4.ip_forward=1");
CommandHelper.Linux(string.Empty, commands.ToArray());
}
else
{
string[] commands = ips.Select(item =>
{
uint prefixValue = NetworkHelper.GetPrefixIP(item.PrefixLength);
IPAddress network = NetworkHelper.ToNetworkIp(item.Address, prefixValue);
return $"ip route add {network}/{item.PrefixLength} via {ip} dev {Name} metric 1 ";
}).ToArray();
if (commands.Length > 0)
{
CommandHelper.Linux(string.Empty, commands);
}
}
}
public void DelRoute(LinkerTunDeviceRouteItem[] ip, bool gateway)
{
if (gateway)
{
foreach (var item in ip)
{
IPAddress network = NetworkHelper.ToNetworkIp(item.Address, NetworkHelper.GetPrefixIP(item.PrefixLength));
string iptableLineNumbers = CommandHelper.Linux(string.Empty, new string[] { $"iptables -t nat -L --line-numbers | grep {network}/{item.PrefixLength} | cut -d' ' -f1" });
if (string.IsNullOrWhiteSpace(iptableLineNumbers) == false)
{
string[] commands = iptableLineNumbers.Split(Environment.NewLine)
.Where(c => string.IsNullOrWhiteSpace(c) == false)
.Select(c => $"iptables -t nat -D POSTROUTING {c}").ToArray();
CommandHelper.Linux(string.Empty, commands);
}
}
}
else
{
string[] commands = ip.Select(item =>
{
uint prefixValue = NetworkHelper.GetPrefixIP(item.PrefixLength);
IPAddress network = NetworkHelper.ToNetworkIp(item.Address, prefixValue);
return $"ip route del {network}/{item.PrefixLength}";
}).ToArray();
CommandHelper.Linux(string.Empty, commands);
}
}
private byte[] buffer = new byte[2 * 1024];
private object writeLockObj = new object();
public ReadOnlyMemory<byte> Read()
{
try
{
int length = fs.Read(buffer.AsSpan(4));
length.ToBytes(buffer);
return buffer.AsMemory(0, length + 4);
}
catch (Exception)
{
}
return Helper.EmptyArray;
}
public bool Write(ReadOnlyMemory<byte> buffer)
{
lock (writeLockObj)
{
try
{
fs.Write(buffer.Span);
fs.Flush();
return true;
}
catch (Exception)
{
}
return false;
}
}
private string GetLinuxInterfaceNum()
{
string output = CommandHelper.Linux(string.Empty, new string[] { "ip route" });
foreach (var item in output.Split(Environment.NewLine))
{
if (item.StartsWith("default via"))
{
var strs = item.Split(' ');
for (int i = 0; i < strs.Length; i++)
{
if (strs[i] == "dev")
{
return strs[i + 1];
}
}
}
}
return string.Empty;
}
public void Clear()
{
}
}
}