diff --git a/adapter/service.go b/adapter/service.go index 78c82205..5ed0798d 100644 --- a/adapter/service.go +++ b/adapter/service.go @@ -1,12 +1,6 @@ package adapter -import "io" - -type Starter interface { - Start() error -} - type Service interface { - Starter - io.Closer + Start() error + Close() error } diff --git a/box.go b/box.go index e6d64b1f..4150c906 100644 --- a/box.go +++ b/box.go @@ -177,7 +177,33 @@ func (s *Box) Start() error { for g := 0; g < i; g++ { s.inbounds[g].Close() } - return err + var tag string + if in.Tag() == "" { + tag = F.ToString(i) + } else { + tag = in.Tag() + } + return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") + } + } + for i, out := range s.outbounds { + if starter, isStarter := out.(common.Starter); isStarter { + err = starter.Start() + if err != nil { + for _, in := range s.inbounds { + common.Close(in) + } + for g := 0; g < i; g++ { + common.Close(s.outbounds[g]) + } + var tag string + if out.Tag() == "" { + tag = F.ToString(i) + } else { + tag = out.Tag() + } + return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]") + } } } if s.clashServer != nil { diff --git a/go.mod b/go.mod index 6315a2cc..618eaad2 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/sagernet/sing v0.0.0-20220816094748-fb82be7f3f08 github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48 - github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7 + github.com/sagernet/sing-tun v0.0.0-20220816152948-85c649d9a3e8 github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4 github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 github.com/spf13/cobra v1.5.0 @@ -27,6 +27,13 @@ require ( golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/net v0.0.0-20220812174116-3211cb980234 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab + golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 + gvisor.dev/gvisor v0.0.0-20220801010827-addd1f7b3e97 +) + +replace ( + github.com/sagernet/netlink => ../../GolandProjects/netlink + github.com/sagernet/sing-tun => ../sing-tun ) require ( @@ -46,7 +53,7 @@ require ( github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 // indirect + github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect golang.org/x/mod v0.5.1 // indirect @@ -54,8 +61,8 @@ require ( golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/tools v0.1.9 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gvisor.dev/gvisor v0.0.0-20220801010827-addd1f7b3e97 // indirect lukechampine.com/blake3 v1.1.7 // indirect ) diff --git a/go.sum b/go.sum index 8f0b16dd..485c7e27 100644 --- a/go.sum +++ b/go.sum @@ -150,19 +150,13 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 h1:hE+vtsjBCCPmxkRz9jZA+CicHgVkDT6H+Av5ZzskVxs= -github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= -github.com/sagernet/sing v0.0.0-20220816094310-1b9cf9a6266f h1:fNgFTFkBLi0oJZUZFLs2LbiITblUgxWgZNGoRU/SIXE= -github.com/sagernet/sing v0.0.0-20220816094310-1b9cf9a6266f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.0.0-20220816094748-fb82be7f3f08 h1:Z5UMSxFO+c2GtJqDlU7SF4OqzEV76KNYktTyzhofL9o= github.com/sagernet/sing v0.0.0-20220816094748-fb82be7f3f08/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae h1:xOpbvgizvIbKKrrcl/CK3RjGY2u7rC+SBXlgqzEZOU4= github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM= github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48 h1:NlcTFKldteZvYBDyr+V9MjZEI0rAWCSFCyLgPvc5n/Y= github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM= -github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7 h1:KwUTQUPvdcJtrZR3WImygB0fINaGIr4X42TnDIDJ9sU= -github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7/go.mod h1:+mJ/s6hO3CZyD7CpHbEuZmIVyRkTYLRl4iTr5a57mG0= github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4 h1:2hLETh97+S4WnfMR27XyC7QVU1SH7FTNoCznP229YJU= github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4/go.mod h1:82O6gzbxLha/W/jxSVQbsqf2lVdRTjMIgyLug0lpJps= github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 h1:pB1Dh1NbwVrLhQhotr4O4Hs3yhiBzmg3AvnUyYjL4x4= @@ -319,6 +313,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0= +golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= diff --git a/inbound/default.go b/inbound/default.go index 010f6f76..063da67c 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -325,7 +325,7 @@ func (a *myInboundAdapter) NewError(ctx context.Context, err error) { func NewError(logger log.ContextLogger, ctx context.Context, err error) { common.Close(err) if E.IsClosedOrCanceled(err) { - logger.TraceContext(ctx, "connection closed: ", err) + logger.DebugContext(ctx, "connection closed: ", err) return } logger.ErrorContext(ctx, err) diff --git a/option/outbound.go b/option/outbound.go index d7faf04d..e735e130 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -16,6 +16,7 @@ type _Outbound struct { ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` VMessOptions VMessOutboundOptions `json:"-"` TrojanOptions TrojanOutboundOptions `json:"-"` + WireGuardOptions WireGuardOutboundOptions `json:"-"` SelectorOptions SelectorOutboundOptions `json:"-"` } @@ -38,6 +39,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) { v = h.VMessOptions case C.TypeTrojan: v = h.TrojanOptions + case C.TypeWireGuard: + v = h.WireGuardOptions case C.TypeSelector: v = h.SelectorOptions default: @@ -67,6 +70,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error { v = &h.VMessOptions case C.TypeTrojan: v = &h.TrojanOptions + case C.TypeWireGuard: + v = &h.WireGuardOptions case C.TypeSelector: v = &h.SelectorOptions default: diff --git a/option/wireguard.go b/option/wireguard.go new file mode 100644 index 00000000..f9846968 --- /dev/null +++ b/option/wireguard.go @@ -0,0 +1,12 @@ +package option + +type WireGuardOutboundOptions struct { + OutboundDialerOptions + ServerOptions + LocalAddress Listable[string] `json:"local_address"` + PrivateKey string `json:"private_key"` + PeerPublicKey string `json:"peer_public_key"` + PreSharedKey string `json:"pre_shared_key,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + Network NetworkList `json:"network,omitempty"` +} diff --git a/outbound/builder.go b/outbound/builder.go index 32f7114d..843faa3f 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -31,6 +31,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions) case C.TypeTrojan: return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions) + case C.TypeWireGuard: + return NewWireGuard(ctx, router, logger, options.Tag, options.WireGuardOptions) case C.TypeSelector: return NewSelector(router, logger, options.Tag, options.SelectorOptions) default: diff --git a/outbound/trojan.go b/outbound/trojan.go index 361221c3..5d1e34c6 100644 --- a/outbound/trojan.go +++ b/outbound/trojan.go @@ -28,7 +28,7 @@ type Trojan struct { } func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { - inbound := &Trojan{ + outbound := &Trojan{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeTrojan, network: options.Network.Build(), @@ -40,15 +40,15 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog key: trojan.Key(options.Password), } var err error - inbound.dialer, err = dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions)) + outbound.dialer, err = dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions)) if err != nil { return nil, err } - inbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*TrojanDialer)(inbound), common.PtrValueOrDefault(options.MultiplexOptions)) + outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*TrojanDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions)) if err != nil { return nil, err } - return inbound, nil + return outbound, nil } func (h *Trojan) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/outbound/vmess.go b/outbound/vmess.go index 9c12eaf0..0dea395c 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -39,7 +39,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg if err != nil { return nil, err } - inbound := &VMess{ + outbound := &VMess{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeVMess, network: options.Network.Build(), @@ -50,15 +50,15 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg client: client, serverAddr: options.ServerOptions.Build(), } - inbound.dialer, err = dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions)) + outbound.dialer, err = dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions)) if err != nil { return nil, err } - inbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*vmessDialer)(inbound), common.PtrValueOrDefault(options.MultiplexOptions)) + outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*vmessDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions)) if err != nil { return nil, err } - return inbound, nil + return outbound, nil } func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/outbound/wireguard.go b/outbound/wireguard.go new file mode 100644 index 00000000..92285fe5 --- /dev/null +++ b/outbound/wireguard.go @@ -0,0 +1,533 @@ +//go:build with_wireguard + +package outbound + +import ( + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "net" + "net/netip" + "os" + "strings" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/debug" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "golang.zx2c4.com/wireguard/conn" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/tun" + "gvisor.dev/gvisor/pkg/bufferv2" + "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/icmp" + "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" + "gvisor.dev/gvisor/pkg/tcpip/transport/udp" +) + +var _ adapter.Outbound = (*WireGuard)(nil) + +type WireGuard struct { + myOutboundAdapter + ctx context.Context + serverAddr M.Socksaddr + dialer N.Dialer + endpoint conn.Endpoint + device *device.Device + tunDevice *wireTunDevice + connAccess sync.Mutex + conn *wireConn +} + +func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) { + outbound := &WireGuard{ + myOutboundAdapter: myOutboundAdapter{ + protocol: C.TypeWireGuard, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + }, + ctx: ctx, + serverAddr: options.ServerOptions.Build(), + dialer: dialer.NewOutbound(router, options.OutboundDialerOptions), + } + var endpointIp netip.Addr + if !outbound.serverAddr.IsFqdn() { + endpointIp = outbound.serverAddr.Addr + } else { + endpointIp = netip.AddrFrom4([4]byte{127, 0, 0, 1}) + } + outbound.endpoint = conn.StdNetEndpoint(netip.AddrPortFrom(endpointIp, outbound.serverAddr.Port)) + localAddress := make([]tcpip.AddressWithPrefix, len(options.LocalAddress)) + if len(localAddress) == 0 { + return nil, E.New("missing local address") + } + for index, address := range options.LocalAddress { + if strings.Contains(address, "/") { + prefix, err := netip.ParsePrefix(address) + if err != nil { + return nil, E.Cause(err, "parse local address prefix ", address) + } + localAddress[index] = tcpip.AddressWithPrefix{ + Address: tcpip.Address(prefix.Addr().AsSlice()), + PrefixLen: prefix.Bits(), + } + } else { + addr, err := netip.ParseAddr(address) + if err != nil { + return nil, E.Cause(err, "parse local address ", address) + } + localAddress[index] = tcpip.Address(addr.AsSlice()).WithPrefix() + } + } + var privateKey, peerPublicKey, preSharedKey string + { + bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey) + if err != nil { + return nil, E.Cause(err, "decode private key") + } + privateKey = hex.EncodeToString(bytes) + } + { + bytes, err := base64.StdEncoding.DecodeString(options.PeerPublicKey) + if err != nil { + return nil, E.Cause(err, "decode peer public key") + } + peerPublicKey = hex.EncodeToString(bytes) + } + if options.PreSharedKey != "" { + bytes, err := base64.StdEncoding.DecodeString(options.PreSharedKey) + if err != nil { + return nil, E.Cause(err, "decode pre shared key") + } + preSharedKey = hex.EncodeToString(bytes) + } + ipcConf := "private_key=" + privateKey + ipcConf += "\npublic_key=" + peerPublicKey + ipcConf += "\nendpoint=" + outbound.endpoint.DstToString() + if preSharedKey != "" { + ipcConf += "\npreshared_key=" + preSharedKey + } + var has4, has6 bool + for _, address := range localAddress { + if address.Address.To4() != "" { + has4 = true + } else { + has6 = true + } + } + if has4 { + ipcConf += "\nallowed_ip=0.0.0.0/0" + } + if has6 { + ipcConf += "\nallowed_ip=::/0" + } + mtu := options.MTU + if mtu == 0 { + mtu = 1450 + } + wireDevice, err := newWireDevice(localAddress, mtu) + if err != nil { + return nil, err + } + wgDevice := device.NewDevice(wireDevice, (*wireClientBind)(outbound), &device.Logger{ + Verbosef: func(format string, args ...interface{}) { + logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) + }, + Errorf: func(format string, args ...interface{}) { + logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) + }, + }) + if debug.Enabled { + logger.Trace("created wireguard ipc conf: \n", ipcConf) + } + err = wgDevice.IpcSet(ipcConf) + if err != nil { + return nil, E.Cause(err, "setup wireguard") + } + outbound.device = wgDevice + outbound.tunDevice = wireDevice + return outbound, nil +} + +func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch network { + case N.NetworkTCP: + w.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + addr := tcpip.FullAddress{ + NIC: defaultNIC, + Port: destination.Port, + } + if destination.IsFqdn() { + addrs, err := w.router.LookupDefault(ctx, destination.Fqdn) + if err != nil { + return nil, err + } + addr.Addr = tcpip.Address(addrs[0].AsSlice()) + } else { + addr.Addr = tcpip.Address(destination.Addr.AsSlice()) + } + bind := tcpip.FullAddress{ + NIC: defaultNIC, + } + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() { + networkProtocol = header.IPv4ProtocolNumber + bind.Addr = w.tunDevice.addr4 + } else { + networkProtocol = header.IPv6ProtocolNumber + bind.Addr = w.tunDevice.addr6 + } + switch N.NetworkName(network) { + case N.NetworkTCP: + return gonet.DialTCPWithBind(ctx, w.tunDevice.stack, bind, addr, networkProtocol) + case N.NetworkUDP: + return gonet.DialUDP(w.tunDevice.stack, &bind, &addr, networkProtocol) + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + bind := tcpip.FullAddress{ + NIC: defaultNIC, + } + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() || w.tunDevice.addr6 == "" { + networkProtocol = header.IPv4ProtocolNumber + bind.Addr = w.tunDevice.addr4 + } else { + networkProtocol = header.IPv6ProtocolNumber + bind.Addr = w.tunDevice.addr6 + } + return gonet.DialUDP(w.tunDevice.stack, &bind, nil, networkProtocol) +} + +func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return NewEarlyConnection(ctx, w, conn, metadata) +} + +func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewPacketConnection(ctx, w, conn, metadata) +} + +func (w *WireGuard) Start() error { + w.tunDevice.events <- tun.EventUp + return nil +} + +func (w *WireGuard) Close() error { + return common.Close( + common.PtrOrNil(w.tunDevice), + common.PtrOrNil(w.device), + common.PtrOrNil(w.conn), + ) +} + +var _ conn.Bind = (*wireClientBind)(nil) + +type wireClientBind WireGuard + +func (c *wireClientBind) connect() (*wireConn, error) { + c.connAccess.Lock() + defer c.connAccess.Unlock() + if c.conn != nil { + select { + case <-c.conn.done: + default: + return c.conn, nil + } + } + udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.serverAddr) + if err != nil { + return nil, &wireError{err} + } + c.conn = &wireConn{ + Conn: udpConn, + done: make(chan struct{}), + } + return c.conn, nil +} + +func (c *wireClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) { + return []conn.ReceiveFunc{c.receive}, 0, nil +} + +func (c *wireClientBind) receive(b []byte) (n int, ep conn.Endpoint, err error) { + udpConn, err := c.connect() + if err != nil { + return + } + n, err = udpConn.Read(b) + if err != nil { + udpConn.Close() + err = &wireError{err} + } + ep = c.endpoint + return +} + +func (c *wireClientBind) Close() error { + c.connAccess.Lock() + defer c.connAccess.Unlock() + common.Close(common.PtrOrNil(c.conn)) + return nil +} + +func (c *wireClientBind) SetMark(mark uint32) error { + return nil +} + +func (c *wireClientBind) Send(b []byte, ep conn.Endpoint) error { + udpConn, err := c.connect() + if err != nil { + return err + } + _, err = udpConn.Write(b) + if err != nil { + udpConn.Close() + } + return err +} + +func (c *wireClientBind) ParseEndpoint(s string) (conn.Endpoint, error) { + return c.endpoint, nil +} + +type wireError struct { + cause error +} + +func (w *wireError) Error() string { + return w.cause.Error() +} + +func (w *wireError) Timeout() bool { + if cause, causeNet := w.cause.(net.Error); causeNet { + return cause.Timeout() + } + return false +} + +func (w *wireError) Temporary() bool { + return true +} + +func (w *wireError) Unwrap() error { + return w.cause +} + +type wireConn struct { + net.Conn + access sync.Mutex + done chan struct{} +} + +func (w *wireConn) Close() error { + w.access.Lock() + defer w.access.Unlock() + select { + case <-w.done: + return net.ErrClosed + default: + } + w.Conn.Close() + close(w.done) + return nil +} + +var _ tun.Device = (*wireTunDevice)(nil) + +const defaultNIC tcpip.NICID = 1 + +type wireTunDevice struct { + stack *stack.Stack + mtu uint32 + events chan tun.Event + outbound chan *stack.PacketBuffer + dispatcher stack.NetworkDispatcher + done chan struct{} + addr4 tcpip.Address + addr6 tcpip.Address +} + +func newWireDevice(localAddresses []tcpip.AddressWithPrefix, mtu uint32) (*wireTunDevice, error) { + ipStack := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, + TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6}, + HandleLocal: true, + }) + tunDevice := &wireTunDevice{ + stack: ipStack, + mtu: mtu, + events: make(chan tun.Event, 4), + outbound: make(chan *stack.PacketBuffer, 256), + done: make(chan struct{}), + } + err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice)) + if err != nil { + return nil, E.New(err.String()) + } + for _, addr := range localAddresses { + var protoAddr tcpip.ProtocolAddress + if len(addr.Address) == net.IPv4len { + tunDevice.addr4 = addr.Address + protoAddr = tcpip.ProtocolAddress{ + Protocol: ipv4.ProtocolNumber, + AddressWithPrefix: addr, + } + } else { + tunDevice.addr6 = addr.Address + protoAddr = tcpip.ProtocolAddress{ + Protocol: ipv6.ProtocolNumber, + AddressWithPrefix: addr, + } + } + err = ipStack.AddProtocolAddress(defaultNIC, protoAddr, stack.AddressProperties{}) + if err != nil { + return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String()) + } + } + sOpt := tcpip.TCPSACKEnabled(true) + ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt) + cOpt := tcpip.CongestionControlOption("cubic") + ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt) + ipStack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: defaultNIC}) + ipStack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: defaultNIC}) + return tunDevice, nil +} + +func (w *wireTunDevice) File() *os.File { + return nil +} + +func (w *wireTunDevice) Read(p []byte, offset int) (n int, err error) { + packetBuffer, ok := <-w.outbound + if !ok { + return 0, os.ErrClosed + } + defer packetBuffer.DecRef() + p = p[offset:] + for _, slice := range packetBuffer.AsSlices() { + n += copy(p[n:], slice) + } + return +} + +func (w *wireTunDevice) Write(p []byte, offset int) (n int, err error) { + p = p[offset:] + if len(p) == 0 { + return + } + var networkProtocol tcpip.NetworkProtocolNumber + switch header.IPVersion(p) { + case header.IPv4Version: + networkProtocol = header.IPv4ProtocolNumber + case header.IPv6Version: + networkProtocol = header.IPv6ProtocolNumber + } + packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: bufferv2.MakeWithData(p), + }) + defer packetBuffer.DecRef() + w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer) + n = len(p) + return +} + +func (w *wireTunDevice) Flush() error { + return nil +} + +func (w *wireTunDevice) MTU() (int, error) { + return int(w.mtu), nil +} + +func (w *wireTunDevice) Name() (string, error) { + return "sing-box", nil +} + +func (w *wireTunDevice) Events() chan tun.Event { + return w.events +} + +func (w *wireTunDevice) Close() error { + select { + case <-w.done: + return os.ErrClosed + default: + } + close(w.done) + w.stack.Close() + for _, endpoint := range w.stack.CleanupEndpoints() { + endpoint.Abort() + } + w.stack.Wait() + close(w.outbound) + return nil +} + +var _ stack.LinkEndpoint = (*wireEndpoint)(nil) + +type wireEndpoint wireTunDevice + +func (ep *wireEndpoint) MTU() uint32 { + return ep.mtu +} + +func (ep *wireEndpoint) MaxHeaderLength() uint16 { + return 0 +} + +func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress { + return "" +} + +func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities { + return stack.CapabilityNone +} + +func (ep *wireEndpoint) Attach(dispatcher stack.NetworkDispatcher) { + ep.dispatcher = dispatcher +} + +func (ep *wireEndpoint) IsAttached() bool { + return ep.dispatcher != nil +} + +func (ep *wireEndpoint) Wait() { +} + +func (ep *wireEndpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareNone +} + +func (ep *wireEndpoint) AddHeader(buffer *stack.PacketBuffer) { +} + +func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { + for _, packetBuffer := range list.AsSlice() { + packetBuffer.IncRef() + ep.outbound <- packetBuffer + } + return list.Len(), nil +} diff --git a/outbound/wireguard_stub.go b/outbound/wireguard_stub.go new file mode 100644 index 00000000..3a8b0e87 --- /dev/null +++ b/outbound/wireguard_stub.go @@ -0,0 +1,16 @@ +//go:build !with_wireguard + +package outbound + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) +} diff --git a/route/router.go b/route/router.go index 35ca3bd1..9e95e1c1 100644 --- a/route/router.go +++ b/route/router.go @@ -362,20 +362,6 @@ func (r *Router) Initialize(outbounds []adapter.Outbound, defaultOutbound func() return E.New("outbound not found for rule[", i, "]: ", rule.Outbound()) } } - for i, detour := range r.outbounds { - if starter, isStarter := detour.(adapter.Starter); isStarter { - err := starter.Start() - if err != nil { - var tag string - if detour.Tag() == "" { - tag = F.ToString(i) - } else { - tag = detour.Tag() - } - return E.Cause(err, "initialize outbound/", detour.Type(), "[", tag, "]") - } - } - } return nil } @@ -935,7 +921,7 @@ func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) { func (r *Router) NewError(ctx context.Context, err error) { common.Close(err) if E.IsClosedOrCanceled(err) { - r.logger.TraceContext(ctx, "connection closed: ", err) + r.logger.DebugContext(ctx, "connection closed: ", err) return } r.logger.ErrorContext(ctx, err) diff --git a/test/box_test.go b/test/box_test.go index 4c7f7abf..58ab0df1 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -8,6 +8,7 @@ import ( "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/socks" @@ -72,9 +73,28 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) { } require.NoError(t, err) })*/ + //require.NoError(t, testPingPongWithConn(t, testPort, dialTCP)) + //require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP)) + require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP)) + require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP)) + + // require.NoError(t, testPacketConnTimeout(t, dialUDP)) +} + +func testSuitWg(t *testing.T, clientPort uint16, testPort uint16) { + dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "") + dialTCP := func() (net.Conn, error) { + return dialer.DialContext(context.Background(), "tcp", M.ParseSocksaddrHostPort("10.0.0.1", testPort)) + } + dialUDP := func() (net.PacketConn, error) { + conn, err := dialer.DialContext(context.Background(), "udp", M.ParseSocksaddrHostPort("10.0.0.1", testPort)) + if err != nil { + return nil, err + } + return bufio.NewUnbindPacketConn(conn), nil + } require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP)) require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP)) // require.NoError(t, testPingPongWithConn(t, testPort, dialTCP)) // require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP)) - // require.NoError(t, testPacketConnTimeout(t, dialUDP)) } diff --git a/test/clash_test.go b/test/clash_test.go index 3330baa8..bd76aa19 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -31,6 +31,7 @@ const ( ImageV2RayCore = "v2fly/v2fly-core:latest" ImageTrojan = "trojangfw/trojan:latest" ImageNaive = "pocat/naiveproxy:client" + ImageBoringTun = "ghcr.io/ntkme/boringtun:edge" ) var allImages = []string{ @@ -39,6 +40,7 @@ var allImages = []string{ ImageV2RayCore, ImageTrojan, ImageNaive, + ImageBoringTun, } var localIP = netip.MustParseAddr("127.0.0.1") @@ -377,7 +379,7 @@ func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.Pack mux.Lock() hashMap[i] = hash[:] mux.Unlock() - + println("write ti ", addr.String()) if _, err = pc.WriteTo(buf, addr); err != nil { t.Log(err) continue @@ -398,11 +400,9 @@ func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.Pack t.Log(err.Error()) return } - hash := md5.Sum(buf[:chunkSize]) hashMap[int(buf[0])] = hash[:] } - sendHash, err := writeRandData(l, rAddr) if err != nil { t.Log(err.Error()) diff --git a/test/config/wireguard.conf b/test/config/wireguard.conf new file mode 100644 index 00000000..10735a6d --- /dev/null +++ b/test/config/wireguard.conf @@ -0,0 +1,8 @@ +[Interface] +PrivateKey = gHWUGzTh5YCEV6k8dneVP537XhVtoQJPIlFNs2zsxlE= +Address = 10.0.0.1/24 +ListenPort = 10000 + +[Peer] +PublicKey = LV2xr9tzxwbs0ZLUlFN9k/0Or9QWqIInvxc/Cu7/2hA= +AllowedIPs = 10.0.0.2/32 \ No newline at end of file diff --git a/test/docker_test.go b/test/docker_test.go index bca1dbeb..d9a9fee7 100644 --- a/test/docker_test.go +++ b/test/docker_test.go @@ -28,6 +28,7 @@ type DockerOptions struct { Env []string Bind map[string]string Stdin []byte + Cap []string } func startDockerContainer(t *testing.T, options DockerOptions) { @@ -56,6 +57,7 @@ func startDockerContainer(t *testing.T, options DockerOptions) { if !C.IsDarwin { hostOptions.NetworkMode = "host" } + hostOptions.CapAdd = options.Cap hostOptions.PortBindings = make(nat.PortMap) for _, port := range options.Ports { diff --git a/test/go.mod b/test/go.mod index 9fc204a0..d5d3ba17 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v20.10.17+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid v4.2.0+incompatible - github.com/sagernet/sing v0.0.0-20220815085149-6b313ff9efc3 + github.com/sagernet/sing v0.0.0-20220816094748-fb82be7f3f08 github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48 github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.0 @@ -52,9 +52,9 @@ require ( github.com/oschwald/maxminddb-golang v1.10.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 // indirect + github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a // indirect github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae // indirect - github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7 // indirect + github.com/sagernet/sing-tun v0.0.0-20220816152948-85c649d9a3e8 // indirect github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4 // indirect github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 // indirect github.com/sirupsen/logrus v1.8.1 // indirect @@ -67,6 +67,8 @@ require ( golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/tools v0.1.9 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect + golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.3.0 // indirect diff --git a/test/go.sum b/test/go.sum index 956ef5b7..f7ef0c53 100644 --- a/test/go.sum +++ b/test/go.sum @@ -172,17 +172,17 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 h1:hE+vtsjBCCPmxkRz9jZA+CicHgVkDT6H+Av5ZzskVxs= -github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a h1:iNtsfGMenajBUGZ/1yAzl1v3p+t/7IJ/ilQXq9haRZ8= +github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= -github.com/sagernet/sing v0.0.0-20220815085149-6b313ff9efc3 h1:f9QdygPxD5wuAOnO6NpWF/Ra5bT6NPUOL3oQNulWSo8= -github.com/sagernet/sing v0.0.0-20220815085149-6b313ff9efc3/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= +github.com/sagernet/sing v0.0.0-20220816094748-fb82be7f3f08 h1:Z5UMSxFO+c2GtJqDlU7SF4OqzEV76KNYktTyzhofL9o= +github.com/sagernet/sing v0.0.0-20220816094748-fb82be7f3f08/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae h1:xOpbvgizvIbKKrrcl/CK3RjGY2u7rC+SBXlgqzEZOU4= github.com/sagernet/sing-dns v0.0.0-20220813025814-e656c9dbf3ae/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM= github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48 h1:NlcTFKldteZvYBDyr+V9MjZEI0rAWCSFCyLgPvc5n/Y= github.com/sagernet/sing-shadowsocks v0.0.0-20220812082714-484a11603b48/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM= -github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7 h1:KwUTQUPvdcJtrZR3WImygB0fINaGIr4X42TnDIDJ9sU= -github.com/sagernet/sing-tun v0.0.0-20220815033412-1407eae46bd7/go.mod h1:+mJ/s6hO3CZyD7CpHbEuZmIVyRkTYLRl4iTr5a57mG0= +github.com/sagernet/sing-tun v0.0.0-20220816152948-85c649d9a3e8 h1:ttjzAzTkVnjGlmw32tGjEdgykSl/qT1i94gxJ1tPDcA= +github.com/sagernet/sing-tun v0.0.0-20220816152948-85c649d9a3e8/go.mod h1:q8vcsDiAbvfepmYBxGcYfyHY7AcLCKW3WewC+zE8++Q= github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4 h1:2hLETh97+S4WnfMR27XyC7QVU1SH7FTNoCznP229YJU= github.com/sagernet/sing-vmess v0.0.0-20220811135656-4f3f07acf9c4/go.mod h1:82O6gzbxLha/W/jxSVQbsqf2lVdRTjMIgyLug0lpJps= github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 h1:pB1Dh1NbwVrLhQhotr4O4Hs3yhiBzmg3AvnUyYjL4x4= @@ -352,6 +352,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0= +golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= diff --git a/test/wireguard_test.go b/test/wireguard_test.go new file mode 100644 index 00000000..e897aa5e --- /dev/null +++ b/test/wireguard_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "net/netip" + "testing" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" +) + +func TestWireGuard(t *testing.T) { + startDockerContainer(t, DockerOptions{ + Image: ImageBoringTun, + Cap: []string{"MKNOD", "NET_ADMIN", "NET_RAW"}, + Ports: []uint16{serverPort, testPort}, + Bind: map[string]string{ + "wireguard.conf": "/etc/wireguard/wg0.conf", + }, + Cmd: []string{"wg0"}, + }) + time.Sleep(5 * time.Second) + startInstance(t, option.Options{ + Log: &option.LogOptions{ + Level: "trace", + }, + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeWireGuard, + WireGuardOptions: option.WireGuardOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + LocalAddress: []string{"10.0.0.2/32"}, + PrivateKey: "qGnwlkZljMxeECW8fbwAWdvgntnbK7B8UmMFl3zM0mk=", + PeerPublicKey: "QsdcBm+oJw2oNv0cIFXLIq1E850lgTBonup4qnKEQBg=", + }, + }, + }, + }) + testSuitWg(t, clientPort, testPort) +}