---
sidebar_position: 100
slug: /dev/tuntap
---

# 98.100、单独使用虚拟网卡

## 1、说明

:::tip[说明]
在你的.NET8.0+项目中，集成tun网卡，适用于`windows`、`linux`，源码在[https://github.com/snltty/linker/tree/master/linker.tun](https://github.com/snltty/linker/tree/master/linker.tun)

### 1、windows
1. [下载wintun](https://www.wintun.net/)，选择适合你系统的 `wintun.dll`放到项目根目录
1. 在windows下，使用 wintun`WintunCreateAdapter`创建适配器，可以提供一个guid，如果不提供，将随机一个，这会导致注册表不断的产生新的记录
2. 如果提供一个固定的guid，在程序的一次会话内可以重复删除和创建适配器，但是，如果你在多个会话内使用同一个guid，将会非常大概率创建适配器失败
3. 所以`linker.tun`选择每次运行程序时生成guid，在本次会话内重复使用，且提供了`LinkerTunDeviceAdapter.Clear()`让你选择在合适的适合清理注册表

### 2、linux
1. 请确保你的系统拥有`tuntap`模块，`ifconfig`、`ip`、`iptables`命令
:::

## 2、编写一个简单的代码

:::tip[说明]
nuget 安装 `linker.tun`，然后编写代码

```c#

internal class Program
{
    public static LinkerTunDeviceAdapter linkerTunDeviceAdapter;
    static void Main(string[] args)
    {
        linkerTunDeviceAdapter = new LinkerTunDeviceAdapter();
        //初始化设备名，和读取数据回调
        linkerTunDeviceAdapter.Initialize("linker111", new LinkerTunDeviceCallback());
        //在初始化后，可以清理一些数据，在windows，将会清理适配器的注册表信息
        //linkerTunDeviceAdapter.Clear();

        //启动网卡，ip，掩码，mtu
        linkerTunDeviceAdapter.Setup(IPAddress.Parse("192.168.55.2"), 24, 1416);
        //设置NAT转发，这会将来到本网卡且目标IP不是本网卡IP的包转发到其它网卡
        //linkerTunDeviceAdapter.SetNat();

        //如果存在错误
        if (string.IsNullOrWhiteSpace(linkerTunDeviceAdapter.Error))
        {
            Console.WriteLine(linkerTunDeviceAdapter.Error);
            //关闭网卡
            linkerTunDeviceAdapter.Shutdown();
        }
        Console.ReadLine();
    }
}

public sealed class LinkerTunDeviceCallback : ILinkerTunDeviceCallback
{
    //收到IP数据包
    public async Task Callback(LinkerTunDevicPacket packet)
    {
        ICMPAnswer(packet);
        await Task.CompletedTask;
    }
    private unsafe void ICMPAnswer(LinkerTunDevicPacket packet)
    {
        fixed (byte* ptr = packet.IPPacket.Span)
        {
            //ICMP包，且是 Request
            if (ptr[9] == 1 && ptr[20] == 8)
            {
                Console.WriteLine($"ICMP to {new IPAddress(packet.IPPacket.Span.Slice(16, 4))}");

                uint dist = BinaryPrimitives.ReadUInt32LittleEndian(packet.IPPacket.Span.Slice(16, 4));
                //目的地址变源地址，
                *(uint*)(ptr + 16) = *(uint*)(ptr + 12);
                //假装是网关回复的
                *(uint*)(ptr + 12) = dist;

                //计算一次IP头校验和
                *(ushort*)(ptr + 10) = 0;
                *(ushort*)(ptr + 10) = Program.linkerTunDeviceAdapter.Checksum((ushort*)ptr, 20);

                //改为ICMP Reply
                *(ushort*)(ptr + 20) = 0;

                //计算ICMP校验和
                *(ushort*)(ptr + 22) = 0;

                int length = packet.IPPacket.Span.Length - 20;
                ushort sum = Program.linkerTunDeviceAdapter.Checksum((ushort*)(ptr + 20), length);
                *(ushort*)(ptr + 22) = sum;

                //写入网卡，回应这个ICMP请求
                Program.linkerTunDeviceAdapter.Write(packet.IPPacket);
            }
        }
    }
}
```
:::
