应用层SNAT

This commit is contained in:
snltty
2025-04-20 17:21:44 +08:00
parent 6775d99ee5
commit fb67331ee4
34 changed files with 362 additions and 136 deletions

View File

@@ -37,7 +37,7 @@ jobs:
release_name: v1.7.3.${{ steps.date.outputs.today }}
draft: false
prerelease: false
body: "1. 优化自动分配IP\r\n2. 优化网卡,排除不明数据包\r\n3. 虚拟网卡点对网IP映射用于解决网段冲突"
body: "1. 优化自动分配IP\r\n2. 优化网卡,排除不明数据包\r\n3. 虚拟网卡点对网IP映射用于解决网段冲突\r\n4. 内置应用层SNAT用于无法使用系统NAT的windows系统"
- name: publish projects
run: ./publish.bat "C:\\Android\\android-sdk"
- name: upload-win-x86-oss

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.cfba5739.js"></script><script defer="defer" src="js/app.644fc91c.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.3aab4747.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.cfba5739.js"></script><script defer="defer" src="js/app.1acc2fd0.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.73965826.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
"use strict";(self["webpackChunklinker_web"]=self["webpackChunklinker_web"]||[]).push([[163],{427:function(e,n,a){a.r(n),a.d(n,{default:function(){return O}});var t=a(6768);const s={class:"net-wrap app-wrap"},l={class:"inner absolute flex flex-column flex-nowrap"},i={class:"head"},o={class:"body flex-1 relative"},c={class:"status"};function r(e,n,a,r,u,d){const g=(0,t.g2)("Head"),p=(0,t.g2)("List"),v=(0,t.g2)("Status");return(0,t.uX)(),(0,t.CE)("div",s,[(0,t.Lk)("div",l,[(0,t.Lk)("div",i,[(0,t.bF)(g)]),(0,t.Lk)("div",o,[(0,t.bF)(p)]),(0,t.Lk)("div",c,[(0,t.bF)(v,{config:!1})])])])}a(4114);const u=e=>((0,t.Qi)("data-v-1fd9ef80"),e=e(),(0,t.jt)(),e),d={class:"head-wrap"},g={class:"tools flex"},p=u((()=>(0,t.Lk)("span",{class:"label"},"服务器 ",-1))),v=u((()=>(0,t.Lk)("span",{class:"flex-1"},null,-1))),h={style:{"margin-left":"1rem"}};function f(e,n,a,s,l,i){const o=(0,t.g2)("el-input"),c=(0,t.g2)("Refresh"),r=(0,t.g2)("el-icon"),u=(0,t.g2)("el-button"),f=(0,t.g2)("Background");return(0,t.uX)(),(0,t.CE)("div",d,[(0,t.Lk)("div",g,[p,(0,t.bF)(o,{modelValue:s.state.server,"onUpdate:modelValue":n[0]||(n[0]=e=>s.state.server=e),readonly:"",style:{width:"14rem"},size:"small"},null,8,["modelValue"]),v,(0,t.bF)(u,{size:"small",onClick:s.handleRefresh},{default:(0,t.k6)((()=>[(0,t.eW)(" 刷新(F5)"),(0,t.bF)(r,null,{default:(0,t.k6)((()=>[(0,t.bF)(c)])),_:1})])),_:1},8,["onClick"]),(0,t.Lk)("div",h,[(0,t.bF)(f,{name:"net"})])])])}var k=a(3830),m=a(144),C=a(7477),b=a(5096),L={components:{Edit:C.ffu,Refresh:C.C42,Background:b.A},setup(){const e=(0,k.B)(),n=(0,m.Kh)({server:(0,t.EW)((()=>e.value.config.Client.Server.Host))}),a=()=>{window.location.reload()};return{state:n,handleRefresh:a}}},_=a(1241);const w=(0,_.A)(L,[["render",f],["__scopeId","data-v-1fd9ef80"]]);var F=w;const S=e=>((0,t.Qi)("data-v-68d1c30a"),e=e(),(0,t.jt)(),e),x={class:"net-list-wrap flex flex-column absolute"},T={class:"flex-1 scrollbar"},z={class:"flex"},E=S((()=>(0,t.Lk)("div",{class:"flex-1"},null,-1))),A={class:"tuntap"},I={class:"page t-c"},P={class:"page-wrap t-c"};function B(e,n,a,s,l,i){const o=(0,t.g2)("DeviceName"),c=(0,t.g2)("UpdaterBtn"),r=(0,t.g2)("TuntapShow"),u=(0,t.g2)("el-pagination");return(0,t.uX)(),(0,t.CE)("div",x,[(0,t.Lk)("div",T,[(0,t.Lk)("ul",null,[((0,t.uX)(!0),(0,t.CE)(t.FK,null,(0,t.pI)(s.devices.page.List,((e,n)=>((0,t.uX)(),(0,t.CE)("li",{key:n},[(0,t.Lk)("dl",null,[(0,t.Lk)("dt",z,[(0,t.Lk)("div",null,[(0,t.bF)(o,{item:e},null,8,["item"])]),E,(0,t.Lk)("div",null,[(0,t.bF)(c,{config:!1,item:e},null,8,["item"])])]),(0,t.Lk)("dd",A,[s.tuntap.list[e.MachineId]?((0,t.uX)(),(0,t.Wv)(r,{key:0,item:e},null,8,["item"])):(0,t.Q3)("",!0)])])])))),128))])]),(0,t.Lk)("div",I,[(0,t.Lk)("div",P,[(0,t.bF)(u,{size:"small",background:"",layout:"prev,pager, next","pager-count":5,total:s.devices.page.Count,"page-size":s.devices.page.Request.Size,"current-page":s.devices.page.Request.Page,onCurrentChange:s.handlePageChange,onSizeChange:s.handlePageSizeChange,"page-sizes":[10,20,50,100,255]},null,8,["total","page-size","current-page","onCurrentChange","onSizeChange"])])])])}var y=a(8104),R=a(7985),D=a(9383),U=a(7115),X=a(6588),V=a(9273),N=a(9983),Q={components:{StarFilled:C.BQ2,UpdaterBtn:U.A,DeviceName:X.A,TuntapShow:V.A},setup(e){(0,k.B)();const n=(0,m.Kh)({}),{devices:a,machineId:s,_getSignList:l,_getSignList1:i,handleDeviceEdit:o,handlePageChange:c,handlePageSizeChange:r,handleDel:u,clearDevicesTimeout:d}=(0,R.r)(),{tuntap:g,_getTuntapInfo:p,handleTuntapRefresh:v,clearTuntapTimeout:h,handleTuntapEdit:f,sortTuntapIP:C}=(0,y.O)(),{_getUpdater:b,_subscribeUpdater:L,clearUpdaterTimeout:_}=(0,D.d)(),{connections:w,forwardConnections:F,_getForwardConnections:S,tuntapConnections:x,_getTuntapConnections:T,socks5Connections:z,_getSocks5Connections:E,handleTunnelConnections:A,clearConnectionsTimeout:I}=(0,N.L2)();return(0,t.sV)((()=>{c(),v(),l(),i(),p(),b(),L()})),(0,t.hi)((()=>{d(),h(),_()})),{state:n,devices:a,machineId:s,handlePageChange:c,handlePageSizeChange:r,tuntap:g}}};const H=(0,_.A)(Q,[["render",B],["__scopeId","data-v-68d1c30a"]]);var K=H,W=a(6743),j=a(1387),q={components:{Head:F,List:K,Status:W.A},setup(){document.addEventListener("contextmenu",(function(e){e.preventDefault()}));const e=(0,k.B)(),n=(0,j.rd)();return(0,t.sV)((()=>{0==e.value.hasAccess("NetManager")&&n.push({name:"NoPermission"})})),{}}};const M=(0,_.A)(q,[["render",r],["__scopeId","data-v-6a3f3b43"]]);var O=M}}]);
"use strict";(self["webpackChunklinker_web"]=self["webpackChunklinker_web"]||[]).push([[743],{427:function(e,n,a){a.r(n),a.d(n,{default:function(){return O}});var t=a(6768);const s={class:"net-wrap app-wrap"},l={class:"inner absolute flex flex-column flex-nowrap"},i={class:"head"},o={class:"body flex-1 relative"},c={class:"status"};function r(e,n,a,r,u,d){const g=(0,t.g2)("Head"),p=(0,t.g2)("List"),v=(0,t.g2)("Status");return(0,t.uX)(),(0,t.CE)("div",s,[(0,t.Lk)("div",l,[(0,t.Lk)("div",i,[(0,t.bF)(g)]),(0,t.Lk)("div",o,[(0,t.bF)(p)]),(0,t.Lk)("div",c,[(0,t.bF)(v,{config:!1})])])])}a(4114);const u=e=>((0,t.Qi)("data-v-1fd9ef80"),e=e(),(0,t.jt)(),e),d={class:"head-wrap"},g={class:"tools flex"},p=u((()=>(0,t.Lk)("span",{class:"label"},"服务器 ",-1))),v=u((()=>(0,t.Lk)("span",{class:"flex-1"},null,-1))),h={style:{"margin-left":"1rem"}};function f(e,n,a,s,l,i){const o=(0,t.g2)("el-input"),c=(0,t.g2)("Refresh"),r=(0,t.g2)("el-icon"),u=(0,t.g2)("el-button"),f=(0,t.g2)("Background");return(0,t.uX)(),(0,t.CE)("div",d,[(0,t.Lk)("div",g,[p,(0,t.bF)(o,{modelValue:s.state.server,"onUpdate:modelValue":n[0]||(n[0]=e=>s.state.server=e),readonly:"",style:{width:"14rem"},size:"small"},null,8,["modelValue"]),v,(0,t.bF)(u,{size:"small",onClick:s.handleRefresh},{default:(0,t.k6)((()=>[(0,t.eW)(" 刷新(F5)"),(0,t.bF)(r,null,{default:(0,t.k6)((()=>[(0,t.bF)(c)])),_:1})])),_:1},8,["onClick"]),(0,t.Lk)("div",h,[(0,t.bF)(f,{name:"net"})])])])}var k=a(3830),m=a(144),C=a(7477),b=a(5096),L={components:{Edit:C.ffu,Refresh:C.C42,Background:b.A},setup(){const e=(0,k.B)(),n=(0,m.Kh)({server:(0,t.EW)((()=>e.value.config.Client.Server.Host))}),a=()=>{window.location.reload()};return{state:n,handleRefresh:a}}},_=a(1241);const w=(0,_.A)(L,[["render",f],["__scopeId","data-v-1fd9ef80"]]);var F=w;const S=e=>((0,t.Qi)("data-v-68d1c30a"),e=e(),(0,t.jt)(),e),x={class:"net-list-wrap flex flex-column absolute"},T={class:"flex-1 scrollbar"},z={class:"flex"},E=S((()=>(0,t.Lk)("div",{class:"flex-1"},null,-1))),A={class:"tuntap"},I={class:"page t-c"},P={class:"page-wrap t-c"};function B(e,n,a,s,l,i){const o=(0,t.g2)("DeviceName"),c=(0,t.g2)("UpdaterBtn"),r=(0,t.g2)("TuntapShow"),u=(0,t.g2)("el-pagination");return(0,t.uX)(),(0,t.CE)("div",x,[(0,t.Lk)("div",T,[(0,t.Lk)("ul",null,[((0,t.uX)(!0),(0,t.CE)(t.FK,null,(0,t.pI)(s.devices.page.List,((e,n)=>((0,t.uX)(),(0,t.CE)("li",{key:n},[(0,t.Lk)("dl",null,[(0,t.Lk)("dt",z,[(0,t.Lk)("div",null,[(0,t.bF)(o,{item:e},null,8,["item"])]),E,(0,t.Lk)("div",null,[(0,t.bF)(c,{config:!1,item:e},null,8,["item"])])]),(0,t.Lk)("dd",A,[s.tuntap.list[e.MachineId]?((0,t.uX)(),(0,t.Wv)(r,{key:0,item:e},null,8,["item"])):(0,t.Q3)("",!0)])])])))),128))])]),(0,t.Lk)("div",I,[(0,t.Lk)("div",P,[(0,t.bF)(u,{size:"small",background:"",layout:"prev,pager, next","pager-count":5,total:s.devices.page.Count,"page-size":s.devices.page.Request.Size,"current-page":s.devices.page.Request.Page,onCurrentChange:s.handlePageChange,onSizeChange:s.handlePageSizeChange,"page-sizes":[10,20,50,100,255]},null,8,["total","page-size","current-page","onCurrentChange","onSizeChange"])])])])}var y=a(8104),R=a(7985),D=a(9383),U=a(7115),X=a(6588),V=a(7163),N=a(9983),Q={components:{StarFilled:C.BQ2,UpdaterBtn:U.A,DeviceName:X.A,TuntapShow:V.A},setup(e){(0,k.B)();const n=(0,m.Kh)({}),{devices:a,machineId:s,_getSignList:l,_getSignList1:i,handleDeviceEdit:o,handlePageChange:c,handlePageSizeChange:r,handleDel:u,clearDevicesTimeout:d}=(0,R.r)(),{tuntap:g,_getTuntapInfo:p,handleTuntapRefresh:v,clearTuntapTimeout:h,handleTuntapEdit:f,sortTuntapIP:C}=(0,y.O)(),{_getUpdater:b,_subscribeUpdater:L,clearUpdaterTimeout:_}=(0,D.d)(),{connections:w,forwardConnections:F,_getForwardConnections:S,tuntapConnections:x,_getTuntapConnections:T,socks5Connections:z,_getSocks5Connections:E,handleTunnelConnections:A,clearConnectionsTimeout:I}=(0,N.L2)();return(0,t.sV)((()=>{c(),v(),l(),i(),p(),b(),L()})),(0,t.hi)((()=>{d(),h(),_()})),{state:n,devices:a,machineId:s,handlePageChange:c,handlePageSizeChange:r,tuntap:g}}};const H=(0,_.A)(Q,[["render",B],["__scopeId","data-v-68d1c30a"]]);var K=H,W=a(6743),j=a(1387),q={components:{Head:F,List:K,Status:W.A},setup(){document.addEventListener("contextmenu",(function(e){e.preventDefault()}));const e=(0,k.B)(),n=(0,j.rd)();return(0,t.sV)((()=>{0==e.value.hasAccess("NetManager")&&n.push({name:"NoPermission"})})),{}}};const M=(0,_.A)(q,[["render",r],["__scopeId","data-v-6a3f3b43"]]);var O=M}}]);

View File

@@ -61,7 +61,5 @@ sidebar_position: 1
2. <a href="https://aka.ms/vs/16/release/vc_redist.x64.exe" target="_blank">Microsoft Visual C++ 2015-2019 Redistributable 运行库</a>
3. <a href="https://www.microsoft.com/download/details.aspx?id=47442" target="_blank">KB3063858 运行库</a>
4. <a href="https://www.microsoft.com/zh-cn/download/details.aspx?id=46148" target="_blank">KB3033929 全球化补丁</a>
5. <a href="https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-web-installer" target="_blank">netframework4.6.2 ICS NAT运行库</a>
:::

View File

@@ -1,11 +0,0 @@
---
sidebar_position: 4
---
# 1.1.2、ICS
:::tip[说明]
1. 如果系统没有<a href="https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-web-installer" target="_blank">netframework4.6.2</a>,就下载安装一下
2. 需要linker v1.7.0+版本
3. 剩下的交给linker
:::

View File

@@ -15,9 +15,7 @@ sidebar_position: 2
:::tip[1、情况1你的设备支持NAT转发时]
1. linux已经自动添加NAT转发(在`OpenWrt`,需要在`防火墙 - 区域设置`中将`转发`设置为`接受`)
2. windows暂时找到两种NAT方式
1. NetNat<a href="./1.1.1、NetNat">请参照 1.1.1、NetNat</a>
2. ICS<a href="./1.1.2、ICS">请参照1.1.2、ICS(Internet Connection Sharing)</a>
2. windows优先使用系统NetNatNetNat失败则启用内置的应用层SNAT但是性能应该没有NetNat好
3. macos需要你自己在**被访问端**添加NAT转发
```
# 开启ip转发

View File

@@ -135,7 +135,6 @@ namespace linker.messenger.tuntap
/// 开关多个bool集合
/// </summary>
public TuntapSwitch Switch { get; set; }
/// <summary>
/// 延迟ms
/// </summary>
@@ -310,6 +309,10 @@ namespace linker.messenger.tuntap
}
}
}
/// <summary>
/// 是否开启了应用层NAT
/// </summary>
public bool AppNat => (Switch & TuntapSwitch.AppNat) == TuntapSwitch.AppNat;
}
public sealed partial class TuntapForwardInfo
@@ -398,6 +401,11 @@ namespace linker.messenger.tuntap
/// 调整网卡顺序
/// </summary>
InterfaceOrder = 128,
/// <summary>
/// 是否开启了应用层NAT
/// </summary>
AppNat = 256,
}

View File

@@ -65,7 +65,7 @@ namespace linker.messenger.tuntap
SystemInfo = $"{System.Runtime.InteropServices.RuntimeInformation.OSDescription} {(string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SNLTTY_LINKER_IS_DOCKER")) == false ? "Docker" : "")}",
Forwards = tuntapConfigTransfer.Info.Forwards,
Switch = tuntapConfigTransfer.Info.Switch
Switch = tuntapConfigTransfer.Info.Switch | (tuntapTransfer.AppNat ? TuntapSwitch.AppNat : 0)
});
}
public void AddData(Memory<byte> data)

View File

@@ -14,6 +14,8 @@ namespace linker.messenger.tuntap
public string SetupError => linkerTunDeviceAdapter.SetupError;
public string NatError => linkerTunDeviceAdapter.NatError;
public bool AppNat => linkerTunDeviceAdapter.AppNat;
public Action OnSetupBefore { get; set; } = () => { };
public Action OnSetupAfter { get; set; } = () => { };
public Action OnSetupSuccess { get; set; } = () => { };

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.245" ProductVersion="0.0.0.245" publishDir="/dist/" dstrip="false" local="false" ignored="false">
<project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.247" ProductVersion="0.0.0.247" publishDir="/dist/" dstrip="false" local="false" ignored="false">
<file name="main.aardio" path="main.aardio" comment="main.aardio"/>
<folder name="资源文件" path="res" embed="true" local="false" ignored="false">
<file name="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/>

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.cfba5739.js"></script><script defer="defer" src="js/app.644fc91c.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.3aab4747.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.cfba5739.js"></script><script defer="defer" src="js/app.1acc2fd0.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.73965826.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";(self["webpackChunklinker_web"]=self["webpackChunklinker_web"]||[]).push([[743],{427:function(e,n,a){a.r(n),a.d(n,{default:function(){return O}});var t=a(6768);const s={class:"net-wrap app-wrap"},l={class:"inner absolute flex flex-column flex-nowrap"},i={class:"head"},o={class:"body flex-1 relative"},c={class:"status"};function r(e,n,a,r,u,d){const g=(0,t.g2)("Head"),p=(0,t.g2)("List"),v=(0,t.g2)("Status");return(0,t.uX)(),(0,t.CE)("div",s,[(0,t.Lk)("div",l,[(0,t.Lk)("div",i,[(0,t.bF)(g)]),(0,t.Lk)("div",o,[(0,t.bF)(p)]),(0,t.Lk)("div",c,[(0,t.bF)(v,{config:!1})])])])}a(4114);const u=e=>((0,t.Qi)("data-v-1fd9ef80"),e=e(),(0,t.jt)(),e),d={class:"head-wrap"},g={class:"tools flex"},p=u((()=>(0,t.Lk)("span",{class:"label"},"服务器 ",-1))),v=u((()=>(0,t.Lk)("span",{class:"flex-1"},null,-1))),h={style:{"margin-left":"1rem"}};function f(e,n,a,s,l,i){const o=(0,t.g2)("el-input"),c=(0,t.g2)("Refresh"),r=(0,t.g2)("el-icon"),u=(0,t.g2)("el-button"),f=(0,t.g2)("Background");return(0,t.uX)(),(0,t.CE)("div",d,[(0,t.Lk)("div",g,[p,(0,t.bF)(o,{modelValue:s.state.server,"onUpdate:modelValue":n[0]||(n[0]=e=>s.state.server=e),readonly:"",style:{width:"14rem"},size:"small"},null,8,["modelValue"]),v,(0,t.bF)(u,{size:"small",onClick:s.handleRefresh},{default:(0,t.k6)((()=>[(0,t.eW)(" 刷新(F5)"),(0,t.bF)(r,null,{default:(0,t.k6)((()=>[(0,t.bF)(c)])),_:1})])),_:1},8,["onClick"]),(0,t.Lk)("div",h,[(0,t.bF)(f,{name:"net"})])])])}var k=a(3830),m=a(144),C=a(7477),b=a(5096),L={components:{Edit:C.ffu,Refresh:C.C42,Background:b.A},setup(){const e=(0,k.B)(),n=(0,m.Kh)({server:(0,t.EW)((()=>e.value.config.Client.Server.Host))}),a=()=>{window.location.reload()};return{state:n,handleRefresh:a}}},_=a(1241);const w=(0,_.A)(L,[["render",f],["__scopeId","data-v-1fd9ef80"]]);var F=w;const S=e=>((0,t.Qi)("data-v-68d1c30a"),e=e(),(0,t.jt)(),e),x={class:"net-list-wrap flex flex-column absolute"},T={class:"flex-1 scrollbar"},z={class:"flex"},E=S((()=>(0,t.Lk)("div",{class:"flex-1"},null,-1))),A={class:"tuntap"},I={class:"page t-c"},P={class:"page-wrap t-c"};function B(e,n,a,s,l,i){const o=(0,t.g2)("DeviceName"),c=(0,t.g2)("UpdaterBtn"),r=(0,t.g2)("TuntapShow"),u=(0,t.g2)("el-pagination");return(0,t.uX)(),(0,t.CE)("div",x,[(0,t.Lk)("div",T,[(0,t.Lk)("ul",null,[((0,t.uX)(!0),(0,t.CE)(t.FK,null,(0,t.pI)(s.devices.page.List,((e,n)=>((0,t.uX)(),(0,t.CE)("li",{key:n},[(0,t.Lk)("dl",null,[(0,t.Lk)("dt",z,[(0,t.Lk)("div",null,[(0,t.bF)(o,{item:e},null,8,["item"])]),E,(0,t.Lk)("div",null,[(0,t.bF)(c,{config:!1,item:e},null,8,["item"])])]),(0,t.Lk)("dd",A,[s.tuntap.list[e.MachineId]?((0,t.uX)(),(0,t.Wv)(r,{key:0,item:e},null,8,["item"])):(0,t.Q3)("",!0)])])])))),128))])]),(0,t.Lk)("div",I,[(0,t.Lk)("div",P,[(0,t.bF)(u,{size:"small",background:"",layout:"prev,pager, next","pager-count":5,total:s.devices.page.Count,"page-size":s.devices.page.Request.Size,"current-page":s.devices.page.Request.Page,onCurrentChange:s.handlePageChange,onSizeChange:s.handlePageSizeChange,"page-sizes":[10,20,50,100,255]},null,8,["total","page-size","current-page","onCurrentChange","onSizeChange"])])])])}var y=a(8104),R=a(7985),D=a(9383),U=a(7115),X=a(6588),V=a(7163),N=a(9983),Q={components:{StarFilled:C.BQ2,UpdaterBtn:U.A,DeviceName:X.A,TuntapShow:V.A},setup(e){(0,k.B)();const n=(0,m.Kh)({}),{devices:a,machineId:s,_getSignList:l,_getSignList1:i,handleDeviceEdit:o,handlePageChange:c,handlePageSizeChange:r,handleDel:u,clearDevicesTimeout:d}=(0,R.r)(),{tuntap:g,_getTuntapInfo:p,handleTuntapRefresh:v,clearTuntapTimeout:h,handleTuntapEdit:f,sortTuntapIP:C}=(0,y.O)(),{_getUpdater:b,_subscribeUpdater:L,clearUpdaterTimeout:_}=(0,D.d)(),{connections:w,forwardConnections:F,_getForwardConnections:S,tuntapConnections:x,_getTuntapConnections:T,socks5Connections:z,_getSocks5Connections:E,handleTunnelConnections:A,clearConnectionsTimeout:I}=(0,N.L2)();return(0,t.sV)((()=>{c(),v(),l(),i(),p(),b(),L()})),(0,t.hi)((()=>{d(),h(),_()})),{state:n,devices:a,machineId:s,handlePageChange:c,handlePageSizeChange:r,tuntap:g}}};const H=(0,_.A)(Q,[["render",B],["__scopeId","data-v-68d1c30a"]]);var K=H,W=a(6743),j=a(1387),q={components:{Head:F,List:K,Status:W.A},setup(){document.addEventListener("contextmenu",(function(e){e.preventDefault()}));const e=(0,k.B)(),n=(0,j.rd)();return(0,t.sV)((()=>{0==e.value.hasAccess("NetManager")&&n.push({name:"NoPermission"})})),{}}};const M=(0,_.A)(q,[["render",r],["__scopeId","data-v-6a3f3b43"]]);var O=M}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,6 @@ using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Net;
using System.Net.Sockets;
namespace linker.tun
{
@@ -23,10 +22,11 @@ namespace linker.tun
private string natError = string.Empty;
public string NatError => natError;
public bool AppNat=> linkerTunDevice?.AppNat ?? false;
private FrozenDictionary<uint, uint> mapDic = new Dictionary<uint, uint>().ToFrozenDictionary();
private uint[] masks = Array.Empty<uint>();
private ConcurrentDictionary<uint, uint> natDic = new ConcurrentDictionary<uint, uint>();

View File

@@ -26,10 +26,11 @@ namespace linker.tun
private string defaultInterfaceName = string.Empty;
private int defaultInterfaceNumber = 0;
private IPAddress defaultInterfaceIP;
private uint defaultInterfaceIP32;
private CancellationTokenSource tokenSource;
private WinDivertNAT winDivertNAT;
private WinDivertNAT winDivertNAT = new WinDivertNAT();
public LinkerWinTunDevice()
{
@@ -218,9 +219,8 @@ namespace linker.tun
}
public void SetAppNat(LinkerTunAppNatItemInfo[] items, out string error)
{
winDivertNAT?.Dispose();
winDivertNAT = new WinDivertNAT(new WinDivertNAT.AddrInfo(address, prefixLength), items.Select(c => new WinDivertNAT.AddrInfo(c.IP, c.PrefixLength)).ToArray(), defaultInterfaceIP);
winDivertNAT.Setup(out error);
winDivertNAT.Shutdown();
winDivertNAT.Setup(address, items.Select(c => new WinDivertNAT.AddrInfo(c.IP, c.PrefixLength)).ToArray(), defaultInterfaceIP, out error);
}
public void RemoveNat(out string error)
{
@@ -239,7 +239,7 @@ namespace linker.tun
try
{
winDivertNAT?.Dispose();
winDivertNAT.Shutdown();
}
catch (Exception)
{
@@ -357,7 +357,7 @@ namespace linker.tun
{
if (session == 0 || tokenSource.IsCancellationRequested) return false;
if (ToAppNat(packet)) return true;
if (winDivertNAT.Inject(packet)) return true;
IntPtr packetPtr = WinTun.WintunAllocateSendPacket(session, (uint)packet.Length);
if (packetPtr != 0)
@@ -375,21 +375,6 @@ namespace linker.tun
}
return false;
}
private bool ToAppNat(ReadOnlyMemory<byte> packet)
{
ReadOnlySpan<byte> span = packet.Span;
if ((byte)(span[0] >> 4 & 0b1111) == 4 && AppNat) //只支持IPV4
{
ReadOnlySpan<byte> ip = span.Slice(16, 4);
uint distIP = BinaryPrimitives.ReadUInt32BigEndian(ip);
//不是虚拟网卡不是广播启用了应用层NATNAT成功
if (distIP != address32 && ip.GetIsBroadcastAddress() == false && winDivertNAT.Inject(packet))
{
return true;
}
}
return false;
}
private void GetWindowsInterfaceNum()
{
@@ -420,6 +405,7 @@ namespace linker.tun
defaultInterfaceName = inter.Name;
defaultInterfaceNumber = inter.GetIPProperties().GetIPv4Properties().Index;
defaultInterfaceIP = ip;
defaultInterfaceIP32 = NetworkHelper.ToValue(ip);
return;
}
}

View File

@@ -1,5 +1,7 @@
using linker.libs;
using linker.libs.extends;
using linker.libs.timer;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
@@ -9,7 +11,6 @@ namespace linker.tun
{
/// <summary>
/// 应用层简单SNAT
/// 大概意思是
/// 1收到【客户端A】的数据包10.18.18.23(客户端A的虚拟网卡IP)->192.168.56.6(局域网IP)
/// 2改为 192.168.56.2(本机IP)->192.168.56.6(局域网IP)
/// 3回来是 192.168.56.6(局域网IP)->192.168.56.2(本机IP)
@@ -18,40 +19,53 @@ namespace linker.tun
/// </summary>
public sealed class WinDivertNAT
{
public bool Running => winDivert != null;
/// <summary>
/// 驱动
/// </summary>
WinDivert winDivert;
private WinDivert winDivert;
/// <summary>
///
/// 网卡IP用来作为源地址
/// </summary>
AddrInfo src;
private NetworkIPv4Addr interfaceAddr;
private uint srcIp;
/// <summary>
/// 目标
/// 用来注入数据包
/// </summary>
AddrInfo[] dsts;
IPAddress interfaceIp;
NetworkIPv4Addr interfaceAddr;
public bool Running => winDivert != null;
private CancellationTokenSource cts;
private ConcurrentDictionary<(uint src, ushort srcPort, uint dst, ushort dstPort, ProtocolType pro), NatMapInfo> natMap = new ConcurrentDictionary<(uint src, ushort srcPort, uint dst, ushort dstPort, ProtocolType pro), NatMapInfo>();
public WinDivertNAT(AddrInfo src, AddrInfo[] dsts, IPAddress interfaceIp)
private WinDivertAddress addr = new WinDivertAddress
{
Layer = WinDivert.Layer.Network,
Outbound = true,
IPv6 = false
};
private CancellationTokenSource cts;
/// <summary>
/// 五元组NAT映射表
/// </summary>
private ConcurrentDictionary<(uint src, ushort srcPort, uint dst, ushort dstPort, ProtocolType pro), NatMapInfo> natMap = new ConcurrentDictionary<(uint src, ushort srcPort, uint dst, ushort dstPort, ProtocolType pro), NatMapInfo>();
/// <summary>
/// 分配端口表
/// </summary>
private ConcurrentDictionary<(uint src, ushort port), ushort> source2portMap = new ConcurrentDictionary<(uint src, ushort port), ushort>();
public WinDivertNAT()
{
this.src = src;
this.dsts = dsts;
this.interfaceIp = interfaceIp;
}
/// <summary>
/// 启动
/// </summary>
/// <param name="error"></param>
/// <param name="src">虚拟网卡IP</param>
/// <param name="dsts">需要NAT的IP</param>
/// <param name="interfaceIp">本地网卡IP</param>
/// <param name="error">false的时候会有报错信息</param>
/// <returns></returns>
public bool Setup(out string error)
public bool Setup(IPAddress src, AddrInfo[] dsts, IPAddress interfaceIp, out string error)
{
error = string.Empty;
@@ -60,18 +74,21 @@ namespace linker.tun
error = "only windows x64,x86";
return false;
}
if (src == null || dsts == null || dsts.Length == 0)
if (dsts == null || dsts.Length == 0)
{
error = "src is null, or dsts empty";
return false;
}
try
{
srcIp = NetworkHelper.ToValue(src);
interfaceAddr = IPv4Addr.Parse(interfaceIp.ToString());
winDivert = new WinDivert(BuildFilter(), WinDivert.Layer.Network, 0, 0);
winDivert = new WinDivert(BuildFilter(dsts), WinDivert.Layer.Network, 0, 0);
cts = new CancellationTokenSource();
Recv(cts);
ClearTask(cts);
Recv();
ClearTask();
return true;
}
catch (Exception ex)
@@ -80,14 +97,20 @@ namespace linker.tun
}
return false;
}
private string BuildFilter()
/// <summary>
/// 过滤条件,只过滤一定的数据包
/// </summary>
/// <returns></returns>
private string BuildFilter(AddrInfo[] dsts)
{
IEnumerable<string> ipRanges = dsts.Select(c => $"(ip.SrcAddr >= {c.NetworkIP} and ip.SrcAddr <= {c.BroadcastIP})");
return $"inbound and ({string.Join(" or ", ipRanges)})";
}
private void Recv()
/// <summary>
/// 开始接收数据包
/// </summary>
private void Recv(CancellationTokenSource cts)
{
cts = new CancellationTokenSource();
TimerHelper.Async(() =>
{
Memory<byte> packet = new Memory<byte>(new byte[10 * WinDivert.MTUMax]);
@@ -112,7 +135,7 @@ namespace linker.tun
break;
}
}
Dispose();
Shutdown();
});
}
@@ -144,28 +167,30 @@ namespace linker.tun
/// 注入数据包,让它直接走正确的网卡,路由到目的地
/// </summary>
/// <param name="buffer"></param>
public unsafe bool Inject(ReadOnlyMemory<byte> buffer)
public unsafe bool Inject(ReadOnlyMemory<byte> packet)
{
fixed (byte* ptr = buffer.Span)
if (winDivert == null) return false;
IPV4Packet ipv4 = new IPV4Packet(packet.Span);
//不是 ipv4是虚拟网卡ip是广播不nat
if (ipv4.Version != ProtocolType.IPv4 || ipv4.DstAddr == srcIp || ipv4.DstAddrSpan.GetIsBroadcastAddress()) return false;
fixed (byte* ptr = packet.Span)
{
foreach (var (i, p) in new WinDivertIndexedPacketParser(buffer))
foreach (var (i, p) in new WinDivertIndexedPacketParser(packet))
{
bool result = (ProtocolType)p.IPv4Hdr->Protocol switch
//本机网卡IP不需要改直接注入就可以
if (p.IPv4Hdr->DstAddr != interfaceAddr)
{
ProtocolType.Icmp => InjectIcmp(p, ptr),
ProtocolType.Tcp => InjectTcp(p, ptr),
ProtocolType.Udp => InjectUdp(p, ptr),
_ => false,
};
if (result == false) return false;
WinDivertAddress addr = new WinDivertAddress
{
Layer = WinDivert.Layer.Network,
Outbound = true,
IPv6 = false
};
bool result = (ProtocolType)p.IPv4Hdr->Protocol switch
{
ProtocolType.Icmp => InjectIcmp(p, ptr),
ProtocolType.Tcp => InjectTcp(p, ptr),
ProtocolType.Udp => InjectUdp(p, ptr),
_ => false,
};
if (result == false) return false;
}
WinDivert.CalcChecksums(p.Packet.Span, ref addr, 0);
winDivert.SendEx(p.Packet.Span, new ReadOnlySpan<WinDivertAddress>(ref addr));
}
@@ -184,15 +209,16 @@ namespace linker.tun
//只操作response 和 request
if (p.ICMPv4Hdr->Type != 0 && p.ICMPv4Hdr->Type != 8) return false;
//IP头长度
byte ipHeaderLength = (byte)((p.Packet.Span[0] & 0b1111) * 4);
IPV4Packet ipv4 = new IPV4Packet(ptr);
if (ipv4.IsFragment) return false;
//原标识符,两个字节
byte* ptr0 = ptr + ipHeaderLength + 4;
byte* ptr1 = ptr + ipHeaderLength + 5;
byte* ptr0 = ipv4.IcmpIdentifier0;
byte* ptr1 = ipv4.IcmpIdentifier1;
//用源地址的第三个,第四个字节作为新的标识符
byte identifier0 = p.Packet.Span[14];
byte identifier1 = p.Packet.Span[15];
byte identifier0 = ipv4.SrcAddrSpan[2];
byte identifier1 = ipv4.SrcAddrSpan[3];
//保存源地址。标识符0目的地址标识符1ICMP
//取值目的地址标识符0源地址标识符1ICMP
@@ -225,12 +251,12 @@ namespace linker.tun
{
//只操作response 和 request
if (p.ICMPv4Hdr->Type != 0 && p.ICMPv4Hdr->Type != 8) return false;
//IP头长度
byte ipHeaderLength = (byte)((*ptr & 0b1111) * 4);
IPV4Packet ipv4 = new IPV4Packet(ptr);
//标识符,两个字节
byte* ptr0 = ptr + ipHeaderLength + 4;
byte* ptr1 = ptr + ipHeaderLength + 5;
byte* ptr0 = ipv4.IcmpIdentifier0;
byte* ptr1 = ipv4.IcmpIdentifier1;
ValueTuple<uint, ushort, uint, ushort, ProtocolType> key = (p.IPv4Hdr->DstAddr.Raw, *ptr0, p.IPv4Hdr->SrcAddr.Raw, *ptr1, ProtocolType.Icmp);
if (natMap.TryRemove(key, out NatMapInfo natMapInfo))
@@ -244,46 +270,143 @@ namespace linker.tun
}
return false;
}
/// <summary>
/// 注入TCP
/// </summary>
/// <param name="p"></param>
/// <param name="ptr"></param>
/// <returns></returns>
private unsafe bool InjectTcp(WinDivertParseResult p, byte* ptr)
{
return false;
byte ipHeaderLength = (byte)((p.Packet.Span[0] & 0b1111) * 4);
IPV4Packet ipv4 = new IPV4Packet(ptr);
byte* ptr0 = ptr + ipHeaderLength + 4;
byte* ptr1 = ptr + ipHeaderLength + 5;
byte identifier0 = p.Packet.Span[14];
byte identifier1 = p.Packet.Span[15];
ValueTuple<uint, ushort, uint, ushort, ProtocolType> key = (interfaceAddr.Raw, identifier0, p.IPv4Hdr->DstAddr.Raw, identifier1, ProtocolType.Icmp);
NatMapInfo natMapInfo = new NatMapInfo
//新端口
ValueTuple<uint, ushort> portKey = (p.IPv4Hdr->SrcAddr.Raw, p.TCPHdr->SrcPort);
if (source2portMap.TryGetValue(portKey, out ushort newPort) == false)
{
SrcAddr = p.IPv4Hdr->SrcAddr,
Identifier0 = *ptr0,
Identifier1 = *ptr1,
LastTime = Environment.TickCount64
};
natMap.AddOrUpdate(key, natMapInfo, (a, b) => natMapInfo);
//只在syn时建立
if (ipv4.TcpFlagSyn == false || ipv4.TcpFlagAck) return false;
newPort = ApplyNewPort();
source2portMap.TryAdd(portKey, newPort);
}
//添加映射
ValueTuple<uint, ushort, uint, ushort, ProtocolType> key = (interfaceAddr.Raw, newPort, p.IPv4Hdr->DstAddr.Raw, p.TCPHdr->DstPort, ProtocolType.Tcp);
if (natMap.TryGetValue(key, out NatMapInfo natMapInfo) == false)
{
natMapInfo = new NatMapInfo
{
SrcAddr = p.IPv4Hdr->SrcAddr,
SrcPort = p.TCPHdr->SrcPort,
LastTime = Environment.TickCount64
};
natMap.TryAdd(key, natMapInfo);
}
natMapInfo.LastTime = Environment.TickCount64;
//fin+ack 或者 rst 就清除
if (ipv4.TcpFlagFin) natMapInfo.Fin0 = ipv4.TcpFlagFin;
if (ipv4.TcpFlagRst) natMapInfo.Rst = ipv4.TcpFlagRst;
if (natMapInfo.Fin0 && ipv4.TcpFlagAck) natMapInfo.FinAck = ipv4.TcpFlagAck;
*ptr0 = identifier0;
*ptr1 = identifier1;
p.IPv4Hdr->SrcAddr = interfaceAddr;
p.TCPHdr->SrcPort = newPort;
return true;
}
/// <summary>
/// 还原TCP
/// </summary>
/// <param name="p"></param>
/// <param name="ptr"></param>
/// <returns></returns>
private unsafe bool RecvTcp(WinDivertParseResult p, byte* ptr)
{
IPV4Packet ipv4 = new IPV4Packet(ptr);
ValueTuple<uint, ushort, uint, ushort, ProtocolType> key = (p.IPv4Hdr->DstAddr.Raw, p.TCPHdr->DstPort, p.IPv4Hdr->SrcAddr.Raw, p.TCPHdr->SrcPort, ProtocolType.Tcp);
if (natMap.TryGetValue(key, out NatMapInfo natMapInfo))
{
natMapInfo.LastTime = Environment.TickCount64;
//fin+ack 或者 rst 就清除
if (ipv4.TcpFlagFin) natMapInfo.Fin1 = ipv4.TcpFlagFin;
if (ipv4.TcpFlagRst) natMapInfo.Rst = ipv4.TcpFlagRst;
if (natMapInfo.Fin1 && ipv4.TcpFlagAck) natMapInfo.FinAck = ipv4.TcpFlagAck;
p.IPv4Hdr->DstAddr = natMapInfo.SrcAddr;
p.TCPHdr->DstPort = natMapInfo.SrcPort;
return true;
}
return false;
}
/// <summary>
/// 注入UDP
/// </summary>
/// <param name="p"></param>
/// <param name="ptr"></param>
/// <returns></returns>
private unsafe bool InjectUdp(WinDivertParseResult p, byte* ptr)
{
//新端口
ValueTuple<uint, ushort> portKey = (p.IPv4Hdr->SrcAddr.Raw, p.UDPHdr->SrcPort);
if (source2portMap.TryGetValue(portKey, out ushort newPort) == false)
{
newPort = ApplyNewPort();
source2portMap.TryAdd(portKey, newPort);
}
//映射
ValueTuple<uint, ushort, uint, ushort, ProtocolType> key = (interfaceAddr.Raw, newPort, p.IPv4Hdr->DstAddr.Raw, p.UDPHdr->DstPort, ProtocolType.Tcp);
if (natMap.TryGetValue(key, out NatMapInfo natMapInfo) == false)
{
natMapInfo = new NatMapInfo
{
SrcAddr = p.IPv4Hdr->SrcAddr,
SrcPort = p.UDPHdr->SrcPort,
LastTime = Environment.TickCount64
};
natMap.TryAdd(key, natMapInfo);
}
natMapInfo.LastTime = Environment.TickCount64;
p.IPv4Hdr->SrcAddr = interfaceAddr;
p.UDPHdr->SrcPort = newPort;
return true;
}
/// <summary>
/// 还原UDP
/// </summary>
/// <param name="p"></param>
/// <param name="ptr"></param>
/// <returns></returns>
private unsafe bool RecvUdp(WinDivertParseResult p, byte* ptr)
{
ValueTuple<uint, ushort, uint, ushort, ProtocolType> key = (p.IPv4Hdr->DstAddr.Raw, p.UDPHdr->DstPort, p.IPv4Hdr->SrcAddr.Raw, p.UDPHdr->SrcPort, ProtocolType.Tcp);
if (natMap.TryGetValue(key, out NatMapInfo natMapInfo))
{
natMapInfo.LastTime = Environment.TickCount64;
p.IPv4Hdr->DstAddr = natMapInfo.SrcAddr;
p.UDPHdr->DstPort = natMapInfo.SrcPort;
return true;
}
return false;
}
private unsafe bool RecvUdp(WinDivertParseResult p, byte* ptr) { return false; }
/// <summary>
/// 注销
/// 申请一个新的端口
/// </summary>
public void Dispose()
/// <returns></returns>
private ushort ApplyNewPort()
{
using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.Bind(new IPEndPoint(IPAddress.Any, 0));
return (ushort)(socket.LocalEndPoint as IPEndPoint).Port;
}
/// <summary>
/// 关闭
/// </summary>
public void Shutdown()
{
cts?.Cancel();
@@ -291,17 +414,22 @@ namespace linker.tun
winDivert = null;
natMap.Clear();
source2portMap.Clear();
}
private void ClearTask()
private void ClearTask(CancellationTokenSource cts)
{
TimerHelper.SetIntervalLong(() =>
{
long now = Environment.TickCount64;
foreach (var item in natMap.Where(c => now - c.Value.LastTime > 1 * 60 * 60).Select(c => c.Key).ToList())
foreach (var item in natMap.Where(c => now - c.Value.LastTime > 1 * 60 * 60 || c.Value.FinAck || c.Value.Rst).Select(c => c.Key).ToList())
{
natMap.TryRemove(item, out _);
if (natMap.TryRemove(item, out NatMapInfo natMapInfo))
{
source2portMap.TryRemove((natMapInfo.SrcAddr.Raw, natMapInfo.SrcPort), out _);
}
}
return cts.IsCancellationRequested == false;
}, 5000);
}
public sealed class AddrInfo
@@ -336,10 +464,112 @@ namespace linker.tun
}
sealed class NatMapInfo
{
//IP头
public NetworkIPv4Addr SrcAddr { get; set; }
//TCP/UDP
public NetworkUInt16 SrcPort { get; set; }
//ICMP
public byte Identifier0 { get; set; }
public byte Identifier1 { get; set; }
//TCP
public bool Fin0 { get; set; }
public bool Fin1 { get; set; }
public bool FinAck { get; set; }
public bool Rst { get; set; }
public long LastTime { get; set; } = Environment.TickCount64;
}
/// <summary>
/// IPV4 包
/// </summary>
unsafe struct IPV4Packet
{
byte* ptr;
/// <summary>
/// 协议版本
/// </summary>
public ProtocolType Version => (ProtocolType)((*ptr >> 4) & 0b1111);
/// <summary>
/// 源地址
/// </summary>
public uint SrcAddr => BinaryPrimitives.ReverseEndianness(*(uint*)(ptr + 12));
/// <summary>
/// 目的地址
/// </summary>
public uint DstAddr => BinaryPrimitives.ReverseEndianness(*(uint*)(ptr + 16));
/// <summary>
/// 源地址
/// </summary>
public ReadOnlySpan<byte> SrcAddrSpan => new Span<byte>((ptr + 12), 4);
/// <summary>
/// 目的地址
/// </summary>
public ReadOnlySpan<byte> DstAddrSpan => new Span<byte>((ptr + 16), 4);
/// <summary>
/// IP头长度
/// </summary>
public int IPHeadLength => (*ptr & 0b1111) * 4;
/// <summary>
/// IP Flag
/// </summary>
public byte Flag => (byte)(*(ptr + 6) >> 5);
/// <summary>
/// 不分片
/// </summary>
public bool DontFragment => (Flag & 0x02) == 2;
/// <summary>
/// 更多分片
/// </summary>
public bool MoreFragment => (Flag & 0x01) == 1;
/// <summary>
/// 分片偏移量
/// </summary>
public ushort Offset => (ushort)(BinaryPrimitives.ReverseEndianness(*(ushort*)(ptr + 6)) & 0x1fff);
/// <summary>
/// 是否分片
/// </summary>
public bool IsFragment => MoreFragment || Offset > 0;
/// <summary>
/// ICMP标志第一个字节
/// </summary>
public byte* IcmpIdentifier0 => ptr + IPHeadLength + 4;
/// <summary>
/// ICMP标志第二个字节
/// </summary>
public byte* IcmpIdentifier1 => ptr + IPHeadLength + 5;
/// <summary>
/// TCP Flag
/// </summary>
public byte TcpFlag => *(ptr + IPHeadLength + 13);
public bool TcpFlagFin => (TcpFlag & 0b000001) != 0;
public bool TcpFlagSyn => (TcpFlag & 0b000010) != 0;
public bool TcpFlagRst => (TcpFlag & 0b000100) != 0;
public bool TcpFlagPsh => (TcpFlag & 0b001000) != 0;
public bool TcpFlagAck => (TcpFlag & 0b010000) != 0;
public bool TcpFlagUrg => (TcpFlag & 0b100000) != 0;
public IPV4Packet(byte* ptr)
{
this.ptr = ptr;
}
public IPV4Packet(ReadOnlySpan<byte> span)
{
fixed (byte* ptr = span)
{
this.ptr = ptr;
}
}
}
}
}

View File

@@ -195,6 +195,9 @@ span.split-pad10 {
.green {
color: green !important;
}
.app-nat {
color: #018a81 !important;
}
.yellow {
color: #e68906 !important;

View File

@@ -10,6 +10,9 @@
<template v-else-if="tuntap.list[item.MachineId].Upgrade && tuntap.list[item.MachineId].NatError">
<strong class="yellow" :title="tuntap.list[item.MachineId].NatError">{{ tuntap.list[item.MachineId].IP }}</strong>
</template>
<template v-else-if="tuntap.list[item.MachineId].AppNat">
<strong class="app-nat" title="虚拟网卡IP系统NAT失败已启用内置应用层SNAT">{{ tuntap.list[item.MachineId].IP }}</strong>
</template>
<template v-else>
<template v-if=" item.Connected && tuntap.list[item.MachineId].running">
<strong class="green gateway">{{ tuntap.list[item.MachineId].IP }}</strong>

View File

@@ -22,7 +22,8 @@
<Company>snltty</Company>
<Description>1. 优化自动分配IP
2. 优化网卡,排除不明数据包
3. 虚拟网卡点对网IP映射用于解决网段冲突</Description>
3. 虚拟网卡点对网IP映射用于解决网段冲突
4. 内置应用层SNAT用于无法使用系统NAT的windows系统</Description>
<Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>

View File

@@ -1,5 +1,6 @@
v1.7.3
2025-04-20 00:00:20
2025-04-20 17:21:43
1. 优化自动分配IP
2. 优化网卡,排除不明数据包
3. 虚拟网卡点对网IP映射用于解决网段冲突
3. 虚拟网卡点对网IP映射用于解决网段冲突
4. 内置应用层SNAT用于无法使用系统NAT的windows系统