Compare commits
27 Commits
v1.1-beta5
...
v1.1-beta8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ce244dd04 | ||
|
|
0f57b93925 | ||
|
|
c90a77a185 | ||
|
|
c6586f19fa | ||
|
|
cbab86ae38 | ||
|
|
17b5f031f1 | ||
|
|
b00b6b9e25 | ||
|
|
fb6b3b0401 | ||
|
|
22ea878fe9 | ||
|
|
abe3dc6039 | ||
|
|
852829b9dc | ||
|
|
407509c985 | ||
|
|
9856b73cb5 | ||
|
|
f42356fbcb | ||
|
|
d0b467671a | ||
|
|
c18c545798 | ||
|
|
693ef293ac | ||
|
|
a006627795 | ||
|
|
0738b184e4 | ||
|
|
42524ba04e | ||
|
|
63fc95b96d | ||
|
|
ab436fc137 | ||
|
|
1546770bfd | ||
|
|
f4b2099488 | ||
|
|
a2c4d68031 | ||
|
|
cfe14f2817 | ||
|
|
a5402ffb69 |
15
Makefile
15
Makefile
@@ -1,7 +1,8 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_clash_api
|
TAGS ?= with_gvisor,with_quic,with_wireguard,with_clash_api
|
||||||
PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags '-s -w -buildid='
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||||
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
|
|
||||||
.PHONY: test release
|
.PHONY: test release
|
||||||
@@ -59,13 +60,19 @@ release_install:
|
|||||||
go install -v github.com/tcnksm/ghr@latest
|
go install -v github.com/tcnksm/ghr@latest
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v . && \
|
@go test -v ./... && \
|
||||||
cd test && \
|
cd test && \
|
||||||
go mod tidy && \
|
go mod tidy && \
|
||||||
go test -v -tags with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr .
|
go test -v -tags "$(TAGS_TEST)" .
|
||||||
|
|
||||||
|
test_stdio:
|
||||||
|
@go test -v ./... && \
|
||||||
|
cd test && \
|
||||||
|
go mod tidy && \
|
||||||
|
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
|
||||||
update:
|
update:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ type ClashServer interface {
|
|||||||
Mode() string
|
Mode() string
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,13 +38,25 @@ type myUpstreamHandlerWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
w.metadata.Destination = metadata.Destination
|
myMetadata := w.metadata
|
||||||
return w.connectionHandler(ctx, conn, w.metadata)
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
w.metadata.Destination = metadata.Destination
|
myMetadata := w.metadata
|
||||||
return w.packetHandler(ctx, conn, w.metadata)
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
@@ -78,13 +90,23 @@ func NewUpstreamContextHandler(
|
|||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
myMetadata.Destination = metadata.Destination
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
myMetadata.Destination = metadata.Destination
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
return w.packetHandler(ctx, conn, *myMetadata)
|
return w.packetHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func WrapH2(err error) error {
|
|||||||
if err == io.ErrUnexpectedEOF {
|
if err == io.ErrUnexpectedEOF {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
if Contains(err, "client disconnected", "body closed by handler") {
|
if Contains(err, "client disconnected", "body closed by handler", "response body closed", "; CANCEL") {
|
||||||
return net.ErrClosed
|
return net.ErrClosed
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -110,7 +110,13 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
if options.TCPFastOpen {
|
if options.TCPFastOpen {
|
||||||
warnTFOOnUnsupportedPlatform.Check()
|
warnTFOOnUnsupportedPlatform.Check()
|
||||||
}
|
}
|
||||||
if !options.UDPFragment {
|
var udpFragment bool
|
||||||
|
if options.UDPFragment != nil {
|
||||||
|
udpFragment = *options.UDPFragment
|
||||||
|
} else {
|
||||||
|
udpFragment = options.UDPFragmentDefault
|
||||||
|
}
|
||||||
|
if !udpFragment {
|
||||||
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
|
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
|
||||||
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
|
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
|
||||||
}
|
}
|
||||||
@@ -140,7 +146,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
return d.udpDialer.DialContext(ctx, network, address.String())
|
return d.udpDialer.DialContext(ctx, network, address.String())
|
||||||
}
|
}
|
||||||
return d.dialer.DialContext(ctx, network, address.Unwrap().String())
|
return d.dialer.DialContext(ctx, network, address.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
|||||||
@@ -466,10 +466,7 @@ func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if buffer.FreeLen() < int(length) {
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
return destination, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
_, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length)))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package mux
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
|
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
@@ -26,14 +26,21 @@ func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var stream net.Conn
|
var group task.Group
|
||||||
for {
|
group.Append0(func(ctx context.Context) error {
|
||||||
stream, err = session.Accept()
|
var stream net.Conn
|
||||||
if err != nil {
|
for {
|
||||||
return err
|
stream, err = session.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
||||||
}
|
}
|
||||||
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
})
|
||||||
}
|
group.Cleanup(func() {
|
||||||
|
session.Close()
|
||||||
|
})
|
||||||
|
return group.Run(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
||||||
@@ -158,9 +165,6 @@ func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksad
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if buffer.FreeLen() < int(length) {
|
|
||||||
return destination, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -223,9 +227,6 @@ func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if buffer.FreeLen() < int(length) {
|
|
||||||
return destination, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -28,11 +28,5 @@ type Info struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
info, err := findProcessInfo(searcher, ctx, network, source, destination)
|
return findProcessInfo(searcher, ctx, network, source, destination)
|
||||||
if err != nil {
|
|
||||||
if source.Addr().Is4In6() {
|
|
||||||
info, err = findProcessInfo(searcher, ctx, network, netip.AddrPortFrom(netip.AddrFrom4(source.Addr().As4()), source.Port()), destination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info, err
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
if header != nil {
|
if header != nil {
|
||||||
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
|
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
|
||||||
Source: M.SocksaddrFromNet(header.SourceAddr),
|
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(),
|
||||||
Destination: M.SocksaddrFromNet(header.DestinationAddr),
|
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(),
|
||||||
}}, nil
|
}}, nil
|
||||||
}
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ package redir
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@@ -32,6 +29,18 @@ func TProxy(fd uintptr, isIPv6 bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TProxyWriteBack() control.Func {
|
||||||
|
return func(network, address string, conn syscall.RawConn) error {
|
||||||
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
|
if M.ParseSocksaddr(address).Addr.Is6() {
|
||||||
|
return syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||||
|
} else {
|
||||||
|
return syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
||||||
controlMessages, err := unix.ParseSocketControlMessage(oob)
|
controlMessages, err := unix.ParseSocketControlMessage(oob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,79 +55,3 @@ func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
|||||||
}
|
}
|
||||||
return netip.AddrPort{}, E.New("not found")
|
return netip.AddrPort{}, E.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialUDP(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
|
||||||
rSockAddr, err := udpAddrToSockAddr(rAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lSockAddr, err := udpAddrToSockAddr(lAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := syscall.Socket(udpAddrFamily(lAddr, rAddr), syscall.SOCK_DGRAM, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.Bind(fd, lSockAddr); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = syscall.Connect(fd, rSockAddr); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fdFile := os.NewFile(uintptr(fd), F.ToString("net-udp-dial-", rAddr))
|
|
||||||
defer fdFile.Close()
|
|
||||||
|
|
||||||
c, err := net.FileConn(fdFile)
|
|
||||||
if err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.(*net.UDPConn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
|
|
||||||
switch {
|
|
||||||
case addr.IP.To4() != nil:
|
|
||||||
ip := [4]byte{}
|
|
||||||
copy(ip[:], addr.IP.To4())
|
|
||||||
|
|
||||||
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
ip := [16]byte{}
|
|
||||||
copy(ip[:], addr.IP.To16())
|
|
||||||
|
|
||||||
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
zoneID = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func udpAddrFamily(lAddr, rAddr *net.UDPAddr) int {
|
|
||||||
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
|
|
||||||
return syscall.AF_INET
|
|
||||||
}
|
|
||||||
return syscall.AF_INET6
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,19 +3,20 @@
|
|||||||
package redir
|
package redir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TProxy(fd uintptr, isIPv6 bool) error {
|
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TProxyWriteBack() control.Func {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
|
||||||
return netip.AddrPort{}, os.ErrInvalid
|
return netip.AddrPort{}, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialUDP(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/task"
|
"github.com/sagernet/sing/common/task"
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
@@ -49,5 +50,8 @@ func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil
|
return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package sniff
|
|
||||||
|
|
||||||
import _ "unsafe" // for linkname
|
|
||||||
|
|
||||||
//go:linkname IsDomainName net.isDomainName
|
|
||||||
func IsDomainName(domain string) bool
|
|
||||||
@@ -165,8 +165,6 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
return &echClientConfig{&tlsConfig}, nil
|
return &echClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeHTTPS = 65
|
|
||||||
|
|
||||||
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
|
|||||||
50
common/tls/mkcert.go
Normal file
50
common/tls/mkcert.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
template := &x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
NotBefore: time.Now().Add(time.Hour * -1),
|
||||||
|
NotAfter: time.Now().Add(time.Hour),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: serverName,
|
||||||
|
},
|
||||||
|
DNSNames: []string{serverName},
|
||||||
|
}
|
||||||
|
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateDer, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
publicPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
|
||||||
|
privPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
|
||||||
|
keyPair, err := tls.X509KeyPair(publicPem, privPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &keyPair, err
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
|||||||
c.config.NextProtos = nextProto
|
c.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errInsecureUnused = E.New("tls: insecure unused")
|
||||||
|
|
||||||
func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -46,6 +48,9 @@ func newSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if options.Insecure {
|
||||||
|
return nil, errInsecureUnused
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
}
|
}
|
||||||
@@ -102,17 +107,23 @@ func newSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
}
|
}
|
||||||
key = content
|
key = content
|
||||||
}
|
}
|
||||||
if certificate == nil {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
return nil, E.New("missing certificate")
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
return GenerateKeyPair(info.ServerName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if certificate == nil {
|
||||||
|
return nil, E.New("missing certificate")
|
||||||
|
} else if key == nil {
|
||||||
|
return nil, E.New("missing key")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPair, err := tls.X509KeyPair(certificate, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse x509 key pair")
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
||||||
}
|
}
|
||||||
if key == nil {
|
|
||||||
return nil, E.New("missing key")
|
|
||||||
}
|
|
||||||
keyPair, err := tls.X509KeyPair(certificate, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse x509 key pair")
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
|
||||||
}
|
}
|
||||||
return &STDServerConfig{
|
return &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
TypeSelector = "selector"
|
TypeSelector = "selector"
|
||||||
|
TypeURLTest = "urltest"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package constant
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TCPTimeout = 5 * time.Second
|
TCPTimeout = 5 * time.Second
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
QUICTimeout = 30 * time.Second
|
QUICTimeout = 30 * time.Second
|
||||||
STUNTimeout = 15 * time.Second
|
STUNTimeout = 15 * time.Second
|
||||||
UDPTimeout = 5 * time.Minute
|
UDPTimeout = 5 * time.Minute
|
||||||
|
DefaultURLTestInterval = 1 * time.Minute
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
var Version = "1.1-beta5"
|
var Version = "1.1-beta8"
|
||||||
|
|||||||
@@ -1,3 +1,32 @@
|
|||||||
|
#### 1.1-beta8
|
||||||
|
|
||||||
|
* Fix leaks on close
|
||||||
|
* Improve websocket writer
|
||||||
|
* Refine tproxy write back
|
||||||
|
* Refine 4in6 processing
|
||||||
|
* Fix shadowsocks plugins
|
||||||
|
* Fix missing source address from transport connection
|
||||||
|
* Fix fqdn socks5 outbound connection
|
||||||
|
* Fix read source address from grpc-go
|
||||||
|
|
||||||
|
#### 1.0.5
|
||||||
|
|
||||||
|
* Fix missing source address from transport connection
|
||||||
|
* Fix fqdn socks5 outbound connection
|
||||||
|
* Fix read source address from grpc-go
|
||||||
|
|
||||||
|
#### 1.1-beta7
|
||||||
|
|
||||||
|
* Add v2ray mux and XUDP support for VMess inbound
|
||||||
|
* Add XUDP support for VMess outbound
|
||||||
|
* Disable DF on direct outbound by default
|
||||||
|
* Fix bugs in 1.1-beta6
|
||||||
|
|
||||||
|
#### 1.1-beta6
|
||||||
|
|
||||||
|
* Add [URLTest outbound](/configuration/outbound/urltest)
|
||||||
|
* Fix bugs in 1.1-beta5
|
||||||
|
|
||||||
#### 1.1-beta5
|
#### 1.1-beta5
|
||||||
|
|
||||||
* Print tags in version command
|
* Print tags in version command
|
||||||
|
|||||||
@@ -15,21 +15,24 @@
|
|||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
| Type | Format |
|
| Type | Format |
|
||||||
|---------------|------------------------------|
|
|----------------|--------------------------------|
|
||||||
| `direct` | [Direct](./direct) |
|
| `direct` | [Direct](./direct) |
|
||||||
| `block` | [Block](./block) |
|
| `block` | [Block](./block) |
|
||||||
| `socks` | [SOCKS](./socks) |
|
| `socks` | [SOCKS](./socks) |
|
||||||
| `http` | [HTTP](./http) |
|
| `http` | [HTTP](./http) |
|
||||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||||
| `vmess` | [VMess](./vmess) |
|
| `vmess` | [VMess](./vmess) |
|
||||||
| `trojan` | [Trojan](./trojan) |
|
| `trojan` | [Trojan](./trojan) |
|
||||||
| `wireguard` | [Wireguard](./wireguard) |
|
| `wireguard` | [Wireguard](./wireguard) |
|
||||||
| `hysteria` | [Hysteria](./hysteria) |
|
| `hysteria` | [Hysteria](./hysteria) |
|
||||||
| `tor` | [Tor](./tor) |
|
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||||
| `ssh` | [SSH](./ssh) |
|
| `vless` | [VLESS](./vless) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `tor` | [Tor](./tor) |
|
||||||
| `selector` | [Selector](./selector) |
|
| `ssh` | [SSH](./ssh) |
|
||||||
|
| `dns` | [DNS](./dns) |
|
||||||
|
| `selector` | [Selector](./selector) |
|
||||||
|
| `urltest` | [URLTest](./urltest) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -15,21 +15,24 @@
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|---------------|------------------------------|
|
|----------------|--------------------------------|
|
||||||
| `direct` | [Direct](./direct) |
|
| `direct` | [Direct](./direct) |
|
||||||
| `block` | [Block](./block) |
|
| `block` | [Block](./block) |
|
||||||
| `socks` | [SOCKS](./socks) |
|
| `socks` | [SOCKS](./socks) |
|
||||||
| `http` | [HTTP](./http) |
|
| `http` | [HTTP](./http) |
|
||||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||||
| `vmess` | [VMess](./vmess) |
|
| `vmess` | [VMess](./vmess) |
|
||||||
| `trojan` | [Trojan](./trojan) |
|
| `trojan` | [Trojan](./trojan) |
|
||||||
| `wireguard` | [Wireguard](./wireguard) |
|
| `wireguard` | [Wireguard](./wireguard) |
|
||||||
| `hysteria` | [Hysteria](./hysteria) |
|
| `hysteria` | [Hysteria](./hysteria) |
|
||||||
| `tor` | [Tor](./tor) |
|
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||||
| `ssh` | [SSH](./ssh) |
|
| `vless` | [VLESS](./vless) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `tor` | [Tor](./tor) |
|
||||||
| `selector` | [Selector](./selector) |
|
| `ssh` | [SSH](./ssh) |
|
||||||
|
| `dns` | [DNS](./dns) |
|
||||||
|
| `selector` | [Selector](./selector) |
|
||||||
|
| `urltest` | [URLTest](./urltest) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
37
docs/configuration/outbound/urltest.md
Normal file
37
docs/configuration/outbound/urltest.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "urltest",
|
||||||
|
"tag": "auto",
|
||||||
|
|
||||||
|
"outbounds": [
|
||||||
|
"proxy-a",
|
||||||
|
"proxy-b",
|
||||||
|
"proxy-c"
|
||||||
|
],
|
||||||
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
|
"interval": "1m",
|
||||||
|
"tolerance": 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### outbounds
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
List of outbound tags to test.
|
||||||
|
|
||||||
|
#### url
|
||||||
|
|
||||||
|
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
|
||||||
|
|
||||||
|
#### interval
|
||||||
|
|
||||||
|
The test interval. `1m` will be used if empty.
|
||||||
|
|
||||||
|
#### tolerance
|
||||||
|
|
||||||
|
The test tolerance in milliseconds. `50` will be used if empty.
|
||||||
37
docs/configuration/outbound/urltest.zh.md
Normal file
37
docs/configuration/outbound/urltest.zh.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "urltest",
|
||||||
|
"tag": "auto",
|
||||||
|
|
||||||
|
"outbounds": [
|
||||||
|
"proxy-a",
|
||||||
|
"proxy-b",
|
||||||
|
"proxy-c"
|
||||||
|
],
|
||||||
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
|
"interval": "1m",
|
||||||
|
"tolerance": 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### outbounds
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
用于测试的出站标签列表。
|
||||||
|
|
||||||
|
#### url
|
||||||
|
|
||||||
|
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204`。
|
||||||
|
|
||||||
|
#### interval
|
||||||
|
|
||||||
|
测试间隔。 默认使用 `1m`。
|
||||||
|
|
||||||
|
#### tolerance
|
||||||
|
|
||||||
|
以毫秒为单位的测试容差。 默认使用 `50`。
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"authenticated_length": true,
|
"authenticated_length": true,
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"packet_addr": false,
|
"packet_encoding": "",
|
||||||
"multiplex": {},
|
"multiplex": {},
|
||||||
"transport": {},
|
"transport": {},
|
||||||
|
|
||||||
@@ -84,9 +84,13 @@ Both is enabled by default.
|
|||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||||
|
|
||||||
#### packet_addr
|
#### packet_encoding
|
||||||
|
|
||||||
Enable packetaddr support.
|
| Encoding | Description |
|
||||||
|
|------------|-----------------------|
|
||||||
|
| (none) | Disabled |
|
||||||
|
| packetaddr | Supported by v2ray 5+ |
|
||||||
|
| xudp | Supported by xray |
|
||||||
|
|
||||||
#### multiplex
|
#### multiplex
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"authenticated_length": true,
|
"authenticated_length": true,
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"packet_addr": false,
|
"packet_encoding": "",
|
||||||
"multiplex": {},
|
"multiplex": {},
|
||||||
"transport": {},
|
"transport": {},
|
||||||
|
|
||||||
@@ -84,9 +84,13 @@ VMess 用户 ID。
|
|||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||||
|
|
||||||
#### packet_addr
|
#### packet_encoding
|
||||||
|
|
||||||
启用 packetaddr 支持。
|
| 编码 | 描述 |
|
||||||
|
|------------|---------------|
|
||||||
|
| (空) | 禁用 |
|
||||||
|
| packetaddr | 由 v2ray 5+ 支持 |
|
||||||
|
| xudp | 由 xray 支持 |
|
||||||
|
|
||||||
#### multiplex
|
#### multiplex
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,9 @@
|
|||||||
| cn | 17.8M | 140.3M |
|
| cn | 17.8M | 140.3M |
|
||||||
| cn (Loyalsoldier) | 74.3M | 246.7M |
|
| cn (Loyalsoldier) | 74.3M | 246.7M |
|
||||||
|
|
||||||
#### Shadowsocks benchmark
|
#### Benchmark
|
||||||
|
|
||||||
|
##### Shadowsocks
|
||||||
|
|
||||||
| / | none | aes-128-gcm | 2022-blake3-aes-128-gcm |
|
| / | none | aes-128-gcm | 2022-blake3-aes-128-gcm |
|
||||||
|------------------------------------|:---------:|:-----------:|:-----------------------:|
|
|------------------------------------|:---------:|:-----------:|:-----------------------:|
|
||||||
@@ -103,6 +105,13 @@
|
|||||||
| shadowsocks-rust (v1.15.0-alpha.5) | 10.7 Gbps | / | 9.36 Gbps |
|
| shadowsocks-rust (v1.15.0-alpha.5) | 10.7 Gbps | / | 9.36 Gbps |
|
||||||
| sing-box | 29.0 Gbps | / | 11.8 Gbps |
|
| sing-box | 29.0 Gbps | / | 11.8 Gbps |
|
||||||
|
|
||||||
|
##### VMess
|
||||||
|
|
||||||
|
| / | TCP | HTTP | H2 TLS | WebSocket TLS | gRPC TLS |
|
||||||
|
|--------------------|:---------:|:---------:|:---------:|:-------------:|:---------:|
|
||||||
|
| v2ray-core (5.1.0) | 7.86 GBps | 2.86 Gbps | 1.83 Gbps | 2.36 Gbps | 2.43 Gbps |
|
||||||
|
| sing-box | 7.96 Gbps | 8.09 Gbps | 6.11 Gbps | 8.02 Gbps | 6.35 Gbps |
|
||||||
|
|
||||||
#### License
|
#### License
|
||||||
|
|
||||||
| / | License |
|
| / | License |
|
||||||
|
|||||||
@@ -45,10 +45,6 @@ sing-box version
|
|||||||
It is also recommended to use systemd to manage sing-box service,
|
It is also recommended to use systemd to manage sing-box service,
|
||||||
see [Linux server installation example](./examples/linux-server-installation).
|
see [Linux server installation example](./examples/linux-server-installation).
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
[](https://github.com/sagernet/sing-box/graphs/contributors)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
|
|||||||
| 构建标志 | 描述 |
|
| 构建标志 | 描述 |
|
||||||
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `with_quic` | 启用 QUIC 支持,参阅 [QUIC 和 HTTP3 DNS 传输层](./configuration/dns/server),[Naive 入站](./configuration/inbound/naive),[Hysteria 入站](./configuration/inbound/hysteria),[Hysteria 出站](./configuration/outbound/hysteria) 和 [V2Ray 传输层#QUIC](./configuration/shared/v2ray-transport#quic)。 |
|
| `with_quic` | 启用 QUIC 支持,参阅 [QUIC 和 HTTP3 DNS 传输层](./configuration/dns/server),[Naive 入站](./configuration/inbound/naive),[Hysteria 入站](./configuration/inbound/hysteria),[Hysteria 出站](./configuration/outbound/hysteria) 和 [V2Ray 传输层#QUIC](./configuration/shared/v2ray-transport#quic)。 |
|
||||||
| `with_grpc` | 启用标准 gRPCuTLS](https://github.com/refraction-networking/utls) 支持, 参阅 [TLS](./configuration/shared/tls#utls)。 |
|
| `with_grpc` | 启用标准 gRPC 支持,参阅 [V2Ray 传输层#gRPC](./configuration/shared/v2ray-transport#grpc)。 |
|
||||||
| `with_acme` | 启用 ACME TLS 证书签发支持,参阅 [TLS](./configuration/shared/tls)。 |
|
|
||||||
| `with_clash_api` | 启用 Clash api 支 支持,参阅 [V2Ray 传输层#gRPC](./configuration/shared/v2ray-transport#grpc)。 |
|
|
||||||
| `with_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 |
|
| `with_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 |
|
||||||
| `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 |
|
| `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 |
|
||||||
| `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 |
|
| `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 |
|
||||||
| `with_utls` | 启用 [持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
|
| `with_utls` | 启用 uTLS 支持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
|
||||||
|
| `with_acme` | 启用 ACME TLS 证书签发支持,参阅 [TLS](./configuration/shared/tls)。 |
|
||||||
|
| `with_clash_api` | 启用 Clash API 支持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
|
||||||
| `with_gvisor` | 启用 gVisor 支持,参阅 [Tun 入站](./configuration/inbound/tun#stack) 和 [WireGuard 出站](./configuration/outbound/wireguard#system_interface)。 |
|
| `with_gvisor` | 启用 gVisor 支持,参阅 [Tun 入站](./configuration/inbound/tun#stack) 和 [WireGuard 出站](./configuration/outbound/wireguard#system_interface)。 |
|
||||||
| `with_embedded_tor` (需要 CGO) | 启用 嵌入式 Tor 支持,参阅 [Tor 出站](./configuration/outbound/tor)。 |
|
| `with_embedded_tor` (需要 CGO) | 启用 嵌入式 Tor 支持,参阅 [Tor 出站](./configuration/outbound/tor)。 |
|
||||||
| `with_lwip` (需要 CGO) | 启用 LWIP Tun 栈支持,参阅 [Tun 入站](./configuration/inbound/tun#stack)。 |
|
| `with_lwip` (需要 CGO) | 启用 LWIP Tun 栈支持,参阅 [Tun 入站](./configuration/inbound/tun#stack)。 |
|
||||||
@@ -42,13 +42,9 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
|
|||||||
sing-box version
|
sing-box version
|
||||||
```
|
```
|
||||||
|
|
||||||
同时推荐使用 Systemd 来管理 sing-box 服务器实例。
|
同时推荐使用 systemd 来管理 sing-box 服务器实例。
|
||||||
参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。
|
参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。
|
||||||
|
|
||||||
## 贡献者
|
|
||||||
|
|
||||||
[](https://github.com/sagernet/sing-box/graphs/contributors)
|
|
||||||
|
|
||||||
## 授权
|
## 授权
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler
|
|||||||
func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
||||||
var info badjson.JSONObject
|
var info badjson.JSONObject
|
||||||
var clashType string
|
var clashType string
|
||||||
var isGroup bool
|
|
||||||
switch detour.Type() {
|
switch detour.Type() {
|
||||||
case C.TypeDirect:
|
case C.TypeDirect:
|
||||||
clashType = "Direct"
|
clashType = "Direct"
|
||||||
@@ -91,7 +90,8 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
|||||||
clashType = "SSH"
|
clashType = "SSH"
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
clashType = "Selector"
|
clashType = "Selector"
|
||||||
isGroup = true
|
case C.TypeURLTest:
|
||||||
|
clashType = "URLTest"
|
||||||
default:
|
default:
|
||||||
clashType = "Direct"
|
clashType = "Direct"
|
||||||
}
|
}
|
||||||
@@ -104,10 +104,9 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
|||||||
} else {
|
} else {
|
||||||
info.Put("history", []*urltest.History{})
|
info.Put("history", []*urltest.History{})
|
||||||
}
|
}
|
||||||
if isGroup {
|
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
|
||||||
selector := detour.(adapter.OutboundGroup)
|
info.Put("now", group.Now())
|
||||||
info.Put("now", selector.Now())
|
info.Put("all", group.All())
|
||||||
info.Put("all", selector.All())
|
|
||||||
}
|
}
|
||||||
return &info
|
return &info
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ func (s *Server) CacheFile() adapter.ClashCacheFile {
|
|||||||
return s.cacheFile
|
return s.cacheFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) HistoryStorage() *urltest.HistoryStorage {
|
||||||
|
return s.urlTestHistory
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||||
return tracker, tracker
|
return tracker, tracker
|
||||||
|
|||||||
16
go.mod
16
go.mod
@@ -23,21 +23,21 @@ require (
|
|||||||
github.com/pires/go-proxyproto v0.6.2
|
github.com/pires/go-proxyproto v0.6.2
|
||||||
github.com/refraction-networking/utls v1.1.2
|
github.com/refraction-networking/utls v1.1.2
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
||||||
github.com/sagernet/sing v0.0.0-20220915031330-38f39bc0c690
|
github.com/sagernet/sing v0.0.0-20220925112014-b12b8b7fd220
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8
|
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220915032336-60b1da576469
|
github.com/sagernet/sing-tun v0.0.0-20220925112147-6bad0c2380ca
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12
|
github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.5.0
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.10.0
|
||||||
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d
|
go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7
|
||||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
golang.org/x/net v0.0.0-20220923203811-8be639271d50
|
||||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b
|
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b
|
||||||
google.golang.org/grpc v1.49.0
|
google.golang.org/grpc v1.49.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -145,16 +145,16 @@ github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTY
|
|||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
||||||
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.0.0-20220915031330-38f39bc0c690 h1:pvaLdkDmsGN2K46vf8rorAhYGFvKPuQNzcofuy3aXXg=
|
github.com/sagernet/sing v0.0.0-20220925112014-b12b8b7fd220 h1:fQk/BHOeHw5murjeNTdmkXmDy9cMlbubRINRH7GDuu4=
|
||||||
github.com/sagernet/sing v0.0.0-20220915031330-38f39bc0c690/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
github.com/sagernet/sing v0.0.0-20220925112014-b12b8b7fd220/go.mod h1:5/u6RMDMoGIkSNtrZb41kJvyIFg3Ysn69P3WiAu8m0c=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 h1:Iyfl+Rm5jcDvXuy/jpOBI3eu35ujci50tkqYHHwwg+8=
|
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b h1:cXCMNJ9heZ+c6l+qUcku60x9KyXo4SOAaJfg/6spOmU=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8/go.mod h1:bPVnJ5gJ0WmUfN1bJP9Cis0ab8SSByx6JVzyLJjDMwA=
|
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220915032336-60b1da576469 h1:tvGUJsOqxZ3ofAY9undQfQ+JCWvmIwLpIOC+XaBFO88=
|
github.com/sagernet/sing-tun v0.0.0-20220925112147-6bad0c2380ca h1:Owgx9izFNYyMyUZ61td+mL3vumBhJz4zNismYlCyQbw=
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220915032336-60b1da576469/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
github.com/sagernet/sing-tun v0.0.0-20220925112147-6bad0c2380ca/go.mod h1:ftP5VXlp3RJO5+WS3mPFk7jB9B/K/QrPEZX3BUORGQY=
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 h1:4HYGbTDDemgBVTmaspXbkgjJlXc3hYVjNxSddJndq8Y=
|
github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685 h1:AZzFNRR/ZwMTceUQ1b/mxx6oyKqmFymdMn/yleJmoVM=
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
|
github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||||
@@ -188,16 +188,16 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
|
|||||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
|
go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
|
||||||
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
|
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
|
||||||
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d h1:ggxwEf5eu0l8v+87VhX1czFh8zJul3hK16Gmruxn7hw=
|
go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab h1:+yW1yrZ09EYNu1spCUOHBBNRbrLnfmutwyhbhCv3b6Q=
|
||||||
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
|
go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7 h1:WJywXQVIb56P2kAvXeMGTIgQ1ZHQxR60+F9dLsodECc=
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||||
@@ -229,8 +229,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
|
golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI=
|
||||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -264,8 +264,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc h1:dpclq5m2YrqPGStKmtw7IcNbKLfbIqKXvNxDJKdIKYc=
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
|||||||
@@ -141,13 +141,13 @@ func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.Inboun
|
|||||||
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
||||||
if !metadata.Source.IsValid() {
|
if !metadata.Source.IsValid() {
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||||
}
|
}
|
||||||
if !metadata.Destination.IsValid() {
|
if !metadata.Destination.IsValid() {
|
||||||
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr())
|
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||||
}
|
}
|
||||||
if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP {
|
if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP {
|
||||||
metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr())
|
metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr()).Unwrap()
|
||||||
}
|
}
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (a *myInboundAdapter) loopUDPIn() {
|
|||||||
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
||||||
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
||||||
metadata.Source = M.SocksaddrFromNetIP(addr)
|
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
|
||||||
metadata.OriginDestination = a.udpAddr
|
metadata.OriginDestination = a.udpAddr
|
||||||
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -86,7 +86,7 @@ func (a *myInboundAdapter) loopUDPOOBIn() {
|
|||||||
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
||||||
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
||||||
metadata.Source = M.SocksaddrFromNetIP(addr)
|
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
|
||||||
metadata.OriginDestination = a.udpAddr
|
metadata.OriginDestination = a.udpAddr
|
||||||
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
|
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,7 +112,7 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() {
|
|||||||
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
||||||
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
||||||
metadata.Source = M.SocksaddrFromNetIP(addr)
|
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
|
||||||
metadata.OriginDestination = a.udpAddr
|
metadata.OriginDestination = a.udpAddr
|
||||||
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -140,7 +140,7 @@ func (a *myInboundAdapter) loopUDPOOBInThreadSafe() {
|
|||||||
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
||||||
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
|
||||||
metadata.Source = M.SocksaddrFromNetIP(addr)
|
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
|
||||||
metadata.OriginDestination = a.udpAddr
|
metadata.OriginDestination = a.udpAddr
|
||||||
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
|
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -261,9 +261,9 @@ func (h *Hysteria) acceptStream(ctx context.Context, conn quic.Connection, strea
|
|||||||
metadata.SniffEnabled = h.listenOptions.SniffEnabled
|
metadata.SniffEnabled = h.listenOptions.SniffEnabled
|
||||||
metadata.SniffOverrideDestination = h.listenOptions.SniffOverrideDestination
|
metadata.SniffOverrideDestination = h.listenOptions.SniffOverrideDestination
|
||||||
metadata.DomainStrategy = dns.DomainStrategy(h.listenOptions.DomainStrategy)
|
metadata.DomainStrategy = dns.DomainStrategy(h.listenOptions.DomainStrategy)
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||||
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr())
|
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||||
metadata.Destination = M.ParseSocksaddrHostPort(request.Host, request.Port)
|
metadata.Destination = M.ParseSocksaddrHostPort(request.Host, request.Port).Unwrap()
|
||||||
|
|
||||||
if !request.UDP {
|
if !request.UDP {
|
||||||
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
|
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (t *TProxy) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr())
|
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||||
return t.newConnection(ctx, conn, metadata)
|
return t.newConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,14 +84,15 @@ func (t *TProxy) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.B
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "get tproxy destination")
|
return E.Cause(err, "get tproxy destination")
|
||||||
}
|
}
|
||||||
metadata.Destination = M.SocksaddrFromNetIP(destination)
|
metadata.Destination = M.SocksaddrFromNetIP(destination).Unwrap()
|
||||||
t.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) {
|
t.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) {
|
||||||
return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{source: natConn}
|
return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{ctx: ctx, source: natConn, destination: metadata.Destination}
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type tproxyPacketWriter struct {
|
type tproxyPacketWriter struct {
|
||||||
|
ctx context.Context
|
||||||
source N.PacketConn
|
source N.PacketConn
|
||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn *net.UDPConn
|
conn *net.UDPConn
|
||||||
@@ -99,25 +100,27 @@ type tproxyPacketWriter struct {
|
|||||||
|
|
||||||
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
var udpConn *net.UDPConn
|
if w.destination == destination && w.conn != nil {
|
||||||
|
_, err := w.conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))
|
||||||
|
if err == nil {
|
||||||
|
w.conn = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var listener net.ListenConfig
|
||||||
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
|
listener.Control = control.Append(listener.Control, redir.TProxyWriteBack())
|
||||||
|
packetConn, err := listener.ListenPacket(w.ctx, "udp", destination.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
udpConn := packetConn.(*net.UDPConn)
|
||||||
if w.destination == destination {
|
if w.destination == destination {
|
||||||
if w.conn != nil {
|
w.conn = udpConn
|
||||||
udpConn = w.conn
|
} else {
|
||||||
}
|
defer udpConn.Close()
|
||||||
}
|
}
|
||||||
if udpConn == nil {
|
return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr())))
|
||||||
var err error
|
|
||||||
udpConn, err = redir.DialUDP(destination.UDPAddr(), M.SocksaddrFromNet(w.source.LocalAddr()).UDPAddr())
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "tproxy udp write back")
|
|
||||||
}
|
|
||||||
if w.destination == destination {
|
|
||||||
w.conn = udpConn
|
|
||||||
} else {
|
|
||||||
defer udpConn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return common.Error(udpConn.Write(buffer.Bytes()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *tproxyPacketWriter) Close() error {
|
func (w *tproxyPacketWriter) Close() error {
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.NewError(ctx, err)
|
t.NewError(ctx, err)
|
||||||
}
|
}
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
|
func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
|
||||||
@@ -212,7 +212,7 @@ func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstre
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.NewError(ctx, err)
|
t.NewError(ctx, err)
|
||||||
}
|
}
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tun) NewError(ctx context.Context, err error) {
|
func (t *Tun) NewError(ctx context.Context, err error) {
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ nav:
|
|||||||
- SSH: configuration/outbound/ssh.md
|
- SSH: configuration/outbound/ssh.md
|
||||||
- DNS: configuration/outbound/dns.md
|
- DNS: configuration/outbound/dns.md
|
||||||
- Selector: configuration/outbound/selector.md
|
- Selector: configuration/outbound/selector.md
|
||||||
|
- URLTest: configuration/outbound/urltest.md
|
||||||
- FAQ:
|
- FAQ:
|
||||||
- faq/index.md
|
- faq/index.md
|
||||||
- Known Issues: faq/known-issues.md
|
- Known Issues: faq/known-issues.md
|
||||||
|
|||||||
@@ -14,3 +14,10 @@ type SelectorOutboundOptions struct {
|
|||||||
Outbounds []string `json:"outbounds"`
|
Outbounds []string `json:"outbounds"`
|
||||||
Default string `json:"default,omitempty"`
|
Default string `json:"default,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type URLTestOutboundOptions struct {
|
||||||
|
Outbounds []string `json:"outbounds"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Interval Duration `json:"interval,omitempty"`
|
||||||
|
Tolerance uint16 `json:"tolerance,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type _Outbound struct {
|
|||||||
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
||||||
VLESSOptions VLESSOutboundOptions `json:"-"`
|
VLESSOptions VLESSOutboundOptions `json:"-"`
|
||||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||||
|
URLTestOptions URLTestOutboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Outbound _Outbound
|
type Outbound _Outbound
|
||||||
@@ -61,6 +62,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
|||||||
v = h.VLESSOptions
|
v = h.VLESSOptions
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = h.SelectorOptions
|
v = h.SelectorOptions
|
||||||
|
case C.TypeURLTest:
|
||||||
|
v = h.URLTestOptions
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown outbound type: ", h.Type)
|
return nil, E.New("unknown outbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
@@ -104,6 +107,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
|||||||
v = &h.VLESSOptions
|
v = &h.VLESSOptions
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = &h.SelectorOptions
|
v = &h.SelectorOptions
|
||||||
|
case C.TypeURLTest:
|
||||||
|
v = &h.URLTestOptions
|
||||||
default:
|
default:
|
||||||
return E.New("unknown outbound type: ", h.Type)
|
return E.New("unknown outbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
@@ -115,17 +120,18 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DialerOptions struct {
|
type DialerOptions struct {
|
||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
BindInterface string `json:"bind_interface,omitempty"`
|
BindInterface string `json:"bind_interface,omitempty"`
|
||||||
BindAddress *ListenAddress `json:"bind_address,omitempty"`
|
BindAddress *ListenAddress `json:"bind_address,omitempty"`
|
||||||
ProtectPath string `json:"protect_path,omitempty"`
|
ProtectPath string `json:"protect_path,omitempty"`
|
||||||
RoutingMark int `json:"routing_mark,omitempty"`
|
RoutingMark int `json:"routing_mark,omitempty"`
|
||||||
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
||||||
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
UDPFragment bool `json:"udp_fragment,omitempty"`
|
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
||||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
UDPFragmentDefault bool `json:"-"`
|
||||||
FallbackDelay Duration `json:"fallback_delay,omitempty"`
|
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||||
|
FallbackDelay Duration `json:"fallback_delay,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerOptions struct {
|
type ServerOptions struct {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package option
|
|||||||
type InboundTLSOptions struct {
|
type InboundTLSOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
ALPN Listable[string] `json:"alpn,omitempty"`
|
ALPN Listable[string] `json:"alpn,omitempty"`
|
||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty"`
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type VMessOutboundOptions struct {
|
|||||||
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
PacketAddr bool `json:"packet_addr,omitempty"`
|
PacketEncoding string `json:"packet_encoding,omitempty"`
|
||||||
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
|
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
|
||||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
|||||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
||||||
|
case C.TypeURLTest:
|
||||||
|
return NewURLTest(router, logger, options.Tag, options.URLTestOptions)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown outbound type: ", options.Type)
|
return nil, E.New("unknown outbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type Direct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) {
|
func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) {
|
||||||
|
options.UDPFragmentDefault = true
|
||||||
outbound := &Direct{
|
outbound := &Direct{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeDirect,
|
protocol: C.TypeDirect,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type Hysteria struct {
|
|||||||
recvBPS uint64
|
recvBPS uint64
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
conn quic.Connection
|
conn quic.Connection
|
||||||
|
rawConn net.Conn
|
||||||
udpAccess sync.RWMutex
|
udpAccess sync.RWMutex
|
||||||
udpSessions map[uint32]chan *hysteria.UDPMessage
|
udpSessions map[uint32]chan *hysteria.UDPMessage
|
||||||
udpDefragger hysteria.Defragger
|
udpDefragger hysteria.Defragger
|
||||||
@@ -149,7 +150,6 @@ func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
h.conn = conn
|
|
||||||
if common.Contains(h.network, N.NetworkUDP) {
|
if common.Contains(h.network, N.NetworkUDP) {
|
||||||
for _, session := range h.udpSessions {
|
for _, session := range h.udpSessions {
|
||||||
close(session)
|
close(session)
|
||||||
@@ -201,6 +201,8 @@ func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
|
|||||||
return nil, E.New("remote error: ", serverHello.Message)
|
return nil, E.New("remote error: ", serverHello.Message)
|
||||||
}
|
}
|
||||||
quicConn.SetCongestionControl(hysteria.NewBrutalSender(congestion.ByteCount(serverHello.RecvBPS)))
|
quicConn.SetCongestionControl(hysteria.NewBrutalSender(congestion.ByteCount(serverHello.RecvBPS)))
|
||||||
|
h.conn = quicConn
|
||||||
|
h.rawConn = udpConn
|
||||||
return quicConn, nil
|
return quicConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +242,7 @@ func (h *Hysteria) Close() error {
|
|||||||
defer h.udpAccess.Unlock()
|
defer h.udpAccess.Unlock()
|
||||||
if h.conn != nil {
|
if h.conn != nil {
|
||||||
h.conn.CloseWithError(0, "")
|
h.conn.CloseWithError(0, "")
|
||||||
|
h.rawConn.Close()
|
||||||
}
|
}
|
||||||
for _, session := range h.udpSessions {
|
for _, session := range h.udpSessions {
|
||||||
close(session)
|
close(session)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S
|
|||||||
default:
|
default:
|
||||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
}
|
}
|
||||||
if destination.IsFqdn() {
|
if h.resolve && destination.IsFqdn() {
|
||||||
addrs, err := h.router.LookupDefault(ctx, destination.Fqdn)
|
addrs, err := h.router.LookupDefault(ctx, destination.Fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, met
|
|||||||
return NewPacketConnection(ctx, h, conn, metadata)
|
return NewPacketConnection(ctx, h, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Trojan) Close() error {
|
||||||
|
return common.Close(h.multiplexDialer, h.transport)
|
||||||
|
}
|
||||||
|
|
||||||
type trojanDialer Trojan
|
type trojanDialer Trojan
|
||||||
|
|
||||||
func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
|||||||
287
outbound/urltest.go
Normal file
287
outbound/urltest.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
|
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/batch"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ adapter.Outbound = (*URLTest)(nil)
|
||||||
|
_ adapter.OutboundGroup = (*URLTest)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type URLTest struct {
|
||||||
|
myOutboundAdapter
|
||||||
|
tags []string
|
||||||
|
link string
|
||||||
|
interval time.Duration
|
||||||
|
tolerance uint16
|
||||||
|
group *URLTestGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewURLTest(router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
||||||
|
outbound := &URLTest{
|
||||||
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
|
protocol: C.TypeURLTest,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
tag: tag,
|
||||||
|
},
|
||||||
|
tags: options.Outbounds,
|
||||||
|
link: options.URL,
|
||||||
|
interval: time.Duration(options.Interval),
|
||||||
|
tolerance: options.Tolerance,
|
||||||
|
}
|
||||||
|
if len(outbound.tags) == 0 {
|
||||||
|
return nil, E.New("missing tags")
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) Network() []string {
|
||||||
|
if s.group == nil {
|
||||||
|
return []string{N.NetworkTCP, N.NetworkUDP}
|
||||||
|
}
|
||||||
|
return s.group.Select(N.NetworkTCP).Network()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) Start() error {
|
||||||
|
outbounds := make([]adapter.Outbound, 0, len(s.tags))
|
||||||
|
for i, tag := range s.tags {
|
||||||
|
detour, loaded := s.router.Outbound(tag)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("outbound ", i, " not found: ", tag)
|
||||||
|
}
|
||||||
|
outbounds = append(outbounds, detour)
|
||||||
|
}
|
||||||
|
s.group = NewURLTestGroup(s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
||||||
|
return s.group.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s URLTest) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
common.PtrOrNil(s.group),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) Now() string {
|
||||||
|
return s.group.Select(N.NetworkTCP).Tag()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) All() []string {
|
||||||
|
return s.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
outbound := s.group.Select(network)
|
||||||
|
conn, err := outbound.DialContext(ctx, network, destination)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
s.logger.ErrorContext(ctx, err)
|
||||||
|
go s.group.checkOutbounds()
|
||||||
|
outbounds := s.group.Fallback(outbound)
|
||||||
|
for _, fallback := range outbounds {
|
||||||
|
conn, err = fallback.DialContext(ctx, network, destination)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
outbound := s.group.Select(N.NetworkUDP)
|
||||||
|
conn, err := outbound.ListenPacket(ctx, destination)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
s.logger.ErrorContext(ctx, err)
|
||||||
|
go s.group.checkOutbounds()
|
||||||
|
outbounds := s.group.Fallback(outbound)
|
||||||
|
for _, fallback := range outbounds {
|
||||||
|
conn, err = fallback.ListenPacket(ctx, destination)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
return NewConnection(ctx, s, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return NewPacketConnection(ctx, s, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLTestGroup struct {
|
||||||
|
router adapter.Router
|
||||||
|
logger log.Logger
|
||||||
|
outbounds []adapter.Outbound
|
||||||
|
link string
|
||||||
|
interval time.Duration
|
||||||
|
tolerance uint16
|
||||||
|
history *urltest.HistoryStorage
|
||||||
|
|
||||||
|
ticker *time.Ticker
|
||||||
|
close chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup {
|
||||||
|
if link == "" {
|
||||||
|
//goland:noinspection HttpUrlsUsage
|
||||||
|
link = "http://www.gstatic.com/generate_204"
|
||||||
|
}
|
||||||
|
if interval == 0 {
|
||||||
|
interval = C.DefaultURLTestInterval
|
||||||
|
}
|
||||||
|
if tolerance == 0 {
|
||||||
|
tolerance = 50
|
||||||
|
}
|
||||||
|
var history *urltest.HistoryStorage
|
||||||
|
if clashServer := router.ClashServer(); clashServer != nil {
|
||||||
|
history = clashServer.HistoryStorage()
|
||||||
|
} else {
|
||||||
|
history = urltest.NewHistoryStorage()
|
||||||
|
}
|
||||||
|
return &URLTestGroup{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
outbounds: outbounds,
|
||||||
|
link: link,
|
||||||
|
interval: interval,
|
||||||
|
tolerance: tolerance,
|
||||||
|
history: history,
|
||||||
|
close: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *URLTestGroup) Start() error {
|
||||||
|
g.ticker = time.NewTicker(g.interval)
|
||||||
|
go g.loopCheck()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *URLTestGroup) Close() error {
|
||||||
|
g.ticker.Stop()
|
||||||
|
close(g.close)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *URLTestGroup) Select(network string) adapter.Outbound {
|
||||||
|
var minDelay uint16
|
||||||
|
var minTime time.Time
|
||||||
|
var minOutbound adapter.Outbound
|
||||||
|
for _, detour := range g.outbounds {
|
||||||
|
if !common.Contains(detour.Network(), network) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
history := g.history.LoadURLTestHistory(RealTag(detour))
|
||||||
|
if history == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if minDelay == 0 || minDelay > history.Delay+g.tolerance || minDelay > history.Delay-g.tolerance && minTime.Before(history.Time) {
|
||||||
|
minDelay = history.Delay
|
||||||
|
minTime = history.Time
|
||||||
|
minOutbound = detour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if minOutbound == nil {
|
||||||
|
for _, detour := range g.outbounds {
|
||||||
|
if !common.Contains(detour.Network(), network) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
minOutbound = detour
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minOutbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound {
|
||||||
|
outbounds := make([]adapter.Outbound, 0, len(g.outbounds)-1)
|
||||||
|
for _, detour := range g.outbounds {
|
||||||
|
if detour != used {
|
||||||
|
outbounds = append(outbounds, detour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(outbounds, func(i, j int) bool {
|
||||||
|
oi := outbounds[i]
|
||||||
|
oj := outbounds[j]
|
||||||
|
hi := g.history.LoadURLTestHistory(RealTag(oi))
|
||||||
|
if hi == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hj := g.history.LoadURLTestHistory(RealTag(oj))
|
||||||
|
if hj == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return hi.Delay < hj.Delay
|
||||||
|
})
|
||||||
|
return outbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *URLTestGroup) loopCheck() {
|
||||||
|
go g.checkOutbounds()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-g.close:
|
||||||
|
return
|
||||||
|
case <-g.ticker.C:
|
||||||
|
g.checkOutbounds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *URLTestGroup) checkOutbounds() {
|
||||||
|
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[any](10))
|
||||||
|
checked := make(map[string]bool)
|
||||||
|
for _, detour := range g.outbounds {
|
||||||
|
tag := detour.Tag()
|
||||||
|
realTag := RealTag(detour)
|
||||||
|
if checked[realTag] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
history := g.history.LoadURLTestHistory(realTag)
|
||||||
|
if history != nil && time.Now().Sub(history.Time) < g.interval {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checked[realTag] = true
|
||||||
|
p, loaded := g.router.Outbound(realTag)
|
||||||
|
if !loaded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.Go(realTag, func() (any, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
|
||||||
|
defer cancel()
|
||||||
|
t, err := urltest.URLTest(ctx, g.link, p)
|
||||||
|
if err != nil {
|
||||||
|
g.logger.Debug("outbound ", tag, " unavailable: ", err)
|
||||||
|
g.history.DeleteURLTestHistory(realTag)
|
||||||
|
} else {
|
||||||
|
g.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
||||||
|
g.history.StoreURLTestHistory(realTag, &urltest.History{
|
||||||
|
Time: time.Now(),
|
||||||
|
Delay: t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
b.Wait()
|
||||||
|
}
|
||||||
@@ -11,8 +11,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/v2ray"
|
"github.com/sagernet/sing-box/transport/v2ray"
|
||||||
"github.com/sagernet/sing-box/transport/vless"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
"github.com/sagernet/sing-vmess/packetaddr"
|
||||||
|
"github.com/sagernet/sing-vmess/vless"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -73,10 +74,6 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *VLESS) Close() error {
|
|
||||||
return common.Close(h.transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.AppendContext(ctx)
|
||||||
metadata.Outbound = h.tag
|
metadata.Outbound = h.tag
|
||||||
@@ -104,7 +101,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S
|
|||||||
return h.client.DialEarlyConn(conn, destination), nil
|
return h.client.DialEarlyConn(conn, destination), nil
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
return h.client.DialPacketConn(conn, destination), nil
|
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||||
default:
|
default:
|
||||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
}
|
}
|
||||||
@@ -129,11 +126,11 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if h.xudp {
|
if h.xudp {
|
||||||
return h.client.DialXUDPPacketConn(conn, destination), nil
|
return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
|
||||||
} else if h.packetAddr {
|
} else if h.packetAddr {
|
||||||
return packetaddr.NewConn(h.client.DialPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
|
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
|
||||||
} else {
|
} else {
|
||||||
return h.client.DialPacketConn(conn, destination), nil
|
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,3 +141,7 @@ func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapt
|
|||||||
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return NewPacketConnection(ctx, h, conn, metadata)
|
return NewPacketConnection(ctx, h, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *VLESS) Close() error {
|
||||||
|
return common.Close(h.transport)
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/v2ray"
|
"github.com/sagernet/sing-box/transport/v2ray"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-vmess"
|
"github.com/sagernet/sing-vmess"
|
||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
"github.com/sagernet/sing-vmess/packetaddr"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
@@ -31,6 +32,7 @@ type VMess struct {
|
|||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
transport adapter.V2RayClientTransport
|
transport adapter.V2RayClientTransport
|
||||||
packetAddr bool
|
packetAddr bool
|
||||||
|
xudp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) {
|
func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) {
|
||||||
@@ -62,8 +64,14 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if outbound.multiplexDialer == nil && options.PacketAddr {
|
switch options.PacketEncoding {
|
||||||
|
case "":
|
||||||
|
case "packetaddr":
|
||||||
outbound.packetAddr = true
|
outbound.packetAddr = true
|
||||||
|
case "xudp":
|
||||||
|
outbound.xudp = true
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
||||||
}
|
}
|
||||||
var clientOptions []vmess.ClientOption
|
var clientOptions []vmess.ClientOption
|
||||||
if options.GlobalPadding {
|
if options.GlobalPadding {
|
||||||
@@ -88,7 +96,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *VMess) Close() error {
|
func (h *VMess) Close() error {
|
||||||
return common.Close(h.transport)
|
return common.Close(h.multiplexDialer, h.transport)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
@@ -176,7 +184,9 @@ func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if h.packetAddr {
|
if h.packetAddr {
|
||||||
return packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
|
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
|
||||||
|
} else if h.xudp {
|
||||||
|
return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
|
||||||
} else {
|
} else {
|
||||||
return h.client.DialEarlyPacketConn(conn, destination), nil
|
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,9 +162,8 @@ func (w *WireGuard) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) Close() error {
|
func (w *WireGuard) Close() error {
|
||||||
return common.Close(
|
if w.device != nil {
|
||||||
w.tunDevice,
|
w.device.Close()
|
||||||
common.PtrOrNil(w.device),
|
}
|
||||||
common.PtrOrNil(w.bind),
|
return common.Close(w.tunDevice)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -554,7 +554,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
metadata.Protocol = sniffMetadata.Protocol
|
metadata.Protocol = sniffMetadata.Protocol
|
||||||
metadata.Domain = sniffMetadata.Domain
|
metadata.Domain = sniffMetadata.Domain
|
||||||
if metadata.SniffOverrideDestination && sniff.IsDomainName(metadata.Domain) {
|
if metadata.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||||
metadata.Destination = M.Socksaddr{
|
metadata.Destination = M.Socksaddr{
|
||||||
Fqdn: metadata.Domain,
|
Fqdn: metadata.Domain,
|
||||||
Port: metadata.Destination.Port,
|
Port: metadata.Destination.Port,
|
||||||
@@ -631,7 +631,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
metadata.Protocol = sniffMetadata.Protocol
|
metadata.Protocol = sniffMetadata.Protocol
|
||||||
metadata.Domain = sniffMetadata.Domain
|
metadata.Domain = sniffMetadata.Domain
|
||||||
if metadata.SniffOverrideDestination && sniff.IsDomainName(metadata.Domain) {
|
if metadata.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||||
metadata.Destination = M.Socksaddr{
|
metadata.Destination = M.Socksaddr{
|
||||||
Fqdn: metadata.Domain,
|
Fqdn: metadata.Domain,
|
||||||
Port: metadata.Destination.Port,
|
Port: metadata.Destination.Port,
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ func NewIPCIDRItem(isSource bool, prefixStrings []string) (*IPCIDRItem, error) {
|
|||||||
|
|
||||||
func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
|
func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
if r.isSource {
|
if r.isSource {
|
||||||
return r.match(metadata.Source.Addr)
|
return r.ipSet.Contains(metadata.Source.Addr)
|
||||||
} else {
|
} else {
|
||||||
if metadata.Destination.IsIP() {
|
if metadata.Destination.IsIP() {
|
||||||
return r.match(metadata.Destination.Addr)
|
return r.ipSet.Contains(metadata.Destination.Addr)
|
||||||
} else {
|
} else {
|
||||||
for _, address := range metadata.DestinationAddresses {
|
for _, address := range metadata.DestinationAddresses {
|
||||||
if r.match(address) {
|
if r.ipSet.Contains(address) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,14 +74,6 @@ func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *IPCIDRItem) match(address netip.Addr) bool {
|
|
||||||
if address.Is4In6() {
|
|
||||||
return r.ipSet.Contains(netip.AddrFrom4(address.As4()))
|
|
||||||
} else {
|
|
||||||
return r.ipSet.Contains(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IPCIDRItem) String() string {
|
func (r *IPCIDRItem) String() string {
|
||||||
return r.description
|
return r.description
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ import (
|
|||||||
"github.com/sagernet/sing/protocol/socks"
|
"github.com/sagernet/sing/protocol/socks"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startInstance(t *testing.T, options option.Options) {
|
func TestMain(m *testing.M) {
|
||||||
|
goleak.VerifyTestMain(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startInstance(t *testing.T, options option.Options) *box.Box {
|
||||||
if debug.Enabled {
|
if debug.Enabled {
|
||||||
options.Log = &option.LogOptions{
|
options.Log = &option.LogOptions{
|
||||||
Level: "trace",
|
Level: "trace",
|
||||||
@@ -27,10 +32,12 @@ func startInstance(t *testing.T, options option.Options) {
|
|||||||
Level: "warning",
|
Level: "warning",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ctx := context.Background()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
var instance *box.Box
|
var instance *box.Box
|
||||||
var err error
|
var err error
|
||||||
for retry := 0; retry < 3; retry++ {
|
for retry := 0; retry < 3; retry++ {
|
||||||
instance, err = box.New(context.Background(), options)
|
instance, err = box.New(ctx, options)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = instance.Start()
|
err = instance.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,7 +49,9 @@ func startInstance(t *testing.T, options option.Options) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
instance.Close()
|
instance.Close()
|
||||||
|
cancel()
|
||||||
})
|
})
|
||||||
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -39,6 +38,7 @@ const (
|
|||||||
ImageShadowTLS = "ghcr.io/ihciah/shadow-tls:latest"
|
ImageShadowTLS = "ghcr.io/ihciah/shadow-tls:latest"
|
||||||
ImageShadowsocksR = "teddysun/shadowsocks-r:latest"
|
ImageShadowsocksR = "teddysun/shadowsocks-r:latest"
|
||||||
ImageXRayCore = "teddysun/xray:latest"
|
ImageXRayCore = "teddysun/xray:latest"
|
||||||
|
ImageShadowsocksLegacy = "mritd/shadowsocks:latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allImages = []string{
|
var allImages = []string{
|
||||||
@@ -53,6 +53,7 @@ var allImages = []string{
|
|||||||
ImageShadowTLS,
|
ImageShadowTLS,
|
||||||
ImageShadowsocksR,
|
ImageShadowsocksR,
|
||||||
ImageXRayCore,
|
ImageXRayCore,
|
||||||
|
ImageShadowsocksLegacy,
|
||||||
}
|
}
|
||||||
|
|
||||||
var localIP = netip.MustParseAddr("127.0.0.1")
|
var localIP = netip.MustParseAddr("127.0.0.1")
|
||||||
@@ -101,12 +102,6 @@ func init() {
|
|||||||
|
|
||||||
io.Copy(io.Discard, imageStream)
|
io.Copy(io.Discard, imageStream)
|
||||||
}
|
}
|
||||||
go func() {
|
|
||||||
err = http.ListenAndServe("0.0.0.0:8965", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) {
|
func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) {
|
||||||
|
|||||||
41
test/config/vmess-mux-client.json
Normal file
41
test/config/vmess-mux-client.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "debug"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"port": "1080",
|
||||||
|
"protocol": "socks",
|
||||||
|
"settings": {
|
||||||
|
"auth": "noauth",
|
||||||
|
"udp": true,
|
||||||
|
"ip": "127.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"protocol": "vmess",
|
||||||
|
"settings": {
|
||||||
|
"vnext": [
|
||||||
|
{
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 1234,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"alterId": 0,
|
||||||
|
"security": "none",
|
||||||
|
"experiments": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mux": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
test/go.mod
17
test/go.mod
@@ -10,10 +10,11 @@ require (
|
|||||||
github.com/docker/docker v20.10.18+incompatible
|
github.com/docker/docker v20.10.18+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/gofrs/uuid v4.3.0+incompatible
|
github.com/gofrs/uuid v4.3.0+incompatible
|
||||||
github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee
|
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||||
github.com/spyzhov/ajson v0.7.1
|
github.com/spyzhov/ajson v0.7.1
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
|
go.uber.org/goleak v1.2.0
|
||||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
berty.tech/go-libtor v1.0.385 // indirect
|
berty.tech/go-libtor v1.0.385 // indirect
|
||||||
|
github.com/Dreamacro/clash v1.11.8 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
@@ -65,20 +67,19 @@ require (
|
|||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
|
||||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 // indirect
|
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b // indirect
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 // indirect
|
github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704 // indirect
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7 // indirect
|
github.com/sagernet/sing-vmess v0.0.0-20220923035311-d70afe4ab2c9 // indirect
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 // indirect
|
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
go.uber.org/zap v1.22.0 // indirect
|
go.uber.org/zap v1.22.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d // indirect
|
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
|
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc // indirect
|
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc // indirect
|
||||||
@@ -86,7 +87,7 @@ require (
|
|||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||||
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect
|
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b // indirect
|
||||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||||
google.golang.org/grpc v1.49.0 // indirect
|
google.golang.org/grpc v1.49.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
|||||||
43
test/go.sum
43
test/go.sum
@@ -5,6 +5,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Dreamacro/clash v1.11.8 h1:t/sy3/tiihRlvV3SsliYFjj8rKpbLw5IJ2PymiHcwS8=
|
||||||
|
github.com/Dreamacro/clash v1.11.8/go.mod h1:LsWCcJFoKuL1C5F2c0m/1690wihTHYSU3J+im09yTwQ=
|
||||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
@@ -105,8 +107,8 @@ github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8t
|
|||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
@@ -161,27 +163,25 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E
|
|||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTYRwpwvEm3nc4eRdxk6vtRbouLVZAzk=
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTYRwpwvEm3nc4eRdxk6vtRbouLVZAzk=
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
||||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 h1:lQ4RFWY/wBYmXl/zJJCwQbhiEIbgEqC7j+nhZYkgwmU=
|
|
||||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0/go.mod h1:xSHLCsdgy5QIozXFEv6uDgMWzyrRdFFjr3TgL+juu6g=
|
|
||||||
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee h1:+3w7+QWnhWi3Qz7+Xcais8zViHRUPIkmxq3eYZm/zvk=
|
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f h1:GX416thAwyc0vHBOal/qplvdhFgYO2dHD5GqADCJ0Ig=
|
||||||
github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 h1:Iyfl+Rm5jcDvXuy/jpOBI3eu35ujci50tkqYHHwwg+8=
|
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b h1:cXCMNJ9heZ+c6l+qUcku60x9KyXo4SOAaJfg/6spOmU=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8/go.mod h1:bPVnJ5gJ0WmUfN1bJP9Cis0ab8SSByx6JVzyLJjDMwA=
|
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7 h1:zdvFDYMz8s0e9UmOxMk0wNGOKh64KfeWpx8UAbJJI60=
|
github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704 h1:DOQQXQbB2gq4n2FuMHrL07HRs2naCCsuiu/9l1JFb9A=
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 h1:4HYGbTDDemgBVTmaspXbkgjJlXc3hYVjNxSddJndq8Y=
|
github.com/sagernet/sing-vmess v0.0.0-20220923035311-d70afe4ab2c9 h1:w7JlEa4xHXuuPTL3Opb+Z5f/l66SYi54rSfobzuxSF8=
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
|
github.com/sagernet/sing-vmess v0.0.0-20220923035311-d70afe4ab2c9/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spyzhov/ajson v0.7.1 h1:1MDIlPc6x0zjNtpa7tDzRAyFAvRX+X8ZsvtYz5lZg6A=
|
github.com/spyzhov/ajson v0.7.1 h1:1MDIlPc6x0zjNtpa7tDzRAyFAvRX+X8ZsvtYz5lZg6A=
|
||||||
github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
|
github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
|
||||||
@@ -205,8 +205,9 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
|||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
|
||||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
|
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
@@ -218,11 +219,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY=
|
||||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||||
@@ -230,6 +230,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -266,8 +267,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -295,10 +296,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc h1:dpclq5m2YrqPGStKmtw7IcNbKLfbIqKXvNxDJKdIKYc=
|
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc h1:dpclq5m2YrqPGStKmtw7IcNbKLfbIqKXvNxDJKdIKYc=
|
||||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -334,8 +335,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 h1:5ZkdpbduT/g+9OtbSDvbF3KvfQG45CtH/ppO8FUmvCQ=
|
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b h1:qgrKnOfe1zyURRNdmDlGbN32i38Zjmw0B1+TMdHcOvg=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg=
|
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b/go.mod h1:6y4CqPAy54NwiN4nC8K+R1eMpQDB1P2d25qmunh2RSA=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
|||||||
170
test/mux_cool_test.go
Normal file
170
test/mux_cool_test.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
|
||||||
|
"github.com/spyzhov/ajson"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMuxCoolServer(t *testing.T) {
|
||||||
|
userId := newUUID()
|
||||||
|
content, err := os.ReadFile("config/vmess-mux-client.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
config, err := ajson.Unmarshal(content)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
config.MustKey("inbounds").MustIndex(0).MustKey("port").SetNumeric(float64(clientPort))
|
||||||
|
outbound := config.MustKey("outbounds").MustIndex(0).MustKey("settings").MustKey("vnext").MustIndex(0)
|
||||||
|
outbound.MustKey("port").SetNumeric(float64(serverPort))
|
||||||
|
user := outbound.MustKey("users").MustIndex(0)
|
||||||
|
user.MustKey("id").SetString(userId.String())
|
||||||
|
|
||||||
|
content, err = ajson.Marshal(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
startDockerContainer(t, DockerOptions{
|
||||||
|
Image: ImageV2RayCore,
|
||||||
|
Ports: []uint16{serverPort, testPort},
|
||||||
|
EntryPoint: "v2ray",
|
||||||
|
Stdin: content,
|
||||||
|
})
|
||||||
|
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeVMess,
|
||||||
|
VMessOptions: option.VMessInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
Users: []option.VMessUser{
|
||||||
|
{
|
||||||
|
Name: "sekai",
|
||||||
|
UUID: userId.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
testSuit(t, clientPort, testPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxCoolClient(t *testing.T) {
|
||||||
|
user := newUUID()
|
||||||
|
content, err := os.ReadFile("config/vmess-server.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
config, err := ajson.Unmarshal(content)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
inbound := config.MustKey("inbounds").MustIndex(0)
|
||||||
|
inbound.MustKey("port").SetNumeric(float64(serverPort))
|
||||||
|
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
|
||||||
|
|
||||||
|
content, err = ajson.Marshal(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
startDockerContainer(t, DockerOptions{
|
||||||
|
Image: ImageXRayCore,
|
||||||
|
Ports: []uint16{serverPort, testPort},
|
||||||
|
EntryPoint: "xray",
|
||||||
|
Stdin: content,
|
||||||
|
})
|
||||||
|
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeMixed,
|
||||||
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: clientPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeVMess,
|
||||||
|
VMessOptions: option.VMessOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: serverPort,
|
||||||
|
},
|
||||||
|
UUID: user.String(),
|
||||||
|
PacketEncoding: "xudp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuit(t, clientPort, testPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxCoolSelf(t *testing.T) {
|
||||||
|
user := newUUID()
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeMixed,
|
||||||
|
Tag: "mixed-in",
|
||||||
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: clientPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeVMess,
|
||||||
|
VMessOptions: option.VMessInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
Users: []option.VMessUser{
|
||||||
|
{
|
||||||
|
Name: "sekai",
|
||||||
|
UUID: user.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeDirect,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeVMess,
|
||||||
|
Tag: "vmess-out",
|
||||||
|
VMessOptions: option.VMessOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: serverPort,
|
||||||
|
},
|
||||||
|
UUID: user.String(),
|
||||||
|
PacketEncoding: "xudp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: &option.RouteOptions{
|
||||||
|
Rules: []option.Rule{
|
||||||
|
{
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
Inbound: []string{"mixed-in"},
|
||||||
|
Outbound: "vmess-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuit(t, clientPort, testPort)
|
||||||
|
}
|
||||||
@@ -17,6 +17,10 @@ var muxProtocols = []mux.Protocol{
|
|||||||
mux.ProtocolSMux,
|
mux.ProtocolSMux,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVMessSMux(t *testing.T) {
|
||||||
|
testVMessMux(t, mux.ProtocolSMux.String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestShadowsocksMux(t *testing.T) {
|
func TestShadowsocksMux(t *testing.T) {
|
||||||
for _, protocol := range muxProtocols {
|
for _, protocol := range muxProtocols {
|
||||||
t.Run(protocol.String(), func(t *testing.T) {
|
t.Run(protocol.String(), func(t *testing.T) {
|
||||||
@@ -25,14 +29,6 @@ func TestShadowsocksMux(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVMessMux(t *testing.T) {
|
|
||||||
for _, protocol := range muxProtocols {
|
|
||||||
t.Run(protocol.String(), func(t *testing.T) {
|
|
||||||
testVMessMux(t, protocol.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testShadowsocksMux(t *testing.T, protocol string) {
|
func testShadowsocksMux(t *testing.T, protocol string) {
|
||||||
method := shadowaead_2022.List[0]
|
method := shadowaead_2022.List[0]
|
||||||
password := mkBase64(t, 16)
|
password := mkBase64(t, 16)
|
||||||
|
|||||||
63
test/ss_plugin_test.go
Normal file
63
test/ss_plugin_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShadowsocksObfs(t *testing.T) {
|
||||||
|
for _, mode := range []string{
|
||||||
|
"http", "tls",
|
||||||
|
} {
|
||||||
|
t.Run("obfs-local "+mode, func(t *testing.T) {
|
||||||
|
testShadowsocksPlugin(t, "obfs-local", "obfs="+mode, "--plugin obfs-server --plugin-opts obfs="+mode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShadowsocksV2RayPlugin(t *testing.T) {
|
||||||
|
testShadowsocksPlugin(t, "v2ray-plugin", "", "--plugin v2ray-plugin --plugin-opts=server")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) {
|
||||||
|
startDockerContainer(t, DockerOptions{
|
||||||
|
Image: ImageShadowsocksLegacy,
|
||||||
|
Ports: []uint16{serverPort, testPort},
|
||||||
|
Env: []string{
|
||||||
|
"SS_MODULE=ss-server",
|
||||||
|
"SS_CONFIG=-s 0.0.0.0 -u -p 10000 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL " + args,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeMixed,
|
||||||
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: clientPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeShadowsocks,
|
||||||
|
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: serverPort,
|
||||||
|
},
|
||||||
|
Method: "chacha20-ietf-poly1305",
|
||||||
|
Password: "FzcLbKs2dY9mhL",
|
||||||
|
Plugin: name,
|
||||||
|
PluginOptions: opts,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuitSimple(t, clientPort, testPort)
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ func newUUID() uuid.UUID {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
func _TestVMessAuto(t *testing.T) {
|
func TestVMessAuto(t *testing.T) {
|
||||||
security := "auto"
|
security := "auto"
|
||||||
t.Run("self", func(t *testing.T) {
|
t.Run("self", func(t *testing.T) {
|
||||||
testVMessSelf(t, security, 0, false, false, false)
|
testVMessSelf(t, security, 0, false, false, false)
|
||||||
@@ -306,7 +306,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo
|
|||||||
AlterId: alterId,
|
AlterId: alterId,
|
||||||
GlobalPadding: globalPadding,
|
GlobalPadding: globalPadding,
|
||||||
AuthenticatedLength: authenticatedLength,
|
AuthenticatedLength: authenticatedLength,
|
||||||
PacketAddr: packetAddr,
|
PacketEncoding: "packetaddr",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = common.Error(buffer.Write(msg.Data))
|
err = common.Error(buffer.Write(msg.Data))
|
||||||
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port)
|
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port).Unwrap()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,20 +473,39 @@ func (c *PacketConn) ReadPacketThreadSafe() (buffer *buf.Buffer, destination M.S
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer = buf.As(msg.Data)
|
buffer = buf.As(msg.Data)
|
||||||
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port)
|
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port).Unwrap()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
return WriteUDPMessage(c.session, UDPMessage{
|
return WriteUDPMessage(c.session, UDPMessage{
|
||||||
SessionID: c.sessionId,
|
SessionID: c.sessionId,
|
||||||
Host: destination.Unwrap().AddrString(),
|
Host: destination.AddrString(),
|
||||||
Port: destination.Port,
|
Port: destination.Port,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
Data: buffer.Bytes(),
|
Data: buffer.Bytes(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
msg := <-c.msgCh
|
||||||
|
if msg == nil {
|
||||||
|
err = net.ErrClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n = copy(p, msg.Data)
|
||||||
|
addr = M.ParseSocksaddrHostPort(msg.Host, msg.Port).UDPAddr()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
err = c.WritePacket(buf.As(p), M.SocksaddrFromNet(addr))
|
||||||
|
if err == nil {
|
||||||
|
n = len(p)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *PacketConn) LocalAddr() net.Addr {
|
func (c *PacketConn) LocalAddr() net.Addr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -507,14 +526,6 @@ func (c *PacketConn) SetWriteDeadline(t time.Time) error {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
||||||
return 0, nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
||||||
return 0, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Read(b []byte) (n int, err error) {
|
func (c *PacketConn) Read(b []byte) (n int, err error) {
|
||||||
return 0, os.ErrInvalid
|
return 0, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newObfsLocal(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
func newObfsLocal(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
||||||
var plugin ObfsLocal
|
plugin := &ObfsLocal{
|
||||||
|
dialer: dialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
}
|
||||||
mode := "http"
|
mode := "http"
|
||||||
if obfsMode, loaded := pluginOpts.Get("obfs"); loaded {
|
if obfsMode, loaded := pluginOpts.Get("obfs"); loaded {
|
||||||
mode = obfsMode
|
mode = obfsMode
|
||||||
@@ -35,7 +38,7 @@ func newObfsLocal(pluginOpts Args, router adapter.Router, dialer N.Dialer, serve
|
|||||||
return nil, E.New("unknown obfs mode ", mode)
|
return nil, E.New("unknown obfs mode ", mode)
|
||||||
}
|
}
|
||||||
plugin.port = F.ToString(serverAddr.Port)
|
plugin.port = F.ToString(serverAddr.Port)
|
||||||
return &plugin, nil
|
return plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObfsLocal struct {
|
type ObfsLocal struct {
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package sip003
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/v2ray"
|
"github.com/sagernet/sing-box/transport/v2ray"
|
||||||
|
"github.com/sagernet/sing-vmess"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -56,6 +59,7 @@ func newV2RayPlugin(pluginOpts Args, router adapter.Router, dialer N.Dialer, ser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mux int
|
||||||
var transportOptions option.V2RayTransportOptions
|
var transportOptions option.V2RayTransportOptions
|
||||||
switch mode {
|
switch mode {
|
||||||
case "websocket":
|
case "websocket":
|
||||||
@@ -68,6 +72,15 @@ func newV2RayPlugin(pluginOpts Args, router adapter.Router, dialer N.Dialer, ser
|
|||||||
Path: path,
|
Path: path,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if muxOpt, loaded := pluginOpts.Get("mux"); loaded {
|
||||||
|
muxVal, err := strconv.Atoi(muxOpt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse mux value")
|
||||||
|
}
|
||||||
|
mux = muxVal
|
||||||
|
} else {
|
||||||
|
mux = 1
|
||||||
|
}
|
||||||
case "quic":
|
case "quic":
|
||||||
transportOptions = option.V2RayTransportOptions{
|
transportOptions = option.V2RayTransportOptions{
|
||||||
Type: C.V2RayTransportTypeQUIC,
|
Type: C.V2RayTransportTypeQUIC,
|
||||||
@@ -76,5 +89,28 @@ func newV2RayPlugin(pluginOpts Args, router adapter.Router, dialer N.Dialer, ser
|
|||||||
return nil, E.New("v2ray-plugin: unknown mode: " + mode)
|
return nil, E.New("v2ray-plugin: unknown mode: " + mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v2ray.NewClientTransport(context.Background(), dialer, serverAddr, transportOptions, tlsClient)
|
transport, err := v2ray.NewClientTransport(context.Background(), dialer, serverAddr, transportOptions, tlsClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mux > 0 {
|
||||||
|
return &v2rayMuxWrapper{transport}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return transport, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Plugin = (*v2rayMuxWrapper)(nil)
|
||||||
|
|
||||||
|
type v2rayMuxWrapper struct {
|
||||||
|
adapter.V2RayClientTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *v2rayMuxWrapper) DialContext(ctx context.Context) (net.Conn, error) {
|
||||||
|
conn, err := w.V2RayClientTransport.DialContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vmess.NewMuxConnWrapper(conn, vmess.MuxDestination), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
@@ -13,6 +14,8 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
gM "google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||||
@@ -41,7 +44,22 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t
|
|||||||
func (s *Server) Tun(server GunService_TunServer) error {
|
func (s *Server) Tun(server GunService_TunServer) error {
|
||||||
ctx, cancel := context.WithCancel(s.ctx)
|
ctx, cancel := context.WithCancel(s.ctx)
|
||||||
conn := NewGRPCConn(server, cancel)
|
conn := NewGRPCConn(server, cancel)
|
||||||
go s.handler.NewConnection(ctx, conn, M.Metadata{})
|
var metadata M.Metadata
|
||||||
|
if remotePeer, loaded := peer.FromContext(server.Context()); loaded {
|
||||||
|
metadata.Source = M.SocksaddrFromNet(remotePeer.Addr)
|
||||||
|
}
|
||||||
|
if grpcMetadata, loaded := gM.FromIncomingContext(server.Context()); loaded {
|
||||||
|
forwardFrom := strings.Join(grpcMetadata.Get("X-Forwarded-For"), ",")
|
||||||
|
if forwardFrom != "" {
|
||||||
|
for _, from := range strings.Split(forwardFrom, ",") {
|
||||||
|
originAddr := M.ParseSocksaddr(from)
|
||||||
|
if originAddr.IsValid() {
|
||||||
|
metadata.Source = originAddr.Unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go s.handler.NewConnection(ctx, conn, metadata)
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ type Client struct {
|
|||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig *tls.STDConfig
|
tlsConfig *tls.STDConfig
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
conn quic.Connection
|
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
|
conn quic.Connection
|
||||||
|
rawConn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
|
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
|
||||||
@@ -64,7 +65,6 @@ func (c *Client) offer() (quic.Connection, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.conn = conn
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +80,8 @@ func (c *Client) offerNew() (quic.Connection, error) {
|
|||||||
packetConn.Close()
|
packetConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
c.conn = quicConn
|
||||||
|
c.rawConn = udpConn
|
||||||
return quicConn, nil
|
return quicConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,3 +96,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
return &hysteria.StreamWrapper{Conn: conn, Stream: stream}, nil
|
return &hysteria.StreamWrapper{Conn: conn, Stream: stream}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
return common.Close(c.conn, c.rawConn)
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||||||
if c.maxEarlyData <= 0 {
|
if c.maxEarlyData <= 0 {
|
||||||
conn, response, err := c.dialer.DialContext(ctx, c.uri, c.headers)
|
conn, response, err := c.dialer.DialContext(ctx, c.uri, c.headers)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &WebsocketConn{Conn: conn}, nil
|
return &WebsocketConn{Conn: conn, Writer: &Writer{conn, false}}, nil
|
||||||
}
|
}
|
||||||
return nil, wrapDialError(response, err)
|
return nil, wrapDialError(response, err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,16 +10,26 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/websocket"
|
"github.com/sagernet/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebsocketConn struct {
|
type WebsocketConn struct {
|
||||||
*websocket.Conn
|
*websocket.Conn
|
||||||
|
*Writer
|
||||||
remoteAddr net.Addr
|
remoteAddr net.Addr
|
||||||
reader io.Reader
|
reader io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewServerConn(wsConn *websocket.Conn, remoteAddr net.Addr) *WebsocketConn {
|
||||||
|
return &WebsocketConn{
|
||||||
|
Conn: wsConn,
|
||||||
|
remoteAddr: remoteAddr,
|
||||||
|
Writer: &Writer{wsConn, true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *WebsocketConn) Close() error {
|
func (c *WebsocketConn) Close() error {
|
||||||
err := c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(C.TCPTimeout))
|
err := c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(C.TCPTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,14 +57,6 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WebsocketConn) Write(b []byte) (n int, err error) {
|
|
||||||
err = wrapError(c.WriteMessage(websocket.BinaryMessage, b))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *WebsocketConn) RemoteAddr() net.Addr {
|
func (c *WebsocketConn) RemoteAddr() net.Addr {
|
||||||
if c.remoteAddr != nil {
|
if c.remoteAddr != nil {
|
||||||
return c.remoteAddr
|
return c.remoteAddr
|
||||||
@@ -66,6 +68,10 @@ func (c *WebsocketConn) SetDeadline(t time.Time) error {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *WebsocketConn) FrontHeadroom() int {
|
||||||
|
return frontHeadroom
|
||||||
|
}
|
||||||
|
|
||||||
type EarlyWebsocketConn struct {
|
type EarlyWebsocketConn struct {
|
||||||
*Client
|
*Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -90,9 +96,9 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
|||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
response *http.Response
|
response *http.Response
|
||||||
)
|
)
|
||||||
if len(earlyData) > int(c.maxEarlyData) {
|
if len(b) > int(c.maxEarlyData) {
|
||||||
earlyData = earlyData[:c.maxEarlyData]
|
earlyData = b[:c.maxEarlyData]
|
||||||
lateData = lateData[c.maxEarlyData:]
|
lateData = b[c.maxEarlyData:]
|
||||||
} else {
|
} else {
|
||||||
earlyData = b
|
earlyData = b
|
||||||
}
|
}
|
||||||
@@ -111,7 +117,7 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, wrapDialError(response, err)
|
return 0, wrapDialError(response, err)
|
||||||
}
|
}
|
||||||
c.conn = &WebsocketConn{Conn: conn}
|
c.conn = &WebsocketConn{Conn: conn, Writer: &Writer{conn, false}}
|
||||||
close(c.create)
|
close(c.create)
|
||||||
if len(lateData) > 0 {
|
if len(lateData) > 0 {
|
||||||
_, err = c.conn.Write(lateData)
|
_, err = c.conn.Write(lateData)
|
||||||
@@ -122,6 +128,46 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
|||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
if c.conn != nil {
|
||||||
|
return c.conn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
earlyData []byte
|
||||||
|
lateData []byte
|
||||||
|
conn *websocket.Conn
|
||||||
|
response *http.Response
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if buffer.Len() > int(c.maxEarlyData) {
|
||||||
|
earlyData = buffer.Bytes()[:c.maxEarlyData]
|
||||||
|
lateData = buffer.Bytes()[c.maxEarlyData:]
|
||||||
|
} else {
|
||||||
|
earlyData = buffer.Bytes()
|
||||||
|
}
|
||||||
|
if len(earlyData) > 0 {
|
||||||
|
earlyDataString := base64.RawURLEncoding.EncodeToString(earlyData)
|
||||||
|
if c.earlyDataHeaderName == "" {
|
||||||
|
conn, response, err = c.dialer.DialContext(c.ctx, c.uri+earlyDataString, c.headers)
|
||||||
|
} else {
|
||||||
|
headers := c.headers.Clone()
|
||||||
|
headers.Set(c.earlyDataHeaderName, earlyDataString)
|
||||||
|
conn, response, err = c.dialer.DialContext(c.ctx, c.uri, headers)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conn, response, err = c.dialer.DialContext(c.ctx, c.uri, c.headers)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return wrapDialError(response, err)
|
||||||
|
}
|
||||||
|
c.conn = &WebsocketConn{Conn: conn, Writer: &Writer{conn, false}}
|
||||||
|
close(c.create)
|
||||||
|
if len(lateData) > 0 {
|
||||||
|
_, err = c.conn.Write(lateData)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) Close() error {
|
func (c *EarlyWebsocketConn) Close() error {
|
||||||
if c.conn == nil {
|
if c.conn == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -164,6 +210,10 @@ func (c *EarlyWebsocketConn) SetWriteDeadline(t time.Time) error {
|
|||||||
return c.conn.SetWriteDeadline(t)
|
return c.conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *EarlyWebsocketConn) FrontHeadroom() int {
|
||||||
|
return frontHeadroom
|
||||||
|
}
|
||||||
|
|
||||||
func wrapError(err error) error {
|
func wrapError(err error) error {
|
||||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
|
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
|
|||||||
6
transport/v2raywebsocket/mask.go
Normal file
6
transport/v2raywebsocket/mask.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package v2raywebsocket
|
||||||
|
|
||||||
|
import _ "unsafe"
|
||||||
|
|
||||||
|
//go:linkname maskBytes github.com/sagernet/websocket.maskBytes
|
||||||
|
func maskBytes(key [4]byte, pos int, b []byte) int
|
||||||
@@ -108,10 +108,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
var metadata M.Metadata
|
var metadata M.Metadata
|
||||||
metadata.Source = sHttp.SourceAddress(request)
|
metadata.Source = sHttp.SourceAddress(request)
|
||||||
conn = &WebsocketConn{
|
conn = NewServerConn(wsConn, metadata.Source.TCPAddr())
|
||||||
Conn: wsConn,
|
|
||||||
remoteAddr: metadata.Source.TCPAddr(),
|
|
||||||
}
|
|
||||||
if len(earlyData) > 0 {
|
if len(earlyData) > 0 {
|
||||||
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
|
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
|
||||||
}
|
}
|
||||||
|
|||||||
73
transport/v2raywebsocket/writer.go
Normal file
73
transport/v2raywebsocket/writer.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package v2raywebsocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const frontHeadroom = 14
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
*websocket.Conn
|
||||||
|
isServer bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
err = w.Conn.WriteMessage(websocket.BinaryMessage, p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
|
||||||
|
var payloadBitLength int
|
||||||
|
dataLen := buffer.Len()
|
||||||
|
data := buffer.Bytes()
|
||||||
|
if dataLen < 126 {
|
||||||
|
payloadBitLength = 1
|
||||||
|
} else if dataLen < 65536 {
|
||||||
|
payloadBitLength = 3
|
||||||
|
} else {
|
||||||
|
payloadBitLength = 9
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerLen int
|
||||||
|
headerLen += 1 // FIN / RSV / OPCODE
|
||||||
|
headerLen += payloadBitLength
|
||||||
|
if !w.isServer {
|
||||||
|
headerLen += 4 // MASK KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
header := buffer.ExtendHeader(headerLen)
|
||||||
|
header[0] = websocket.BinaryMessage | 1<<7
|
||||||
|
if w.isServer {
|
||||||
|
header[1] = 0
|
||||||
|
} else {
|
||||||
|
header[1] = 1 << 7
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataLen < 126 {
|
||||||
|
header[1] |= byte(dataLen)
|
||||||
|
} else if dataLen < 65536 {
|
||||||
|
header[1] |= 126
|
||||||
|
binary.BigEndian.PutUint16(header[2:], uint16(dataLen))
|
||||||
|
} else {
|
||||||
|
header[1] |= 127
|
||||||
|
binary.BigEndian.PutUint64(header[2:], uint64(dataLen))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !w.isServer {
|
||||||
|
maskKey := rand.Uint32()
|
||||||
|
binary.BigEndian.PutUint32(header[1+payloadBitLength:], maskKey)
|
||||||
|
maskBytes(*(*[4]byte)(header[1+payloadBitLength:]), 0, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.Error(w.Conn.NetConn().Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package vless
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
key []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(userId string) (*Client, error) {
|
|
||||||
user := uuid.FromStringOrNil(userId)
|
|
||||||
if user == uuid.Nil {
|
|
||||||
user = uuid.NewV5(user, userId)
|
|
||||||
}
|
|
||||||
return &Client{key: user.Bytes()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) *Conn {
|
|
||||||
return &Conn{Conn: conn, key: c.key, destination: destination}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) *PacketConn {
|
|
||||||
return &PacketConn{Conn: conn, key: c.key, destination: destination}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) *XUDPConn {
|
|
||||||
return &XUDPConn{Conn: conn, key: c.key, destination: destination}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
key []byte
|
|
||||||
destination M.Socksaddr
|
|
||||||
requestWritten bool
|
|
||||||
responseRead bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
err = ReadResponse(c.Conn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
|
||||||
if !c.requestWritten {
|
|
||||||
err = WriteRequest(c.Conn, Request{c.key, CommandTCP, c.destination}, b)
|
|
||||||
if err == nil {
|
|
||||||
n = len(b)
|
|
||||||
}
|
|
||||||
c.requestWritten = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type PacketConn struct {
|
|
||||||
net.Conn
|
|
||||||
key []byte
|
|
||||||
destination M.Socksaddr
|
|
||||||
requestWritten bool
|
|
||||||
responseRead bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Read(b []byte) (n int, err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
err = ReadResponse(c.Conn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
}
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(c.Conn, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cap(b) < int(length) {
|
|
||||||
return 0, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
return io.ReadFull(c.Conn, b[:length])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Write(b []byte) (n int, err error) {
|
|
||||||
if !c.requestWritten {
|
|
||||||
err = WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, b)
|
|
||||||
if err == nil {
|
|
||||||
n = len(b)
|
|
||||||
}
|
|
||||||
c.requestWritten = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = binary.Write(c.Conn, binary.BigEndian, uint16(len(b)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
defer buffer.Release()
|
|
||||||
dataLen := buffer.Len()
|
|
||||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(dataLen))
|
|
||||||
if !c.requestWritten {
|
|
||||||
err := WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, buffer.Bytes())
|
|
||||||
c.requestWritten = true
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return common.Error(c.Conn.Write(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
||||||
n, err = c.Read(p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
||||||
return c.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) FrontHeadroom() int {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package vless
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Version = 0
|
|
||||||
CommandTCP = 1
|
|
||||||
CommandUDP = 2
|
|
||||||
CommandMux = 3
|
|
||||||
NetworkUDP = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
var AddressSerializer = M.NewSerializer(
|
|
||||||
M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
|
|
||||||
M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
|
|
||||||
M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
|
|
||||||
M.PortThenAddress(),
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
UUID []byte
|
|
||||||
Command byte
|
|
||||||
Destination M.Socksaddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteRequest(writer io.Writer, request Request, payload []byte) error {
|
|
||||||
var requestLen int
|
|
||||||
requestLen += 1 // version
|
|
||||||
requestLen += 16 // uuid
|
|
||||||
requestLen += 1 // protobuf length
|
|
||||||
requestLen += 1 // command
|
|
||||||
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
|
||||||
requestLen += len(payload)
|
|
||||||
_buffer := buf.StackNewSize(requestLen)
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
common.Must(
|
|
||||||
buffer.WriteByte(Version),
|
|
||||||
common.Error(buffer.Write(request.UUID)),
|
|
||||||
buffer.WriteByte(0),
|
|
||||||
buffer.WriteByte(CommandTCP),
|
|
||||||
AddressSerializer.WriteAddrPort(buffer, request.Destination),
|
|
||||||
common.Error(buffer.Write(payload)),
|
|
||||||
)
|
|
||||||
return common.Error(writer.Write(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func WritePacketRequest(writer io.Writer, request Request, payload []byte) error {
|
|
||||||
var requestLen int
|
|
||||||
requestLen += 1 // version
|
|
||||||
requestLen += 16 // uuid
|
|
||||||
requestLen += 1 // protobuf length
|
|
||||||
requestLen += 1 // command
|
|
||||||
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
|
||||||
if len(payload) > 0 {
|
|
||||||
requestLen += 2
|
|
||||||
requestLen += len(payload)
|
|
||||||
}
|
|
||||||
_buffer := buf.StackNewSize(requestLen)
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
common.Must(
|
|
||||||
buffer.WriteByte(Version),
|
|
||||||
common.Error(buffer.Write(request.UUID)),
|
|
||||||
buffer.WriteByte(0),
|
|
||||||
buffer.WriteByte(CommandUDP),
|
|
||||||
AddressSerializer.WriteAddrPort(buffer, request.Destination),
|
|
||||||
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
|
||||||
common.Error(buffer.Write(payload)),
|
|
||||||
)
|
|
||||||
return common.Error(writer.Write(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadResponse(reader io.Reader) error {
|
|
||||||
version, err := rw.ReadByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if version != Version {
|
|
||||||
return E.New("unknown version: ", version)
|
|
||||||
}
|
|
||||||
protobufLength, err := rw.ReadByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if protobufLength > 0 {
|
|
||||||
err = rw.SkipN(reader, int(protobufLength))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
package vless
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type XUDPConn struct {
|
|
||||||
net.Conn
|
|
||||||
key []byte
|
|
||||||
destination M.Socksaddr
|
|
||||||
requestWritten bool
|
|
||||||
responseRead bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *XUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
||||||
return 0, nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *XUDPConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
|
||||||
start := buffer.Start()
|
|
||||||
if !c.responseRead {
|
|
||||||
err = ReadResponse(c.Conn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
buffer.FullReset()
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.Conn, 6)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(buffer, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
header, err := buffer.ReadBytes(4)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch header[2] {
|
|
||||||
case 1:
|
|
||||||
// frame new
|
|
||||||
return M.Socksaddr{}, E.New("unexpected frame new")
|
|
||||||
case 2:
|
|
||||||
// frame keep
|
|
||||||
if length != 4 {
|
|
||||||
_, err = buffer.ReadFullFrom(c.Conn, int(length)-2)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buffer.Advance(1)
|
|
||||||
destination, err = AddressSerializer.ReadAddrPort(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = buffer.ReadFullFrom(c.Conn, 2)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
destination = c.destination
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
// frame end
|
|
||||||
return M.Socksaddr{}, io.EOF
|
|
||||||
case 4:
|
|
||||||
// frame keep alive
|
|
||||||
default:
|
|
||||||
return M.Socksaddr{}, E.New("unexpected frame: ", buffer.Byte(2))
|
|
||||||
}
|
|
||||||
// option error
|
|
||||||
if header[3]&2 == 2 {
|
|
||||||
return M.Socksaddr{}, E.Cause(net.ErrClosed, "remote closed")
|
|
||||||
}
|
|
||||||
// option data
|
|
||||||
if header[3]&1 != 1 {
|
|
||||||
buffer.Resize(start, 0)
|
|
||||||
return c.ReadPacket(buffer)
|
|
||||||
} else {
|
|
||||||
err = binary.Read(buffer, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buffer.Resize(start, 0)
|
|
||||||
_, err = buffer.ReadFullFrom(c.Conn, int(length))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *XUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
||||||
destination := M.SocksaddrFromNet(addr)
|
|
||||||
headerLen := c.frontHeadroom(AddressSerializer.AddrPortLen(destination))
|
|
||||||
buffer := buf.NewSize(headerLen + len(p))
|
|
||||||
buffer.Advance(headerLen)
|
|
||||||
common.Must1(buffer.Write(p))
|
|
||||||
err = c.WritePacket(buffer, destination)
|
|
||||||
if err == nil {
|
|
||||||
n = len(p)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *XUDPConn) frontHeadroom(addrLen int) int {
|
|
||||||
if !c.requestWritten {
|
|
||||||
var headerLen int
|
|
||||||
headerLen += 1 // version
|
|
||||||
headerLen += 16 // uuid
|
|
||||||
headerLen += 1 // protobuf length
|
|
||||||
headerLen += 1 // command
|
|
||||||
headerLen += 2 // frame len
|
|
||||||
headerLen += 5 // frame header
|
|
||||||
headerLen += addrLen
|
|
||||||
headerLen += 2 // payload len
|
|
||||||
return headerLen
|
|
||||||
} else {
|
|
||||||
return 7 + addrLen + 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *XUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
defer buffer.Release()
|
|
||||||
dataLen := buffer.Len()
|
|
||||||
addrLen := M.SocksaddrSerializer.AddrPortLen(destination)
|
|
||||||
if !c.requestWritten {
|
|
||||||
header := buf.With(buffer.ExtendHeader(c.frontHeadroom(addrLen)))
|
|
||||||
common.Must(
|
|
||||||
header.WriteByte(Version),
|
|
||||||
common.Error(header.Write(c.key)),
|
|
||||||
header.WriteByte(0),
|
|
||||||
header.WriteByte(CommandMux),
|
|
||||||
binary.Write(header, binary.BigEndian, uint16(5+addrLen)),
|
|
||||||
header.WriteByte(0),
|
|
||||||
header.WriteByte(0),
|
|
||||||
header.WriteByte(1), // frame type new
|
|
||||||
header.WriteByte(1), // option data
|
|
||||||
header.WriteByte(NetworkUDP),
|
|
||||||
AddressSerializer.WriteAddrPort(header, destination),
|
|
||||||
binary.Write(header, binary.BigEndian, uint16(dataLen)),
|
|
||||||
)
|
|
||||||
c.requestWritten = true
|
|
||||||
} else {
|
|
||||||
header := buffer.ExtendHeader(c.frontHeadroom(addrLen))
|
|
||||||
binary.BigEndian.PutUint16(header, uint16(5+addrLen))
|
|
||||||
header[2] = 0
|
|
||||||
header[3] = 0
|
|
||||||
header[4] = 2 // frame keep
|
|
||||||
header[5] = 1 // option data
|
|
||||||
header[6] = NetworkUDP
|
|
||||||
err := AddressSerializer.WriteAddrPort(buf.With(header[7:]), destination)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint16(header[7+addrLen:], uint16(dataLen))
|
|
||||||
}
|
|
||||||
return common.Error(c.Conn.Write(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *XUDPConn) FrontHeadroom() int {
|
|
||||||
return c.frontHeadroom(M.MaxSocksaddrLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *XUDPConn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,7 @@ type ClientBind struct {
|
|||||||
peerAddr M.Socksaddr
|
peerAddr M.Socksaddr
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
conn *wireConn
|
conn *wireConn
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientBind(ctx context.Context, dialer N.Dialer, peerAddr M.Socksaddr) *ClientBind {
|
func NewClientBind(ctx context.Context, dialer N.Dialer, peerAddr M.Socksaddr) *ClientBind {
|
||||||
@@ -63,6 +64,12 @@ func (c *ClientBind) connect() (*wireConn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
func (c *ClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
err = net.ErrClosed
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
return []conn.ReceiveFunc{c.receive}, 0, nil
|
return []conn.ReceiveFunc{c.receive}, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +82,12 @@ func (c *ClientBind) receive(b []byte) (n int, ep conn.Endpoint, err error) {
|
|||||||
n, err = udpConn.Read(b)
|
n, err = udpConn.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
udpConn.Close()
|
udpConn.Close()
|
||||||
err = &wireError{err}
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
default:
|
||||||
|
err = &wireError{err}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ep = Endpoint(c.peerAddr)
|
ep = Endpoint(c.peerAddr)
|
||||||
return
|
return
|
||||||
@@ -85,6 +97,16 @@ func (c *ClientBind) Close() error {
|
|||||||
c.connAccess.Lock()
|
c.connAccess.Lock()
|
||||||
defer c.connAccess.Unlock()
|
defer c.connAccess.Unlock()
|
||||||
common.Close(common.PtrOrNil(c.conn))
|
common.Close(common.PtrOrNil(c.conn))
|
||||||
|
if c.done == nil {
|
||||||
|
c.done = make(chan struct{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return net.ErrClosed
|
||||||
|
default:
|
||||||
|
close(c.done)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ type StackDevice struct {
|
|||||||
events chan tun.Event
|
events chan tun.Event
|
||||||
outbound chan *stack.PacketBuffer
|
outbound chan *stack.PacketBuffer
|
||||||
dispatcher stack.NetworkDispatcher
|
dispatcher stack.NetworkDispatcher
|
||||||
done chan struct{}
|
|
||||||
addr4 tcpip.Address
|
addr4 tcpip.Address
|
||||||
addr6 tcpip.Address
|
addr6 tcpip.Address
|
||||||
}
|
}
|
||||||
@@ -51,7 +50,6 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, er
|
|||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
events: make(chan tun.Event, 1),
|
events: make(chan tun.Event, 1),
|
||||||
outbound: make(chan *stack.PacketBuffer, 256),
|
outbound: make(chan *stack.PacketBuffer, 256),
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
|
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -193,11 +191,11 @@ func (w *StackDevice) Events() chan tun.Event {
|
|||||||
|
|
||||||
func (w *StackDevice) Close() error {
|
func (w *StackDevice) Close() error {
|
||||||
select {
|
select {
|
||||||
case <-w.done:
|
case <-w.events:
|
||||||
return os.ErrClosed
|
return os.ErrClosed
|
||||||
default:
|
default:
|
||||||
|
close(w.events)
|
||||||
}
|
}
|
||||||
close(w.done)
|
|
||||||
w.stack.Close()
|
w.stack.Close()
|
||||||
for _, endpoint := range w.stack.CleanupEndpoints() {
|
for _, endpoint := range w.stack.CleanupEndpoints() {
|
||||||
endpoint.Abort()
|
endpoint.Abort()
|
||||||
|
|||||||
@@ -106,5 +106,11 @@ func (w *SystemDevice) Events() chan wgTun.Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *SystemDevice) Close() error {
|
func (w *SystemDevice) Close() error {
|
||||||
|
select {
|
||||||
|
case <-w.events:
|
||||||
|
return os.ErrClosed
|
||||||
|
default:
|
||||||
|
close(w.events)
|
||||||
|
}
|
||||||
return w.device.Close()
|
return w.device.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user