github.com/sagernet/sing-box@v1.9.0-rc.20/outbound/direct.go (about) 1 package outbound 2 3 import ( 4 "context" 5 "net" 6 "net/netip" 7 "time" 8 9 "github.com/sagernet/sing-box/adapter" 10 "github.com/sagernet/sing-box/common/dialer" 11 C "github.com/sagernet/sing-box/constant" 12 "github.com/sagernet/sing-box/log" 13 "github.com/sagernet/sing-box/option" 14 "github.com/sagernet/sing-dns" 15 "github.com/sagernet/sing/common/bufio" 16 E "github.com/sagernet/sing/common/exceptions" 17 M "github.com/sagernet/sing/common/metadata" 18 N "github.com/sagernet/sing/common/network" 19 ) 20 21 var ( 22 _ adapter.Outbound = (*Direct)(nil) 23 _ N.ParallelDialer = (*Direct)(nil) 24 ) 25 26 type Direct struct { 27 myOutboundAdapter 28 dialer N.Dialer 29 domainStrategy dns.DomainStrategy 30 fallbackDelay time.Duration 31 overrideOption int 32 overrideDestination M.Socksaddr 33 loopBack *loopBackDetector 34 } 35 36 func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { 37 options.UDPFragmentDefault = true 38 outboundDialer, err := dialer.New(router, options.DialerOptions) 39 if err != nil { 40 return nil, err 41 } 42 outbound := &Direct{ 43 myOutboundAdapter: myOutboundAdapter{ 44 protocol: C.TypeDirect, 45 network: []string{N.NetworkTCP, N.NetworkUDP}, 46 router: router, 47 logger: logger, 48 tag: tag, 49 dependencies: withDialerDependency(options.DialerOptions), 50 }, 51 domainStrategy: dns.DomainStrategy(options.DomainStrategy), 52 fallbackDelay: time.Duration(options.FallbackDelay), 53 dialer: outboundDialer, 54 loopBack: newLoopBackDetector(router), 55 } 56 if options.ProxyProtocol != 0 { 57 return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") 58 } 59 if options.OverrideAddress != "" && options.OverridePort != 0 { 60 outbound.overrideOption = 1 61 outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) 62 } else if options.OverrideAddress != "" { 63 outbound.overrideOption = 2 64 outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) 65 } else if options.OverridePort != 0 { 66 outbound.overrideOption = 3 67 outbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} 68 } 69 return outbound, nil 70 } 71 72 func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 73 ctx, metadata := adapter.AppendContext(ctx) 74 metadata.Outbound = h.tag 75 metadata.Destination = destination 76 switch h.overrideOption { 77 case 1: 78 destination = h.overrideDestination 79 case 2: 80 newDestination := h.overrideDestination 81 newDestination.Port = destination.Port 82 destination = newDestination 83 case 3: 84 destination.Port = h.overrideDestination.Port 85 } 86 network = N.NetworkName(network) 87 switch network { 88 case N.NetworkTCP: 89 h.logger.InfoContext(ctx, "outbound connection to ", destination) 90 case N.NetworkUDP: 91 h.logger.InfoContext(ctx, "outbound packet connection to ", destination) 92 } 93 conn, err := h.dialer.DialContext(ctx, network, destination) 94 if err != nil { 95 return nil, err 96 } 97 return h.loopBack.NewConn(conn), nil 98 } 99 100 func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { 101 ctx, metadata := adapter.AppendContext(ctx) 102 metadata.Outbound = h.tag 103 metadata.Destination = destination 104 switch h.overrideOption { 105 case 1, 2: 106 // override address 107 return h.DialContext(ctx, network, destination) 108 case 3: 109 destination.Port = h.overrideDestination.Port 110 } 111 network = N.NetworkName(network) 112 switch network { 113 case N.NetworkTCP: 114 h.logger.InfoContext(ctx, "outbound connection to ", destination) 115 case N.NetworkUDP: 116 h.logger.InfoContext(ctx, "outbound packet connection to ", destination) 117 } 118 var domainStrategy dns.DomainStrategy 119 if h.domainStrategy != dns.DomainStrategyAsIS { 120 domainStrategy = h.domainStrategy 121 } else { 122 domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) 123 } 124 return N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay) 125 } 126 127 func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 128 ctx, metadata := adapter.ExtendContext(ctx) 129 metadata.Outbound = h.tag 130 metadata.Destination = destination 131 originDestination := destination 132 switch h.overrideOption { 133 case 1: 134 destination = h.overrideDestination 135 case 2: 136 newDestination := h.overrideDestination 137 newDestination.Port = destination.Port 138 destination = newDestination 139 case 3: 140 destination.Port = h.overrideDestination.Port 141 } 142 if h.overrideOption == 0 { 143 h.logger.InfoContext(ctx, "outbound packet connection") 144 } else { 145 h.logger.InfoContext(ctx, "outbound packet connection to ", destination) 146 } 147 conn, err := h.dialer.ListenPacket(ctx, destination) 148 if err != nil { 149 return nil, err 150 } 151 conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) 152 if originDestination != destination { 153 conn = bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, originDestination) 154 } 155 return conn, nil 156 } 157 158 func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { 159 if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { 160 return E.New("reject loopback connection to ", metadata.Destination) 161 } 162 return NewConnection(ctx, h, conn, metadata) 163 } 164 165 func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { 166 if h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { 167 return E.New("reject loopback packet connection to ", metadata.Destination) 168 } 169 return NewPacketConnection(ctx, h, conn, metadata) 170 }