mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-01-12 13:55:37 +08:00
Proxy: Add TUN inbound for Windows & Linux, including Android (#5464)
* Proxy: Implement tun raw network interface inbound support for Linux * Proxy: Tun. Include "android" as build condition for build of tun_default implementation * Proxy: Tun. Add .Close() cleanup calls to Handler.Init() where needed * Proxy: Add Tun for Android * Proxy: Tun. Implement Windows support --------- Co-authored-by: yuhan6665 <1588741+yuhan6665@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,8 @@ const (
|
||||
BrowserDialerAddress = "xray.browser.dialer"
|
||||
XUDPLog = "xray.xudp.show"
|
||||
XUDPBaseKey = "xray.xudp.basekey"
|
||||
|
||||
TunFdKey = "xray.tun.fd"
|
||||
)
|
||||
|
||||
type EnvFlag struct {
|
||||
|
||||
30
infra/conf/tun.go
Normal file
30
infra/conf/tun.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/proxy/tun"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type TunConfig struct {
|
||||
Name string `json:"name"`
|
||||
MTU uint32 `json:"MTU"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
}
|
||||
|
||||
func (v *TunConfig) Build() (proto.Message, error) {
|
||||
config := &tun.Config{
|
||||
Name: v.Name,
|
||||
MTU: v.MTU,
|
||||
UserLevel: v.UserLevel,
|
||||
}
|
||||
|
||||
if v.Name == "" {
|
||||
config.Name = "xray0"
|
||||
}
|
||||
|
||||
if v.MTU == 0 {
|
||||
config.MTU = 1500
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
@@ -28,6 +28,7 @@ var (
|
||||
"vmess": func() interface{} { return new(VMessInboundConfig) },
|
||||
"trojan": func() interface{} { return new(TrojanServerConfig) },
|
||||
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: false} },
|
||||
"tun": func() interface{} { return new(TunConfig) },
|
||||
}, "protocol", "settings")
|
||||
|
||||
outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
|
||||
174
proxy/tun/README.md
Normal file
174
proxy/tun/README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# TUN network layer 3 input support
|
||||
|
||||
TUN interface support bridges the gap between network layer 3 and layer 7, introducing raw network input.
|
||||
|
||||
This functionality is targeted to assist applications/end devices that don't have proxy support, or can't run external applications (like Smart TV's). Making it possible to run Xray proxy right on network edge devices (routers) with support to route raw network traffic. \
|
||||
Primary targets are Linux based router devices. Like most popular OpenWRT option. \
|
||||
Although support for Windows is also implemented (see below).
|
||||
|
||||
## PLEASE READ FOLLOWING CAREFULLY
|
||||
|
||||
If you are not sure what this is and do you need it or not - you don't. \
|
||||
This functionality is intended to be configured by network professionals, who understand the deployment case and scenarios. \
|
||||
Plainly enabling it in the config probably will result nothing, or lock your router up in infinite network loop.
|
||||
|
||||
## DETAILS
|
||||
|
||||
Current implementation does not contain options to configure network level addresses, routing or rules.
|
||||
Enabling the feature will result only tun interface up, and that's it. \
|
||||
This is explicit decision, significantly simplifying implementation, and allowing any number of custom configurations, consumers could come up with. Network interface is OS level entity, and OS is what should manage it. \
|
||||
Working configuration, is tun enabled in Xray config with specific name (e.g. xray0), and OS level configuration to manage "xray0" interface, applying routing and rules on interface up.
|
||||
This way consistency of system level routing and rules is ensured from single place of responsibility - the OS itself. \
|
||||
Examples of how to achieve this on a simple Linux system (Ubuntu with systemd-networkd) can be found at the end of this README.
|
||||
|
||||
Due to this inbound not actually being a proxy, the configuration ignore required listen and port options, and never listen on any port. \
|
||||
Here is simple Xray config snippet to enable the inbound:
|
||||
```
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 0,
|
||||
"protocol": "tun",
|
||||
"settings": {
|
||||
"name": "xray0",
|
||||
"MTU": 1492
|
||||
}
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
## SUPPORTED FEATURES
|
||||
|
||||
- IPv4 and IPv6
|
||||
- TCP and UDP
|
||||
|
||||
## LIMITATION
|
||||
|
||||
- No ICMP support
|
||||
- Connections are established to any host, as connection success is only a mark of successful accepting packet for proxying. Hosts that are not accepting connections or don't even exists, will look like they opened a connection (SYN-ACK), and never send back a single byte, closing connection (RST) after some time. This is the side effect of the whole process actually being a proxy, and not real network layer 3 vpn
|
||||
|
||||
## CONSIDERATIONS
|
||||
|
||||
This feature being network level interface that require raw routing, bring some ambiguities that need to be taken in account. \
|
||||
Xray-core itself is connecting to its uplinks on a network level, therefore, it's really simple to lock network up in an infinite loop, when trying to pass "everything through Xray". \
|
||||
You can't just route 0.0.0.0/0 through xray0 interface, as that will result Xray-core itself try to reach its uplink through xray0 interface, resulting infinite network loop.
|
||||
There are several ways to address this:
|
||||
|
||||
- Approach 1: \
|
||||
Add precise static route to Xray upstreams, having them always routed through static internet gateway.
|
||||
E.g. when 123.123.123.123 is the Xray VLESS uplink, this network configuration will work just fine:
|
||||
```
|
||||
ip route add 123.123.123.123/32 via <provider internet gateway ip>
|
||||
ip route add 0.0.0.0/0 dev xray0
|
||||
```
|
||||
This has disadvantages, - a lot of conditions must be kept static: internet gateway address, xray uplink ip address, and so on.
|
||||
- Approach 1-b: \
|
||||
Route only specific networks through Xray, keeping the default gateway unchanged.
|
||||
This can be done in many different ways, using ip sets, routing daemons like BGP peers, etc... All you need to do is to route the paths through xray0 dev.
|
||||
The disadvantage in this case is smaller, - you need to make sure the uplink will not become part of those sets and that's it. Can easily be done with route metric priorities.
|
||||
- Approach 2: \
|
||||
Separate main route table and Xray route table with default gateways pointing to different destinations.
|
||||
This way you can achieve full protection of hosts behind the router, keeping router configuration as flexible as desired. \
|
||||
There are two ways to do that: \
|
||||
Either configure xray0 interface to appear and operate as default gateway in a separate route table, e.g. 1001. Then mark and route protected traffic by ip rules to that table. \
|
||||
It's a simplest way to make a "non-damaging" configuration, when the only thing you need to do to enable/disable proxying is to flip the ip rules off. Which is also a disadvantage of itself - if by accident ip rules will get disabled, the traffic will spill out of the internet interface unprotected. \
|
||||
Or, other way around, move default routing to a separate route table, so that all usual routing information is set in e.g. route table 1000,
|
||||
and Xray interface operate in the main route table. This will allow proper flexibility, but you need to ensure traffic from the Xray process, running on the router, is marked to get routed through table 1000. This again can be achieved in same ways, using ip rules and iptable rules combinations. \
|
||||
Big advantage of that, is that traffic of the route itself is going to be wrapped into the proxy, including DNS queries, without any additional effort. Although, the disadvantage of that is, that in any case proxying stops (uplink dies, Xray hangs, encryption start to flap), it will result complete internet inaccessibility. \
|
||||
Any approach is applicable, and if you know what you are doing (which is expected, if you read until here) you do understand which one you want and can manage. \
|
||||
|
||||
### Important:
|
||||
|
||||
TUN is network level entity, therefore communication through it, is always ip to ip, there are no host names. \
|
||||
Therefore, DNS resolution will always happen before traffic even enter the interface (it will be separate ip-to-ip packets/connections to resolve hostnames). \
|
||||
You always need to consider that DNS queries in any configuration you chose, most likely, will originate from the router itself (hosts behind the router access router DNS, router DNS fire queries to the outside).
|
||||
Without proper addressing that, DNS queries will expose actual destinations/websites accessed through the router. \
|
||||
To address that you can as ignore (not use) DNS of the router (just delegate some public DNS in DHCP configuration to your devices), or make sure routing rules are configured the way, DNS resolution of the router itself runs through Xray interface/routing table.
|
||||
|
||||
You also need to remember that local traffic of the router (e.g. DNS, firmware updates, etc.), is subject of firewall rules as outcoming/incoming traffic (not forward).
|
||||
If you have restrictive firewall, you need to allow input/output traffic through xray0 interface, for it to properly dispatch and reach the OS back.
|
||||
|
||||
Additionally, working with two route tables is not taken lightly by Linux, and sometimes make it panic about "martian packets", which it calls the packets arriving through interfaces it does not expect they could arrive from. \
|
||||
It was just a warning until recent kernel versions, but now traffic is often dropped. \
|
||||
In simple case this can be just disabled with
|
||||
```
|
||||
/usr/sbin/sysctl -w net.ipv4.conf.all.rp_filter=0
|
||||
```
|
||||
But proper approach should be defining your route tables fully and consistently, adding all routes corresponding to traffic that flow through them.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
systemd-networkd \
|
||||
configuration file you can place in /etc/systemd/networkd as 90-xray0.network
|
||||
which will configure xray0 interface and routing using route table 1001, when the interface will appear in the system (Xray starts). And deconfigure when disappears.
|
||||
```
|
||||
[Match]
|
||||
Name = xray0
|
||||
|
||||
[Network]
|
||||
KeepConfiguration = yes
|
||||
|
||||
[Link]
|
||||
ActivationPolicy = manual
|
||||
RequiredForOnline = no
|
||||
|
||||
[Route]
|
||||
Table = 1001
|
||||
Destination = 0.0.0.0/0
|
||||
|
||||
[RoutingPolicyRule]
|
||||
From = 192.168.0.0/24
|
||||
Table = 1001
|
||||
```
|
||||
RoutingPolicyRule will add the record into ip rules, that will funnel all traffic from 192.168.0.0/24 through the table 1001 \
|
||||
Please note that for ideal configuration of the routing you will also need to add the route to 192.168.0.0/24 to the route table 1001.
|
||||
You can do that e.g. in the file, describing your adapter serving local network (e.g. 10-br0.network), additionally to its native properties like:
|
||||
```
|
||||
[Match]
|
||||
Name = br0
|
||||
Type = bridge
|
||||
|
||||
[Network]
|
||||
...skip...
|
||||
Address = 192.168.0.1/24
|
||||
...skip...
|
||||
|
||||
[Route]
|
||||
Table = 1001
|
||||
Destination = 192.168.0.0/24
|
||||
PreferredSource = 192.168.0.1
|
||||
Scope = link
|
||||
```
|
||||
All in all systemd-networkd and its derivatives (like netplan or NetworkManager) provide all means to configure your networking, according to your wish, that will ensure network consistency of xray0 interface coming up and down, relative to other network configuration like internet interfaces, nat rules and so on.
|
||||
|
||||
## WINDOWS SUPPORT
|
||||
|
||||
Windows version of the same functionality is implemented through Wintun library. \
|
||||
To make it start, wintun.dll specific for your Windows/arch must be present next to Xray.exe binary.
|
||||
|
||||
After the start network adapter with the name you chose in the config will be created in the system, and exist while Xray is running.
|
||||
|
||||
You can give the adapter ip address manually, you can live Windows to give it autogenerated ip address (which take few seconds), it doesn't matter, the traffic going _through_ the interface will be forwarded into the app for proxying. \
|
||||
Minimal configuration that will work for local machine is routing passing the traffic on-link through the interface.
|
||||
You will need the interface id for that, unfortunately it is going to change with every Xray start due to implementation ambiguity between Xray and wintun driver.
|
||||
You can find the interface id with the command
|
||||
```
|
||||
route print
|
||||
```
|
||||
it will be in the list of interfaces on the top of the output
|
||||
```
|
||||
===========================================================================
|
||||
Interface List
|
||||
8...cc cc cc cc cc cc ......Realtek PCIe GbE Family Controller
|
||||
47...........................Xray Tunnel
|
||||
1...........................Software Loopback Interface 1
|
||||
===========================================================================
|
||||
```
|
||||
In this case the interface id is "47". \
|
||||
Then you can add on-link route through the adapter with (example) command
|
||||
```
|
||||
route add 1.1.1.1 mask 255.0.0.0 0.0.0.0 if 47
|
||||
```
|
||||
Note on ipv6 support. \
|
||||
Despite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \
|
||||
So everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine
|
||||
1
proxy/tun/config.go
Normal file
1
proxy/tun/config.go
Normal file
@@ -0,0 +1 @@
|
||||
package tun
|
||||
149
proxy/tun/config.pb.go
Normal file
149
proxy/tun/config.pb.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v6.30.2
|
||||
// source: proxy/tun/config.proto
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
MTU uint32 `protobuf:"varint,2,opt,name=MTU,proto3" json:"MTU,omitempty"`
|
||||
UserLevel uint32 `protobuf:"varint,3,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Config) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMTU() uint32 {
|
||||
if x != nil {
|
||||
return x.MTU
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetUserLevel() uint32 {
|
||||
if x != nil {
|
||||
return x.UserLevel
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e,
|
||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x75, 0x6e, 0x22, 0x4d,
|
||||
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x4d, 0x54, 0x55, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x4d, 0x54, 0x55, 0x12, 0x1d,
|
||||
0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x4c, 0x0a,
|
||||
0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
|
||||
0x74, 0x75, 0x6e, 0x50, 0x01, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x75, 0x6e, 0xaa, 0x02, 0x0e, 0x58, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x75, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_config_proto_rawDescOnce sync.Once
|
||||
file_config_proto_rawDescData = file_config_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_config_proto_rawDescGZIP() []byte {
|
||||
file_config_proto_rawDescOnce.Do(func() {
|
||||
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
|
||||
})
|
||||
return file_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.proxy.tun.Config
|
||||
}
|
||||
var file_config_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_config_proto_init() }
|
||||
func file_config_proto_init() {
|
||||
if File_config_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_config_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_config_proto_goTypes,
|
||||
DependencyIndexes: file_config_proto_depIdxs,
|
||||
MessageInfos: file_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_config_proto = out.File
|
||||
file_config_proto_rawDesc = nil
|
||||
file_config_proto_goTypes = nil
|
||||
file_config_proto_depIdxs = nil
|
||||
}
|
||||
13
proxy/tun/config.proto
Normal file
13
proxy/tun/config.proto
Normal file
@@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.proxy.tun;
|
||||
option csharp_namespace = "Xray.Proxy.Tun";
|
||||
option go_package = "github.com/xtls/xray-core/proxy/tun";
|
||||
option java_package = "com.xray.proxy.tun";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Config {
|
||||
string name = 1;
|
||||
uint32 MTU = 2;
|
||||
uint32 user_level = 3;
|
||||
}
|
||||
142
proxy/tun/handler.go
Normal file
142
proxy/tun/handler.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
c "github.com/xtls/xray-core/common/ctx"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
)
|
||||
|
||||
// Handler is managing object that tie together tun interface, ip stack and dispatch connections to the routing
|
||||
type Handler struct {
|
||||
ctx context.Context
|
||||
config *Config
|
||||
stack Stack
|
||||
policyManager policy.Manager
|
||||
dispatcher routing.Dispatcher
|
||||
}
|
||||
|
||||
// ConnectionHandler interface with the only method that stack is going to push new connections to
|
||||
type ConnectionHandler interface {
|
||||
HandleConnection(conn net.Conn, destination net.Destination)
|
||||
}
|
||||
|
||||
// Handler implements ConnectionHandler
|
||||
var _ ConnectionHandler = (*Handler)(nil)
|
||||
|
||||
func (t *Handler) policy() policy.Session {
|
||||
p := t.policyManager.ForLevel(t.config.UserLevel)
|
||||
return p
|
||||
}
|
||||
|
||||
// Init the Handler instance with necessary parameters
|
||||
func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {
|
||||
var err error
|
||||
|
||||
t.ctx = core.ToBackgroundDetachedContext(ctx)
|
||||
t.policyManager = pm
|
||||
t.dispatcher = dispatcher
|
||||
|
||||
tunName := t.config.Name
|
||||
tunOptions := TunOptions{
|
||||
Name: tunName,
|
||||
MTU: t.config.MTU,
|
||||
}
|
||||
tunInterface, err := NewTun(tunOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errors.LogInfo(t.ctx, tunName, " created")
|
||||
|
||||
tunStackOptions := StackOptions{
|
||||
Tun: tunInterface,
|
||||
IdleTimeout: pm.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
|
||||
}
|
||||
tunStack, err := NewStack(t.ctx, tunStackOptions, t)
|
||||
if err != nil {
|
||||
_ = tunInterface.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
err = tunStack.Start()
|
||||
if err != nil {
|
||||
_ = tunStack.Close()
|
||||
_ = tunInterface.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
err = tunInterface.Start()
|
||||
if err != nil {
|
||||
_ = tunStack.Close()
|
||||
_ = tunInterface.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
t.stack = tunStack
|
||||
|
||||
errors.LogInfo(t.ctx, tunName, " up")
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleConnection pass the connection coming from the ip stack to the routing dispatcher
|
||||
func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
||||
sid := session.NewID()
|
||||
ctx := c.ContextWithID(t.ctx, sid)
|
||||
errors.LogInfo(ctx, "processing connection from: ", conn.RemoteAddr())
|
||||
|
||||
inbound := session.Inbound{}
|
||||
inbound.Name = "tun"
|
||||
inbound.CanSpliceCopy = 1
|
||||
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
|
||||
inbound.User = &protocol.MemoryUser{
|
||||
Level: t.config.UserLevel,
|
||||
}
|
||||
|
||||
ctx = session.ContextWithInbound(ctx, &inbound)
|
||||
ctx = session.SubContextFromMuxInbound(ctx)
|
||||
|
||||
link := &transport.Link{
|
||||
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
||||
Writer: buf.NewWriter(conn),
|
||||
}
|
||||
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
|
||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||
return
|
||||
}
|
||||
|
||||
errors.LogInfo(ctx, "connection completed")
|
||||
}
|
||||
|
||||
// Network implements proxy.Inbound
|
||||
// and exists only to comply to proxy interface, declaring it doesn't listen on any network,
|
||||
// making the process not open any port for this inbound (input will be network interface)
|
||||
func (t *Handler) Network() []net.Network {
|
||||
return []net.Network{}
|
||||
}
|
||||
|
||||
// Process implements proxy.Inbound
|
||||
// and exists only to comply to proxy interface, which should never get any inputs due to no listening ports
|
||||
func (t *Handler) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
t := &Handler{config: config.(*Config)}
|
||||
err := core.RequireFeatures(ctx, func(pm policy.Manager, dispatcher routing.Dispatcher) error {
|
||||
return t.Init(ctx, pm, dispatcher)
|
||||
})
|
||||
return t, err
|
||||
}))
|
||||
}
|
||||
17
proxy/tun/stack.go
Normal file
17
proxy/tun/stack.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Stack interface implement ip protocol stack, bridging raw network packets and data streams
|
||||
type Stack interface {
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// StackOptions for the stack implementation
|
||||
type StackOptions struct {
|
||||
Tun Tun
|
||||
IdleTimeout time.Duration
|
||||
}
|
||||
206
proxy/tun/stack_gvisor.go
Normal file
206
proxy/tun/stack_gvisor.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNIC tcpip.NICID = 1
|
||||
|
||||
tcpRXBufMinSize = tcp.MinBufferSize
|
||||
tcpRXBufDefSize = tcp.DefaultSendBufferSize
|
||||
tcpRXBufMaxSize = 8 << 20 // 8MiB
|
||||
|
||||
tcpTXBufMinSize = tcp.MinBufferSize
|
||||
tcpTXBufDefSize = tcp.DefaultReceiveBufferSize
|
||||
tcpTXBufMaxSize = 6 << 20 // 6MiB
|
||||
)
|
||||
|
||||
// stackGVisor is ip stack implemented by gVisor package
|
||||
type stackGVisor struct {
|
||||
ctx context.Context
|
||||
tun GVisorTun
|
||||
idleTimeout time.Duration
|
||||
handler *Handler
|
||||
stack *stack.Stack
|
||||
endpoint stack.LinkEndpoint
|
||||
}
|
||||
|
||||
// GVisorTun implements a bridge to connect gVisor ip stack to tun interface
|
||||
type GVisorTun interface {
|
||||
newEndpoint() (stack.LinkEndpoint, error)
|
||||
}
|
||||
|
||||
// NewStack builds new ip stack (using gVisor)
|
||||
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
||||
gStack := &stackGVisor{
|
||||
ctx: ctx,
|
||||
tun: options.Tun.(GVisorTun),
|
||||
idleTimeout: options.IdleTimeout,
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
return gStack, nil
|
||||
}
|
||||
|
||||
// Start is called by Handler to bring stack to life
|
||||
func (t *stackGVisor) Start() error {
|
||||
linkEndpoint, err := t.tun.newEndpoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ipStack, err := createStack(linkEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tcpForwarder := tcp.NewForwarder(ipStack, 0, 65535, func(r *tcp.ForwarderRequest) {
|
||||
go func(r *tcp.ForwarderRequest) {
|
||||
var wq waiter.Queue
|
||||
var id = r.ID()
|
||||
|
||||
// Perform a TCP three-way handshake.
|
||||
ep, err := r.CreateEndpoint(&wq)
|
||||
if err != nil {
|
||||
errors.LogError(t.ctx, err.String())
|
||||
r.Complete(true)
|
||||
return
|
||||
}
|
||||
|
||||
options := ep.SocketOptions()
|
||||
options.SetKeepAlive(false)
|
||||
options.SetReuseAddress(true)
|
||||
options.SetReusePort(true)
|
||||
|
||||
t.handler.HandleConnection(
|
||||
gonet.NewTCPConn(&wq, ep),
|
||||
// local address on the gVisor side is connection destination
|
||||
net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)),
|
||||
)
|
||||
|
||||
// close the socket
|
||||
ep.Close()
|
||||
// send connection complete upstream
|
||||
r.Complete(false)
|
||||
}(r)
|
||||
})
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||
|
||||
udpForwarder := udp.NewForwarder(ipStack, func(r *udp.ForwarderRequest) {
|
||||
go func(r *udp.ForwarderRequest) {
|
||||
var wq waiter.Queue
|
||||
var id = r.ID()
|
||||
|
||||
ep, err := r.CreateEndpoint(&wq)
|
||||
if err != nil {
|
||||
errors.LogError(t.ctx, err.String())
|
||||
return
|
||||
}
|
||||
|
||||
options := ep.SocketOptions()
|
||||
options.SetReuseAddress(true)
|
||||
options.SetReusePort(true)
|
||||
|
||||
t.handler.HandleConnection(
|
||||
gonet.NewUDPConn(&wq, ep),
|
||||
// local address on the gVisor side is connection destination
|
||||
net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)),
|
||||
)
|
||||
|
||||
// close the socket
|
||||
ep.Close()
|
||||
}(r)
|
||||
})
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
|
||||
|
||||
t.stack = ipStack
|
||||
t.endpoint = linkEndpoint
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is called by Handler to shut down the stack
|
||||
func (t *stackGVisor) Close() error {
|
||||
if t.stack == nil {
|
||||
return nil
|
||||
}
|
||||
t.endpoint.Attach(nil)
|
||||
t.stack.Close()
|
||||
for _, endpoint := range t.stack.CleanupEndpoints() {
|
||||
endpoint.Abort()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createStack configure gVisor ip stack
|
||||
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
|
||||
opts := stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
|
||||
HandleLocal: false,
|
||||
}
|
||||
gStack := stack.New(opts)
|
||||
|
||||
err := gStack.CreateNIC(defaultNIC, ep)
|
||||
if err != nil {
|
||||
return nil, errors.New(err.String())
|
||||
}
|
||||
|
||||
gStack.SetRouteTable([]tcpip.Route{
|
||||
{Destination: header.IPv4EmptySubnet, NIC: defaultNIC},
|
||||
{Destination: header.IPv6EmptySubnet, NIC: defaultNIC},
|
||||
})
|
||||
|
||||
err = gStack.SetSpoofing(defaultNIC, true)
|
||||
if err != nil {
|
||||
return nil, errors.New(err.String())
|
||||
}
|
||||
err = gStack.SetPromiscuousMode(defaultNIC, true)
|
||||
if err != nil {
|
||||
return nil, errors.New(err.String())
|
||||
}
|
||||
|
||||
cOpt := tcpip.CongestionControlOption("cubic")
|
||||
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt)
|
||||
sOpt := tcpip.TCPSACKEnabled(true)
|
||||
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
|
||||
mOpt := tcpip.TCPModerateReceiveBufferOption(true)
|
||||
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)
|
||||
|
||||
tcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{
|
||||
Min: tcpRXBufMinSize,
|
||||
Default: tcpRXBufDefSize,
|
||||
Max: tcpRXBufMaxSize,
|
||||
}
|
||||
err = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpRXBufOpt)
|
||||
if err != nil {
|
||||
return nil, errors.New(err.String())
|
||||
}
|
||||
|
||||
tcpTXBufOpt := tcpip.TCPSendBufferSizeRangeOption{
|
||||
Min: tcpTXBufMinSize,
|
||||
Default: tcpTXBufDefSize,
|
||||
Max: tcpTXBufMaxSize,
|
||||
}
|
||||
err = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpTXBufOpt)
|
||||
if err != nil {
|
||||
return nil, errors.New(err.String())
|
||||
}
|
||||
|
||||
return gStack, nil
|
||||
}
|
||||
13
proxy/tun/tun.go
Normal file
13
proxy/tun/tun.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package tun
|
||||
|
||||
// Tun interface implements tun interface interaction
|
||||
type Tun interface {
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// TunOptions for tun interface implementation
|
||||
type TunOptions struct {
|
||||
Name string
|
||||
MTU uint32
|
||||
}
|
||||
58
proxy/tun/tun_android.go
Normal file
58
proxy/tun/tun_android.go
Normal file
@@ -0,0 +1,58 @@
|
||||
//go:build android
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
type AndroidTun struct {
|
||||
tunFd int
|
||||
options TunOptions
|
||||
}
|
||||
|
||||
// DefaultTun implements Tun
|
||||
var _ Tun = (*AndroidTun)(nil)
|
||||
|
||||
// DefaultTun implements GVisorTun
|
||||
var _ GVisorTun = (*AndroidTun)(nil)
|
||||
|
||||
// NewTun builds new tun interface handler
|
||||
func NewTun(options TunOptions) (Tun, error) {
|
||||
fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" }))
|
||||
errors.LogInfo(context.Background(), "read Android Tun Fd ", fd, err)
|
||||
|
||||
err = unix.SetNonblock(fd, true)
|
||||
if err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AndroidTun{
|
||||
tunFd: fd,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *AndroidTun) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AndroidTun) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return fdbased.New(&fdbased.Options{
|
||||
FDs: []int{t.tunFd},
|
||||
MTU: t.options.MTU,
|
||||
RXChecksumOffload: true,
|
||||
})
|
||||
}
|
||||
34
proxy/tun/tun_default.go
Normal file
34
proxy/tun/tun_default.go
Normal file
@@ -0,0 +1,34 @@
|
||||
//go:build !linux && !windows && !android
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
type DefaultTun struct {
|
||||
}
|
||||
|
||||
// DefaultTun implements Tun
|
||||
var _ Tun = (*DefaultTun)(nil)
|
||||
|
||||
// DefaultTun implements GVisorTun
|
||||
var _ GVisorTun = (*DefaultTun)(nil)
|
||||
|
||||
// NewTun builds new tun interface handler
|
||||
func NewTun(options TunOptions) (Tun, error) {
|
||||
return nil, errors.New("Tun is not supported on your platform")
|
||||
}
|
||||
|
||||
func (t *DefaultTun) Start() error {
|
||||
return errors.New("Tun is not supported on your platform")
|
||||
}
|
||||
|
||||
func (t *DefaultTun) Close() error {
|
||||
return errors.New("Tun is not supported on your platform")
|
||||
}
|
||||
|
||||
func (t *DefaultTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return nil, errors.New("Tun is not supported on your platform")
|
||||
}
|
||||
120
proxy/tun/tun_linux.go
Normal file
120
proxy/tun/tun_linux.go
Normal file
@@ -0,0 +1,120 @@
|
||||
//go:build linux && !android
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// LinuxTun is an object that handles tun network interface on linux
|
||||
// current version is heavily stripped to do nothing more,
|
||||
// then create a network interface, to be provided as file descriptor to gVisor ip stack
|
||||
type LinuxTun struct {
|
||||
tunFd int
|
||||
tunLink netlink.Link
|
||||
options TunOptions
|
||||
}
|
||||
|
||||
// LinuxTun implements Tun
|
||||
var _ Tun = (*LinuxTun)(nil)
|
||||
|
||||
// LinuxTun implements GVisorTun
|
||||
var _ GVisorTun = (*LinuxTun)(nil)
|
||||
|
||||
// NewTun builds new tun interface handler (linux specific)
|
||||
func NewTun(options TunOptions) (Tun, error) {
|
||||
tunFd, err := open(options.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tunLink, err := setup(options.Name, int(options.MTU))
|
||||
if err != nil {
|
||||
_ = unix.Close(tunFd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linuxTun := &LinuxTun{
|
||||
tunFd: tunFd,
|
||||
tunLink: tunLink,
|
||||
options: options,
|
||||
}
|
||||
|
||||
return linuxTun, nil
|
||||
}
|
||||
|
||||
// open the file that implements tun interface in the OS
|
||||
func open(name string) (int, error) {
|
||||
fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
ifr, err := unix.NewIfreq(name)
|
||||
if err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
flags := unix.IFF_TUN | unix.IFF_NO_PI
|
||||
ifr.SetUint16(uint16(flags))
|
||||
err = unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)
|
||||
if err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = unix.SetNonblock(fd, true)
|
||||
if err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// setup the interface through netlink socket
|
||||
func setup(name string, MTU int) (netlink.Link, error) {
|
||||
tunLink, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = netlink.LinkSetMTU(tunLink, MTU)
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetDown(tunLink)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tunLink, nil
|
||||
}
|
||||
|
||||
// Start is called by handler to bring tun interface to life
|
||||
func (t *LinuxTun) Start() error {
|
||||
err := netlink.LinkSetUp(t.tunLink)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is called to shut down the tun interface
|
||||
func (t *LinuxTun) Close() error {
|
||||
_ = netlink.LinkSetDown(t.tunLink)
|
||||
_ = unix.Close(t.tunFd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newEndpoint builds new gVisor stack.LinkEndpoint from the tun interface file descriptor
|
||||
func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return fdbased.New(&fdbased.Options{
|
||||
FDs: []int{t.tunFd},
|
||||
MTU: t.options.MTU,
|
||||
RXChecksumOffload: true,
|
||||
})
|
||||
}
|
||||
84
proxy/tun/tun_windows.go
Normal file
84
proxy/tun/tun_windows.go
Normal file
@@ -0,0 +1,84 @@
|
||||
//go:build windows
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wintun"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// WindowsTun is an object that handles tun network interface on Windows
|
||||
// current version is heavily stripped to do nothing more,
|
||||
// then create a network interface, to be provided as endpoint to gVisor ip stack
|
||||
type WindowsTun struct {
|
||||
options TunOptions
|
||||
adapter *wintun.Adapter
|
||||
session wintun.Session
|
||||
MTU uint32
|
||||
}
|
||||
|
||||
// WindowsTun implements Tun
|
||||
var _ Tun = (*WindowsTun)(nil)
|
||||
|
||||
// WindowsTun implements GVisorTun
|
||||
var _ GVisorTun = (*WindowsTun)(nil)
|
||||
|
||||
// NewTun creates a Wintun interface with the given name. Should a Wintun
|
||||
// interface with the same name exist, it tried to be reused.
|
||||
func NewTun(options TunOptions) (Tun, error) {
|
||||
// instantiate wintun adapter
|
||||
adapter, err := open(options.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// start the interface with ring buffer capacity of 8 MiB
|
||||
session, err := adapter.StartSession(0x800000)
|
||||
if err != nil {
|
||||
_ = adapter.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tun := &WindowsTun{
|
||||
options: options,
|
||||
adapter: adapter,
|
||||
session: session,
|
||||
// there is currently no iphndl.dll support, which is the netlink library for windows
|
||||
// so there is nowhere to change MTU for the Wintun interface, and we take its default value
|
||||
MTU: wintun.PacketSizeMax,
|
||||
}
|
||||
|
||||
return tun, nil
|
||||
}
|
||||
|
||||
func open(name string) (*wintun.Adapter, error) {
|
||||
var guid *windows.GUID
|
||||
// try to open existing adapter by name
|
||||
adapter, err := wintun.OpenAdapter(name)
|
||||
if err == nil {
|
||||
return adapter, nil
|
||||
}
|
||||
// try to create adapter anew
|
||||
adapter, err = wintun.CreateAdapter(name, "Xray", guid)
|
||||
if err == nil {
|
||||
return adapter, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (t *WindowsTun) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *WindowsTun) Close() error {
|
||||
t.session.End()
|
||||
_ = t.adapter.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newEndpoint builds new gVisor stack.LinkEndpoint (WintunEndpoint) on top of WindowsTun
|
||||
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &WintunEndpoint{tun: t}, nil
|
||||
}
|
||||
180
proxy/tun/tun_windows_endpoint.go
Normal file
180
proxy/tun/tun_windows_endpoint.go
Normal file
@@ -0,0 +1,180 @@
|
||||
//go:build windows
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
_ "unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// WintunEndpoint implements GVisor stack.LinkEndpoint
|
||||
var _ stack.LinkEndpoint = (*WintunEndpoint)(nil)
|
||||
|
||||
type WintunEndpoint struct {
|
||||
tun *WindowsTun
|
||||
dispatcherCancel context.CancelFunc
|
||||
}
|
||||
|
||||
var ErrUnsupportedNetworkProtocol = errors.New("unsupported ip version")
|
||||
|
||||
//go:linkname procyield runtime.procyield
|
||||
func procyield(cycles uint32)
|
||||
|
||||
func (e *WintunEndpoint) MTU() uint32 {
|
||||
return e.tun.MTU
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetMTU(mtu uint32) {
|
||||
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
if e.dispatcherCancel != nil {
|
||||
e.dispatcherCancel()
|
||||
e.dispatcherCancel = nil
|
||||
}
|
||||
|
||||
if dispatcher != nil {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go e.dispatchLoop(ctx, dispatcher)
|
||||
e.dispatcherCancel = cancel
|
||||
}
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) IsAttached() bool {
|
||||
return e.dispatcherCancel != nil
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Wait() {
|
||||
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) AddHeader(buffer *stack.PacketBuffer) {
|
||||
// tun interface doesn't have link layer header, it will be added by the OS
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Close() {
|
||||
if e.dispatcherCancel != nil {
|
||||
e.dispatcherCancel()
|
||||
e.dispatcherCancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetOnCloseAction(f func()) {
|
||||
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
|
||||
var n int
|
||||
// for all packets in the list to send
|
||||
for _, packetBuffer := range packetBufferList.AsSlice() {
|
||||
// request buffer from Wintun
|
||||
packet, err := e.tun.session.AllocateSendPacket(packetBuffer.Size())
|
||||
if err != nil {
|
||||
return n, &tcpip.ErrAborted{}
|
||||
}
|
||||
|
||||
// copy the bytes of slices that compose the packet into the allocated buffer
|
||||
var index int
|
||||
for _, packetElement := range packetBuffer.AsSlices() {
|
||||
index += copy(packet[index:], packetElement)
|
||||
}
|
||||
|
||||
// signal Wintun to send that buffer as the packet
|
||||
e.tun.session.SendPacket(packet)
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) readPacket() (tcpip.NetworkProtocolNumber, *stack.PacketBuffer, error) {
|
||||
packet, err := e.tun.session.ReceivePacket()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
var networkProtocol tcpip.NetworkProtocolNumber
|
||||
switch header.IPVersion(packet) {
|
||||
case header.IPv4Version:
|
||||
networkProtocol = header.IPv4ProtocolNumber
|
||||
case header.IPv6Version:
|
||||
networkProtocol = header.IPv6ProtocolNumber
|
||||
default:
|
||||
e.tun.session.ReleaseReceivePacket(packet)
|
||||
return 0, nil, ErrUnsupportedNetworkProtocol
|
||||
}
|
||||
|
||||
packetBuffer := buffer.MakeWithView(buffer.NewViewWithData(packet))
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: packetBuffer,
|
||||
IsForwardedPacket: true,
|
||||
OnRelease: func() {
|
||||
e.tun.session.ReleaseReceivePacket(packet)
|
||||
},
|
||||
})
|
||||
return networkProtocol, pkt, nil
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
|
||||
readWait := e.tun.session.ReadWaitEvent()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
networkProtocolNumber, packet, err := e.readPacket()
|
||||
// read queue empty, yield slightly, wait for the spinlock, retry
|
||||
if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
|
||||
procyield(1)
|
||||
_, _ = windows.WaitForSingleObject(readWait, windows.INFINITE)
|
||||
continue
|
||||
}
|
||||
// discard unknown network protocol packet
|
||||
if errors.Is(err, ErrUnsupportedNetworkProtocol) {
|
||||
continue
|
||||
}
|
||||
// stop dispatcher loop on any other interface failure
|
||||
if err != nil {
|
||||
e.Attach(nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// dispatch the buffer to the stack
|
||||
dispatcher.DeliverNetworkPacket(networkProtocolNumber, packet)
|
||||
// signal the buffer that it can be released
|
||||
packet.DecRef()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user