mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
https://github.com/XTLS/Xray-core/pull/6035#issuecomment-4336755860 Fixes https://github.com/XTLS/Xray-core/issues/6030
310 lines
7.8 KiB
Go
310 lines
7.8 KiB
Go
//go:build windows
|
|
|
|
package tun
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/binary"
|
|
go_errors "errors"
|
|
"net"
|
|
"net/netip"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"golang.org/x/sys/windows"
|
|
"golang.zx2c4.com/wintun"
|
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
)
|
|
|
|
//go:linkname procyield runtime.procyield
|
|
func procyield(cycles uint32)
|
|
|
|
// 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 {
|
|
sync.RWMutex
|
|
|
|
options *Config
|
|
adapter *wintun.Adapter
|
|
session wintun.Session
|
|
readWait windows.Handle
|
|
luid winipcfg.LUID
|
|
changeCallback winipcfg.ChangeCallback
|
|
closed bool
|
|
}
|
|
|
|
// WindowsTun implements Tun
|
|
var _ Tun = (*WindowsTun)(nil)
|
|
|
|
// WindowsTun implements GVisorDevice
|
|
var _ GVisorDevice = (*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 *Config) (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,
|
|
readWait: session.ReadWaitEvent(),
|
|
luid: winipcfg.LUID(adapter.LUID()),
|
|
}
|
|
|
|
return tun, nil
|
|
}
|
|
|
|
func open(name string) (*wintun.Adapter, error) {
|
|
// generate a deterministic GUID from the adapter name
|
|
id := md5.Sum([]byte(name))
|
|
guid := (*windows.GUID)(unsafe.Pointer(&id[0]))
|
|
// 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 {
|
|
var has4, has6 bool
|
|
allowedIPs := make([]netip.Prefix, 0, len(t.options.AutoSystemRoutingTable))
|
|
for _, route := range t.options.AutoSystemRoutingTable {
|
|
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route))
|
|
}
|
|
routesMap := make(map[winipcfg.RouteData]struct{})
|
|
for _, ip := range allowedIPs {
|
|
route := winipcfg.RouteData{
|
|
Destination: ip.Masked(),
|
|
Metric: 0,
|
|
}
|
|
if ip.Addr().Is4() {
|
|
has4 = true
|
|
route.NextHop = netip.IPv4Unspecified()
|
|
} else {
|
|
has6 = true
|
|
route.NextHop = netip.IPv6Unspecified()
|
|
}
|
|
routesMap[route] = struct{}{}
|
|
}
|
|
routesData := make([]*winipcfg.RouteData, 0, len(routesMap))
|
|
for route := range routesMap {
|
|
r := route
|
|
routesData = append(routesData, &r)
|
|
}
|
|
err := t.luid.SetRoutes(routesData)
|
|
if err != nil {
|
|
return errors.New("unable to set routes").Base(err)
|
|
}
|
|
|
|
if len(t.options.Gateway) > 0 {
|
|
addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
|
|
for _, address := range t.options.Gateway {
|
|
addresses = append(addresses, netip.MustParsePrefix(address))
|
|
}
|
|
err := t.luid.SetIPAddresses(addresses)
|
|
if err != nil {
|
|
return errors.New("unable to set ips").Base(err)
|
|
}
|
|
}
|
|
|
|
if has4 {
|
|
ipif, err := t.luid.IPInterface(windows.AF_INET)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
|
ipif.DadTransmits = 0
|
|
ipif.ManagedAddressConfigurationSupported = false
|
|
ipif.OtherStatefulConfigurationSupported = false
|
|
ipif.NLMTU = t.options.MTU
|
|
ipif.UseAutomaticMetric = false
|
|
ipif.Metric = 0
|
|
err = ipif.Set()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if has6 {
|
|
ipif, err := t.luid.IPInterface(windows.AF_INET6)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
|
ipif.DadTransmits = 0
|
|
ipif.ManagedAddressConfigurationSupported = false
|
|
ipif.OtherStatefulConfigurationSupported = false
|
|
ipif.NLMTU = t.options.MTU
|
|
ipif.UseAutomaticMetric = false
|
|
ipif.Metric = 0
|
|
err = ipif.Set()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(t.options.DNS) > 0 {
|
|
dns := make([]netip.Addr, 0, len(t.options.DNS))
|
|
for _, ip := range t.options.DNS {
|
|
dns = append(dns, netip.MustParseAddr(ip))
|
|
}
|
|
err := t.luid.SetDNS(windows.AF_INET, dns, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = t.luid.SetDNS(windows.AF_INET6, dns, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if updater != nil {
|
|
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
|
updater.Update()
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *WindowsTun) Close() error {
|
|
t.Lock()
|
|
defer t.Unlock()
|
|
if t.closed {
|
|
return nil
|
|
}
|
|
t.closed = true
|
|
|
|
if t.changeCallback != nil {
|
|
t.changeCallback.Unregister()
|
|
}
|
|
t.session.End()
|
|
_ = t.adapter.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *WindowsTun) Name() (string, error) {
|
|
row, err := t.luid.Interface()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return row.Alias(), nil
|
|
}
|
|
|
|
func (t *WindowsTun) Index() (int, error) {
|
|
row, err := t.luid.Interface()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int(row.InterfaceIndex), nil
|
|
}
|
|
|
|
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
|
func (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error {
|
|
t.RLock()
|
|
defer t.RUnlock()
|
|
if t.closed {
|
|
return &tcpip.ErrClosedForSend{}
|
|
}
|
|
|
|
// request buffer from Wintun
|
|
packet, err := t.session.AllocateSendPacket(packetBuffer.Size())
|
|
if err != nil {
|
|
return &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
|
|
t.session.SendPacket(packet)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadPacket implements GVisorDevice method to read one packet from the tun device
|
|
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
|
|
// which will make the stack call Wait which should implement desired push-back
|
|
func (t *WindowsTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
|
packet, err := t.session.ReceivePacket()
|
|
if go_errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
|
|
return 0, nil, ErrQueueEmpty
|
|
}
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
version := packet[0] >> 4
|
|
packetBuffer := buffer.MakeWithView(buffer.NewViewWithData(packet))
|
|
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Payload: packetBuffer,
|
|
IsForwardedPacket: true,
|
|
OnRelease: func() {
|
|
t.session.ReleaseReceivePacket(packet)
|
|
},
|
|
}), nil
|
|
}
|
|
|
|
func (t *WindowsTun) Wait() {
|
|
procyield(1)
|
|
_, _ = windows.WaitForSingleObject(t.readWait, windows.INFINITE)
|
|
}
|
|
|
|
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
|
}
|
|
|
|
const (
|
|
IP_UNICAST_IF = 31
|
|
IPV6_UNICAST_IF = 31
|
|
)
|
|
|
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
|
var index [4]byte
|
|
binary.BigEndian.PutUint32(index[:], uint32(iface.Index))
|
|
|
|
var err1, err2, err3, err4 error
|
|
|
|
switch network {
|
|
case "tcp6", "udp6", "ip6":
|
|
err1 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
|
|
if network == "udp6" {
|
|
err2 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
|
|
}
|
|
fallthrough
|
|
case "tcp4", "udp4", "ip4":
|
|
err3 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
|
if network == "udp4" || network == "udp6" {
|
|
err4 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_MULTICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
|
}
|
|
default:
|
|
panic(network + " " + address)
|
|
}
|
|
|
|
return errors.Combine(err1, err2, err3, err4)
|
|
}
|