mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Add proxy protocol support
This commit is contained in:
parent
aa8cdaee22
commit
1413c5022a
50
common/proxyproto/dialer.go
Normal file
50
common/proxyproto/dialer.go
Normal file
|
@ -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
common/proxyproto/listener.go
Normal file
40
common/proxyproto/listener.go
Normal file
|
@ -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
go.mod
1
go.mod
|
@ -16,6 +16,7 @@ require (
|
||||||
github.com/hashicorp/yamux v0.1.1
|
github.com/hashicorp/yamux v0.1.1
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0
|
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/certmagic v0.0.0-20220819042630-4a57f8b6853a
|
||||||
github.com/sagernet/netlink v0.0.0-20220820041223-3cd8365d17ac
|
github.com/sagernet/netlink v0.0.0-20220820041223-3cd8365d17ac
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
||||||
|
|
2
go.sum
2
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/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 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/proxyproto"
|
||||||
"github.com/sagernet/sing-box/common/settings"
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
@ -45,7 +46,7 @@ type myInboundAdapter struct {
|
||||||
|
|
||||||
// internal
|
// internal
|
||||||
|
|
||||||
tcpListener *net.TCPListener
|
tcpListener net.Listener
|
||||||
udpConn *net.UDPConn
|
udpConn *net.UDPConn
|
||||||
udpAddr M.Socksaddr
|
udpAddr M.Socksaddr
|
||||||
packetAccess sync.RWMutex
|
packetAccess sync.RWMutex
|
||||||
|
@ -101,10 +102,10 @@ func (a *myInboundAdapter) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *myInboundAdapter) ListenTCP() (*net.TCPListener, error) {
|
func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
|
||||||
var err error
|
var err error
|
||||||
bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
|
||||||
var tcpListener *net.TCPListener
|
var tcpListener net.Listener
|
||||||
if !a.listenOptions.TCPFastOpen {
|
if !a.listenOptions.TCPFastOpen {
|
||||||
tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
|
tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,11 +114,15 @@ func (a *myInboundAdapter) ListenTCP() (*net.TCPListener, error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.logger.Info("tcp server started at ", tcpListener.Addr())
|
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
|
a.tcpListener = tcpListener
|
||||||
return tcpListener, err
|
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)
|
bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
|
||||||
udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
|
udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -135,7 +140,7 @@ func (a *myInboundAdapter) Close() error {
|
||||||
err = a.clearSystemProxy()
|
err = a.clearSystemProxy()
|
||||||
}
|
}
|
||||||
return E.Errors(err, common.Close(
|
return E.Errors(err, common.Close(
|
||||||
common.PtrOrNil(a.tcpListener),
|
a.tcpListener,
|
||||||
common.PtrOrNil(a.udpConn),
|
common.PtrOrNil(a.udpConn),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -168,7 +173,7 @@ func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.Packe
|
||||||
func (a *myInboundAdapter) loopTCPIn() {
|
func (a *myInboundAdapter) loopTCPIn() {
|
||||||
tcpListener := a.tcpListener
|
tcpListener := a.tcpListener
|
||||||
for {
|
for {
|
||||||
conn, err := tcpListener.AcceptTCP()
|
conn, err := tcpListener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -183,8 +188,15 @@ 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)
|
||||||
metadata.Network = N.NetworkTCP
|
metadata.Network = N.NetworkTCP
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
if !metadata.Source.IsValid() {
|
||||||
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr())
|
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
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ package inbound
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
|
@ -26,23 +24,18 @@ import (
|
||||||
var _ adapter.Inbound = (*Hysteria)(nil)
|
var _ adapter.Inbound = (*Hysteria)(nil)
|
||||||
|
|
||||||
type Hysteria struct {
|
type Hysteria struct {
|
||||||
ctx context.Context
|
myInboundAdapter
|
||||||
router adapter.Router
|
quicConfig *quic.Config
|
||||||
logger log.ContextLogger
|
tlsConfig *TLSConfig
|
||||||
tag string
|
authKey []byte
|
||||||
listenOptions option.ListenOptions
|
xplusKey []byte
|
||||||
quicConfig *quic.Config
|
sendBPS uint64
|
||||||
tlsConfig *TLSConfig
|
recvBPS uint64
|
||||||
authKey []byte
|
listener quic.Listener
|
||||||
xplusKey []byte
|
udpAccess sync.RWMutex
|
||||||
sendBPS uint64
|
udpSessionId uint32
|
||||||
recvBPS uint64
|
udpSessions map[uint32]chan *hysteria.UDPMessage
|
||||||
udpListener net.PacketConn
|
udpDefragger hysteria.Defragger
|
||||||
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) {
|
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")
|
return nil, E.New("invalid down speed")
|
||||||
}
|
}
|
||||||
inbound := &Hysteria{
|
inbound := &Hysteria{
|
||||||
ctx: ctx,
|
myInboundAdapter: myInboundAdapter{
|
||||||
router: router,
|
protocol: C.TypeHysteria,
|
||||||
logger: logger,
|
network: []string{N.NetworkUDP},
|
||||||
tag: tag,
|
ctx: ctx,
|
||||||
quicConfig: quicConfig,
|
router: router,
|
||||||
listenOptions: options.ListenOptions,
|
logger: logger,
|
||||||
authKey: auth,
|
tag: tag,
|
||||||
xplusKey: xplus,
|
listenOptions: options.ListenOptions,
|
||||||
sendBPS: up,
|
},
|
||||||
recvBPS: down,
|
quicConfig: quicConfig,
|
||||||
udpSessions: make(map[uint32]chan *hysteria.UDPMessage),
|
authKey: auth,
|
||||||
|
xplusKey: xplus,
|
||||||
|
sendBPS: up,
|
||||||
|
recvBPS: down,
|
||||||
|
udpSessions: make(map[uint32]chan *hysteria.UDPMessage),
|
||||||
}
|
}
|
||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
|
@ -127,19 +124,8 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||||
return inbound, nil
|
return inbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) Type() string {
|
|
||||||
return C.TypeHysteria
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Hysteria) Tag() string {
|
|
||||||
return h.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Hysteria) Start() error {
|
func (h *Hysteria) Start() error {
|
||||||
listenAddr := M.SocksaddrFrom(netip.Addr(h.listenOptions.Listen), h.listenOptions.ListenPort)
|
packetConn, err := h.myInboundAdapter.ListenUDP()
|
||||||
var packetConn net.PacketConn
|
|
||||||
var err error
|
|
||||||
packetConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", listenAddr.Addr), listenAddr.UDPAddr())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -147,7 +133,6 @@ func (h *Hysteria) Start() error {
|
||||||
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
||||||
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
||||||
}
|
}
|
||||||
h.udpListener = packetConn
|
|
||||||
err = h.tlsConfig.Start()
|
err = h.tlsConfig.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -316,7 +301,7 @@ func (h *Hysteria) Close() error {
|
||||||
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
|
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
|
||||||
h.udpAccess.Unlock()
|
h.udpAccess.Unlock()
|
||||||
return common.Close(
|
return common.Close(
|
||||||
h.udpListener,
|
&h.myInboundAdapter,
|
||||||
h.listener,
|
h.listener,
|
||||||
common.PtrOrNil(h.tlsConfig),
|
common.PtrOrNil(h.tlsConfig),
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,4 +11,5 @@ type DirectOutboundOptions struct {
|
||||||
OutboundDialerOptions
|
OutboundDialerOptions
|
||||||
OverrideAddress string `json:"override_address,omitempty"`
|
OverrideAddress string `json:"override_address,omitempty"`
|
||||||
OverridePort uint16 `json:"override_port,omitempty"`
|
OverridePort uint16 `json:"override_port,omitempty"`
|
||||||
|
ProxyProtocol uint8 `json:"proxy_protocol,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,9 +106,10 @@ type InboundOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenOptions struct {
|
type ListenOptions struct {
|
||||||
Listen ListenAddress `json:"listen"`
|
Listen ListenAddress `json:"listen"`
|
||||||
ListenPort uint16 `json:"listen_port,omitempty"`
|
ListenPort uint16 `json:"listen_port,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
||||||
|
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
|
||||||
InboundOptions
|
InboundOptions
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ type SocksOutboundOptions struct {
|
||||||
type HTTPOutboundOptions struct {
|
type HTTPOutboundOptions struct {
|
||||||
OutboundDialerOptions
|
OutboundDialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
TLSOptions *OutboundTLSOptions `json:"tls,omitempty"`
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
||||||
}
|
}
|
||||||
switch options.Type {
|
switch options.Type {
|
||||||
case C.TypeDirect:
|
case C.TypeDirect:
|
||||||
return NewDirect(router, logger, options.Tag, options.DirectOptions), nil
|
return NewDirect(router, logger, options.Tag, options.DirectOptions)
|
||||||
case C.TypeBlock:
|
case C.TypeBlock:
|
||||||
return NewBlock(logger, options.Tag), nil
|
return NewBlock(logger, options.Tag), nil
|
||||||
case C.TypeDNS:
|
case C.TypeDNS:
|
||||||
|
|
|
@ -3,14 +3,18 @@ package outbound
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
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"
|
||||||
|
|
||||||
|
"github.com/pires/go-proxyproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Outbound = (*Direct)(nil)
|
var _ adapter.Outbound = (*Direct)(nil)
|
||||||
|
@ -20,9 +24,10 @@ type Direct struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
overrideOption int
|
overrideOption int
|
||||||
overrideDestination M.Socksaddr
|
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{
|
outbound := &Direct{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeDirect,
|
protocol: C.TypeDirect,
|
||||||
|
@ -31,7 +36,11 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
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 {
|
if options.OverrideAddress != "" && options.OverridePort != 0 {
|
||||||
outbound.overrideOption = 1
|
outbound.overrideOption = 1
|
||||||
|
@ -43,11 +52,12 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
|
||||||
outbound.overrideOption = 3
|
outbound.overrideOption = 3
|
||||||
outbound.overrideDestination = M.Socksaddr{Port: options.OverridePort}
|
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) {
|
func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.AppendContext(ctx)
|
||||||
|
originDestination := metadata.Destination
|
||||||
metadata.Outbound = h.tag
|
metadata.Outbound = h.tag
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
switch h.overrideOption {
|
switch h.overrideOption {
|
||||||
|
@ -60,13 +70,33 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M.
|
||||||
case 3:
|
case 3:
|
||||||
destination.Port = h.overrideDestination.Port
|
destination.Port = h.overrideDestination.Port
|
||||||
}
|
}
|
||||||
|
network = N.NetworkName(network)
|
||||||
switch network {
|
switch network {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
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.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) {
|
func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ type HTTP struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
64
test/direct_test.go
Normal file
64
test/direct_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -10,11 +10,11 @@ require (
|
||||||
github.com/docker/docker v20.10.17+incompatible
|
github.com/docker/docker v20.10.17+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/gofrs/uuid v4.2.0+incompatible
|
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/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
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234
|
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -51,6 +51,7 @@ require (
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0 // 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/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
|
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
|
||||||
|
|
10
test/go.sum
10
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/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 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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/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-20220822075357-8b9965b73533 h1:oOOlmOE6QAGtkYeNyboTm/RzPO8g9mCybg0xveWxvnI=
|
github.com/sagernet/sing v0.0.0-20220823075935-c333192241ec h1:71B48luR/x6uGug+8VN1oUwGuBNgpb7lgb0q9FjgsVw=
|
||||||
github.com/sagernet/sing v0.0.0-20220822075357-8b9965b73533/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
|
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 h1:XUTocA/Ek0dFxUX+xJCWMPPFZCn2GC/uLrBjTSr1vHY=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666/go.mod h1:eDyH7AJmqBGjZQdQmpZIzlbTREudZuWDExMuGKgjRVM=
|
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=
|
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-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-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-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-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes=
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
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-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=
|
||||||
|
|
Loading…
Reference in a new issue