Browse Source

Add proxy protocol support

世界 3 years ago
parent
commit
1413c5022a
15 changed files with 260 additions and 71 deletions
  1. 50 0
      common/proxyproto/dialer.go
  2. 40 0
      common/proxyproto/listener.go
  3. 1 0
      go.mod
  4. 2 0
      go.sum
  5. 20 8
      inbound/default.go
  6. 29 44
      inbound/hysteria.go
  7. 1 0
      option/direct.go
  8. 5 4
      option/inbound.go
  9. 3 3
      option/simple.go
  10. 1 1
      outbound/builder.go
  11. 34 4
      outbound/direct.go
  12. 1 1
      outbound/http.go
  13. 64 0
      test/direct_test.go
  14. 3 2
      test/go.mod
  15. 6 4
      test/go.sum

+ 50 - 0
common/proxyproto/dialer.go

@@ -0,0 +1,50 @@
+package proxyproto
+
+import (
+	"context"
+	"net"
+	"net/netip"
+
+	"github.com/sagernet/sing-box/adapter"
+	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+
+	"github.com/pires/go-proxyproto"
+)
+
+var _ N.Dialer = (*Dialer)(nil)
+
+type Dialer struct {
+	N.Dialer
+}
+
+func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
+	switch N.NetworkName(network) {
+	case N.NetworkTCP:
+		conn, err := d.Dialer.DialContext(ctx, network, destination)
+		if err != nil {
+			return nil, err
+		}
+		var source M.Socksaddr
+		metadata := adapter.ContextFrom(ctx)
+		if metadata != nil {
+			source = metadata.Source
+		}
+		if !source.IsValid() {
+			source = M.SocksaddrFromNet(conn.LocalAddr())
+		}
+		if destination.Addr.Is6() {
+			source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port)
+		}
+		h := proxyproto.HeaderProxyFromAddrs(1, source.TCPAddr(), destination.TCPAddr())
+		_, err = h.WriteTo(conn)
+		if err != nil {
+			conn.Close()
+			return nil, E.Cause(err, "write proxy protocol header")
+		}
+		return conn, nil
+	default:
+		return d.Dialer.DialContext(ctx, network, destination)
+	}
+}

+ 40 - 0
common/proxyproto/listener.go

@@ -0,0 +1,40 @@
+package proxyproto
+
+import (
+	std_bufio "bufio"
+	"net"
+
+	"github.com/sagernet/sing/common/buf"
+	"github.com/sagernet/sing/common/bufio"
+	M "github.com/sagernet/sing/common/metadata"
+
+	"github.com/pires/go-proxyproto"
+)
+
+type Listener struct {
+	net.Listener
+}
+
+func (l *Listener) Accept() (net.Conn, error) {
+	conn, err := l.Listener.Accept()
+	if err != nil {
+		return nil, err
+	}
+	bufReader := std_bufio.NewReader(conn)
+	header, err := proxyproto.Read(bufReader)
+	if err != nil {
+		return nil, err
+	}
+	if bufReader.Buffered() > 0 {
+		cache := buf.NewSize(bufReader.Buffered())
+		_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
+		if err != nil {
+			return nil, err
+		}
+		conn = bufio.NewCachedConn(conn, cache)
+	}
+	return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
+		Source:      M.SocksaddrFromNet(header.SourceAddr),
+		Destination: M.SocksaddrFromNet(header.DestinationAddr),
+	}}, nil
+}

+ 1 - 0
go.mod

@@ -16,6 +16,7 @@ require (
 	github.com/hashicorp/yamux v0.1.1
 	github.com/logrusorgru/aurora v2.0.3+incompatible
 	github.com/oschwald/maxminddb-golang v1.10.0
+	github.com/pires/go-proxyproto v0.6.2
 	github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a
 	github.com/sagernet/netlink v0.0.0-20220820041223-3cd8365d17ac
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb

+ 2 - 0
go.sum

@@ -121,6 +121,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
 github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
 github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
+github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
+github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

+ 20 - 8
inbound/default.go

@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/proxyproto"
 	"github.com/sagernet/sing-box/common/settings"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
@@ -45,7 +46,7 @@ type myInboundAdapter struct {
 
 	// internal
 
-	tcpListener          *net.TCPListener
+	tcpListener          net.Listener
 	udpConn              *net.UDPConn
 	udpAddr              M.Socksaddr
 	packetAccess         sync.RWMutex
@@ -101,10 +102,10 @@ func (a *myInboundAdapter) Start() error {
 	return nil
 }
 
-func (a *myInboundAdapter) ListenTCP() (*net.TCPListener, error) {
+func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
 	var err error
 	bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
-	var tcpListener *net.TCPListener
+	var tcpListener net.Listener
 	if !a.listenOptions.TCPFastOpen {
 		tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
 	} else {
@@ -113,11 +114,15 @@ func (a *myInboundAdapter) ListenTCP() (*net.TCPListener, error) {
 	if err == nil {
 		a.logger.Info("tcp server started at ", tcpListener.Addr())
 	}
+	if a.listenOptions.ProxyProtocol {
+		a.logger.Debug("proxy protocol enabled")
+		tcpListener = &proxyproto.Listener{Listener: tcpListener}
+	}
 	a.tcpListener = tcpListener
 	return tcpListener, err
 }
 
-func (a *myInboundAdapter) ListenUDP() (*net.UDPConn, error) {
+func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) {
 	bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
 	udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
 	if err != nil {
@@ -135,7 +140,7 @@ func (a *myInboundAdapter) Close() error {
 		err = a.clearSystemProxy()
 	}
 	return E.Errors(err, common.Close(
-		common.PtrOrNil(a.tcpListener),
+		a.tcpListener,
 		common.PtrOrNil(a.udpConn),
 	))
 }
@@ -168,7 +173,7 @@ func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.Packe
 func (a *myInboundAdapter) loopTCPIn() {
 	tcpListener := a.tcpListener
 	for {
-		conn, err := tcpListener.AcceptTCP()
+		conn, err := tcpListener.Accept()
 		if err != nil {
 			return
 		}
@@ -183,8 +188,15 @@ func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.Inboun
 	metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
 	metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
 	metadata.Network = N.NetworkTCP
-	metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
-	metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr())
+	if !metadata.Source.IsValid() {
+		metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
+	}
+	if !metadata.Destination.IsValid() {
+		metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr())
+	}
+	if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP {
+		metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr())
+	}
 	return metadata
 }
 

+ 29 - 44
inbound/hysteria.go

@@ -5,8 +5,6 @@ package inbound
 import (
 	"bytes"
 	"context"
-	"net"
-	"net/netip"
 	"sync"
 
 	"github.com/sagernet/quic-go"
@@ -26,23 +24,18 @@ import (
 var _ adapter.Inbound = (*Hysteria)(nil)
 
 type Hysteria struct {
-	ctx           context.Context
-	router        adapter.Router
-	logger        log.ContextLogger
-	tag           string
-	listenOptions option.ListenOptions
-	quicConfig    *quic.Config
-	tlsConfig     *TLSConfig
-	authKey       []byte
-	xplusKey      []byte
-	sendBPS       uint64
-	recvBPS       uint64
-	udpListener   net.PacketConn
-	listener      quic.Listener
-	udpAccess     sync.RWMutex
-	udpSessionId  uint32
-	udpSessions   map[uint32]chan *hysteria.UDPMessage
-	udpDefragger  hysteria.Defragger
+	myInboundAdapter
+	quicConfig   *quic.Config
+	tlsConfig    *TLSConfig
+	authKey      []byte
+	xplusKey     []byte
+	sendBPS      uint64
+	recvBPS      uint64
+	listener     quic.Listener
+	udpAccess    sync.RWMutex
+	udpSessionId uint32
+	udpSessions  map[uint32]chan *hysteria.UDPMessage
+	udpDefragger hysteria.Defragger
 }
 
 func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) {
@@ -101,17 +94,21 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
 		return nil, E.New("invalid down speed")
 	}
 	inbound := &Hysteria{
-		ctx:           ctx,
-		router:        router,
-		logger:        logger,
-		tag:           tag,
-		quicConfig:    quicConfig,
-		listenOptions: options.ListenOptions,
-		authKey:       auth,
-		xplusKey:      xplus,
-		sendBPS:       up,
-		recvBPS:       down,
-		udpSessions:   make(map[uint32]chan *hysteria.UDPMessage),
+		myInboundAdapter: myInboundAdapter{
+			protocol:      C.TypeHysteria,
+			network:       []string{N.NetworkUDP},
+			ctx:           ctx,
+			router:        router,
+			logger:        logger,
+			tag:           tag,
+			listenOptions: options.ListenOptions,
+		},
+		quicConfig:  quicConfig,
+		authKey:     auth,
+		xplusKey:    xplus,
+		sendBPS:     up,
+		recvBPS:     down,
+		udpSessions: make(map[uint32]chan *hysteria.UDPMessage),
 	}
 	if options.TLS == nil || !options.TLS.Enabled {
 		return nil, C.ErrTLSRequired
@@ -127,19 +124,8 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
 	return inbound, nil
 }
 
-func (h *Hysteria) Type() string {
-	return C.TypeHysteria
-}
-
-func (h *Hysteria) Tag() string {
-	return h.tag
-}
-
 func (h *Hysteria) Start() error {
-	listenAddr := M.SocksaddrFrom(netip.Addr(h.listenOptions.Listen), h.listenOptions.ListenPort)
-	var packetConn net.PacketConn
-	var err error
-	packetConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", listenAddr.Addr), listenAddr.UDPAddr())
+	packetConn, err := h.myInboundAdapter.ListenUDP()
 	if err != nil {
 		return err
 	}
@@ -147,7 +133,6 @@ func (h *Hysteria) Start() error {
 		packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
 		packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
 	}
-	h.udpListener = packetConn
 	err = h.tlsConfig.Start()
 	if err != nil {
 		return err
@@ -316,7 +301,7 @@ func (h *Hysteria) Close() error {
 	h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
 	h.udpAccess.Unlock()
 	return common.Close(
-		h.udpListener,
+		&h.myInboundAdapter,
 		h.listener,
 		common.PtrOrNil(h.tlsConfig),
 	)

+ 1 - 0
option/direct.go

@@ -11,4 +11,5 @@ type DirectOutboundOptions struct {
 	OutboundDialerOptions
 	OverrideAddress string `json:"override_address,omitempty"`
 	OverridePort    uint16 `json:"override_port,omitempty"`
+	ProxyProtocol   uint8  `json:"proxy_protocol,omitempty"`
 }

+ 5 - 4
option/inbound.go

@@ -106,9 +106,10 @@ type InboundOptions struct {
 }
 
 type ListenOptions struct {
-	Listen      ListenAddress `json:"listen"`
-	ListenPort  uint16        `json:"listen_port,omitempty"`
-	TCPFastOpen bool          `json:"tcp_fast_open,omitempty"`
-	UDPTimeout  int64         `json:"udp_timeout,omitempty"`
+	Listen        ListenAddress `json:"listen"`
+	ListenPort    uint16        `json:"listen_port,omitempty"`
+	TCPFastOpen   bool          `json:"tcp_fast_open,omitempty"`
+	UDPTimeout    int64         `json:"udp_timeout,omitempty"`
+	ProxyProtocol bool          `json:"proxy_protocol,omitempty"`
 	InboundOptions
 }

+ 3 - 3
option/simple.go

@@ -27,7 +27,7 @@ type SocksOutboundOptions struct {
 type HTTPOutboundOptions struct {
 	OutboundDialerOptions
 	ServerOptions
-	Username   string              `json:"username,omitempty"`
-	Password   string              `json:"password,omitempty"`
-	TLSOptions *OutboundTLSOptions `json:"tls,omitempty"`
+	Username string              `json:"username,omitempty"`
+	Password string              `json:"password,omitempty"`
+	TLS      *OutboundTLSOptions `json:"tls,omitempty"`
 }

+ 1 - 1
outbound/builder.go

@@ -16,7 +16,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
 	}
 	switch options.Type {
 	case C.TypeDirect:
-		return NewDirect(router, logger, options.Tag, options.DirectOptions), nil
+		return NewDirect(router, logger, options.Tag, options.DirectOptions)
 	case C.TypeBlock:
 		return NewBlock(logger, options.Tag), nil
 	case C.TypeDNS:

+ 34 - 4
outbound/direct.go

@@ -3,14 +3,18 @@ package outbound
 import (
 	"context"
 	"net"
+	"net/netip"
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/common/dialer"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
+	E "github.com/sagernet/sing/common/exceptions"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
+
+	"github.com/pires/go-proxyproto"
 )
 
 var _ adapter.Outbound = (*Direct)(nil)
@@ -20,9 +24,10 @@ type Direct struct {
 	dialer              N.Dialer
 	overrideOption      int
 	overrideDestination M.Socksaddr
+	proxyProto          uint8
 }
 
-func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) *Direct {
+func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) {
 	outbound := &Direct{
 		myOutboundAdapter: myOutboundAdapter{
 			protocol: C.TypeDirect,
@@ -31,7 +36,11 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
 			logger:   logger,
 			tag:      tag,
 		},
-		dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
+		dialer:     dialer.NewOutbound(router, options.OutboundDialerOptions),
+		proxyProto: options.ProxyProtocol,
+	}
+	if options.ProxyProtocol > 2 {
+		return nil, E.New("invalid proxy protocol option: ", options.ProxyProtocol)
 	}
 	if options.OverrideAddress != "" && options.OverridePort != 0 {
 		outbound.overrideOption = 1
@@ -43,11 +52,12 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
 		outbound.overrideOption = 3
 		outbound.overrideDestination = M.Socksaddr{Port: options.OverridePort}
 	}
-	return outbound
+	return outbound, nil
 }
 
 func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
 	ctx, metadata := adapter.AppendContext(ctx)
+	originDestination := metadata.Destination
 	metadata.Outbound = h.tag
 	metadata.Destination = destination
 	switch h.overrideOption {
@@ -60,13 +70,33 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M.
 	case 3:
 		destination.Port = h.overrideDestination.Port
 	}
+	network = N.NetworkName(network)
 	switch network {
 	case N.NetworkTCP:
 		h.logger.InfoContext(ctx, "outbound connection to ", destination)
 	case N.NetworkUDP:
 		h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
 	}
-	return h.dialer.DialContext(ctx, network, destination)
+	conn, err := h.dialer.DialContext(ctx, network, destination)
+	if err != nil {
+		return nil, err
+	}
+	if h.proxyProto > 0 {
+		source := metadata.Source
+		if !source.IsValid() {
+			source = M.SocksaddrFromNet(conn.LocalAddr())
+		}
+		if originDestination.Addr.Is6() {
+			source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port)
+		}
+		header := proxyproto.HeaderProxyFromAddrs(h.proxyProto, source.TCPAddr(), originDestination.TCPAddr())
+		_, err = header.WriteTo(conn)
+		if err != nil {
+			conn.Close()
+			return nil, E.Cause(err, "write proxy protocol header")
+		}
+	}
+	return conn, nil
 }
 
 func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {

+ 1 - 1
outbound/http.go

@@ -24,7 +24,7 @@ type HTTP struct {
 }
 
 func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
-	detour, err := dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions))
+	detour, err := dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLS))
 	if err != nil {
 		return nil, err
 	}

+ 64 - 0
test/direct_test.go

@@ -0,0 +1,64 @@
+package main
+
+import (
+	"net/netip"
+	"testing"
+
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/option"
+)
+
+func TestProxyProtocol(t *testing.T) {
+	startInstance(t, option.Options{
+		Log: &option.LogOptions{
+			Level: "error",
+		},
+		Inbounds: []option.Inbound{
+			{
+				Type: C.TypeMixed,
+				Tag:  "mixed-in",
+				MixedOptions: option.HTTPMixedInboundOptions{
+					ListenOptions: option.ListenOptions{
+						Listen:     option.ListenAddress(netip.IPv4Unspecified()),
+						ListenPort: clientPort,
+					},
+				},
+			},
+			{
+				Type: C.TypeDirect,
+				DirectOptions: option.DirectInboundOptions{
+					ListenOptions: option.ListenOptions{
+						Listen:        option.ListenAddress(netip.IPv4Unspecified()),
+						ListenPort:    serverPort,
+						ProxyProtocol: true,
+					},
+				},
+			},
+		},
+		Outbounds: []option.Outbound{
+			{
+				Type: C.TypeDirect,
+			},
+			{
+				Type: C.TypeDirect,
+				Tag:  "trojan-out",
+				DirectOptions: option.DirectOutboundOptions{
+					OverrideAddress: "127.0.0.1",
+					OverridePort:    serverPort,
+					ProxyProtocol:   2,
+				},
+			},
+		},
+		Route: &option.RouteOptions{
+			Rules: []option.Rule{
+				{
+					DefaultOptions: option.DefaultRule{
+						Inbound:  []string{"mixed-in"},
+						Outbound: "trojan-out",
+					},
+				},
+			},
+		},
+	})
+	testSuit(t, clientPort, testPort)
+}

+ 3 - 2
test/go.mod

@@ -10,11 +10,11 @@ require (
 	github.com/docker/docker v20.10.17+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/gofrs/uuid v4.2.0+incompatible
-	github.com/sagernet/sing v0.0.0-20220822075357-8b9965b73533
+	github.com/sagernet/sing v0.0.0-20220823075935-c333192241ec
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
 	github.com/spyzhov/ajson v0.7.1
 	github.com/stretchr/testify v1.8.0
-	golang.org/x/net v0.0.0-20220812174116-3211cb980234
+	golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c
 )
 
 require (
@@ -51,6 +51,7 @@ require (
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/oschwald/maxminddb-golang v1.10.0 // indirect
+	github.com/pires/go-proxyproto v0.6.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect

+ 6 - 4
test/go.sum

@@ -141,6 +141,8 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB
 github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
 github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
+github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
+github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -160,8 +162,8 @@ 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/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-20220822075357-8b9965b73533 h1:oOOlmOE6QAGtkYeNyboTm/RzPO8g9mCybg0xveWxvnI=
-github.com/sagernet/sing v0.0.0-20220822075357-8b9965b73533/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
+github.com/sagernet/sing v0.0.0-20220823075935-c333192241ec h1:71B48luR/x6uGug+8VN1oUwGuBNgpb7lgb0q9FjgsVw=
+github.com/sagernet/sing v0.0.0-20220823075935-c333192241ec/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
 github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 h1:XUTocA/Ek0dFxUX+xJCWMPPFZCn2GC/uLrBjTSr1vHY=
 github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666/go.mod h1:eDyH7AJmqBGjZQdQmpZIzlbTREudZuWDExMuGKgjRVM=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
@@ -241,8 +243,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/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-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes=
+golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c/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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=