Add http/block outbound & Improve route

This commit is contained in:
世界 2022-07-03 23:23:18 +08:00
parent 18e3f43df3
commit 4fc4eb09b0
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
25 changed files with 408 additions and 201 deletions

View file

@ -11,6 +11,7 @@ import (
type Outbound interface { type Outbound interface {
Type() string Type() string
Tag() string Tag() string
Network() []string
N.Dialer N.Dialer
NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error

View file

@ -12,7 +12,6 @@ type Router interface {
Start() error Start() error
Close() error Close() error
DefaultOutbound() Outbound
Outbound(tag string) (Outbound, bool) Outbound(tag string) (Outbound, bool)
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error

View file

@ -2,6 +2,7 @@ package constant
const ( const (
TypeDirect = "direct" TypeDirect = "direct"
TypeBlock = "block"
TypeSocks = "socks" TypeSocks = "socks"
TypeHTTP = "http" TypeHTTP = "http"
TypeMixed = "mixed" TypeMixed = "mixed"

2
go.mod
View file

@ -7,7 +7,7 @@ require (
github.com/goccy/go-json v0.9.8 github.com/goccy/go-json v0.9.8
github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/oschwald/geoip2-golang v1.7.0 github.com/oschwald/geoip2-golang v1.7.0
github.com/sagernet/sing v0.0.0-20220703114149-368e41b67bc4 github.com/sagernet/sing v0.0.0-20220703122912-677c52f01aba
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0

4
go.sum
View file

@ -20,8 +20,8 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/sing v0.0.0-20220703114149-368e41b67bc4 h1:ePp3j7E71+yJfuIxDLzYkngK1AelkP2jITjkMKaHoBs= github.com/sagernet/sing v0.0.0-20220703122912-677c52f01aba h1:ffb+Es7ddyDDOYUXKoJz5vpA+9C80GK7f7sjYN9rFvY=
github.com/sagernet/sing v0.0.0-20220703114149-368e41b67bc4/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= github.com/sagernet/sing v0.0.0-20220703122912-677c52f01aba/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=

View file

@ -1,31 +0,0 @@
package option
import (
"net/netip"
"github.com/goccy/go-json"
)
type ListenAddress netip.Addr
func (a ListenAddress) MarshalJSON() ([]byte, error) {
addr := netip.Addr(a)
if !addr.IsValid() {
return json.Marshal("")
}
return json.Marshal(addr.String())
}
func (a *ListenAddress) UnmarshalJSON(bytes []byte) error {
var value string
err := json.Unmarshal(bytes, &value)
if err != nil {
return err
}
addr, err := netip.ParseAddr(value)
if err != nil {
return err
}
*a = ListenAddress(addr)
return nil
}

View file

@ -2,14 +2,15 @@ package option
import ( import (
"github.com/goccy/go-json" "github.com/goccy/go-json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
type _Inbound struct { type _Inbound struct {
Tag string `json:"tag,omitempty"`
Type string `json:"type"` Type string `json:"type"`
Tag string `json:"tag,omitempty"`
DirectOptions DirectInboundOptions `json:"-"` DirectOptions DirectInboundOptions `json:"-"`
SocksOptions SimpleInboundOptions `json:"-"` SocksOptions SimpleInboundOptions `json:"-"`
HTTPOptions SimpleInboundOptions `json:"-"` HTTPOptions SimpleInboundOptions `json:"-"`
@ -22,25 +23,25 @@ type Inbound _Inbound
func (h Inbound) Equals(other Inbound) bool { func (h Inbound) Equals(other Inbound) bool {
return h.Type == other.Type && return h.Type == other.Type &&
h.Tag == other.Tag && h.Tag == other.Tag &&
common.Equals(h.DirectOptions, other.DirectOptions) && h.DirectOptions == other.DirectOptions &&
common.Equals(h.SocksOptions, other.SocksOptions) && h.SocksOptions.Equals(other.SocksOptions) &&
common.Equals(h.HTTPOptions, other.HTTPOptions) && h.HTTPOptions.Equals(other.HTTPOptions) &&
common.Equals(h.MixedOptions, other.MixedOptions) && h.MixedOptions.Equals(other.MixedOptions) &&
common.Equals(h.ShadowsocksOptions, other.ShadowsocksOptions) h.ShadowsocksOptions == other.ShadowsocksOptions
} }
func (h Inbound) MarshalJSON() ([]byte, error) { func (h Inbound) MarshalJSON() ([]byte, error) {
var v any var v any
switch h.Type { switch h.Type {
case "direct": case C.TypeDirect:
v = h.DirectOptions v = h.DirectOptions
case "socks": case C.TypeSocks:
v = h.SocksOptions v = h.SocksOptions
case "http": case C.TypeHTTP:
v = h.HTTPOptions v = h.HTTPOptions
case "mixed": case C.TypeMixed:
v = h.MixedOptions v = h.MixedOptions
case "shadowsocks": case C.TypeShadowsocks:
v = h.ShadowsocksOptions v = h.ShadowsocksOptions
default: default:
return nil, E.New("unknown inbound type: ", h.Type) return nil, E.New("unknown inbound type: ", h.Type)
@ -55,15 +56,15 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
} }
var v any var v any
switch h.Type { switch h.Type {
case "direct": case C.TypeDirect:
v = &h.DirectOptions v = &h.DirectOptions
case "socks": case C.TypeSocks:
v = &h.SocksOptions v = &h.SocksOptions
case "http": case C.TypeHTTP:
v = &h.HTTPOptions v = &h.HTTPOptions
case "mixed": case C.TypeMixed:
v = &h.MixedOptions v = &h.MixedOptions
case "shadowsocks": case C.TypeShadowsocks:
v = &h.ShadowsocksOptions v = &h.ShadowsocksOptions
default: default:
return nil return nil
@ -99,23 +100,9 @@ type DirectInboundOptions struct {
OverridePort uint16 `json:"override_port,omitempty"` OverridePort uint16 `json:"override_port,omitempty"`
} }
func (o DirectInboundOptions) Equals(other DirectInboundOptions) bool {
return o.ListenOptions == other.ListenOptions &&
common.ComparableSliceEquals(o.Network, other.Network) &&
o.OverrideAddress == other.OverrideAddress &&
o.OverridePort == other.OverridePort
}
type ShadowsocksInboundOptions struct { type ShadowsocksInboundOptions struct {
ListenOptions ListenOptions
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
Method string `json:"method"` Method string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
} }
func (o ShadowsocksInboundOptions) Equals(other ShadowsocksInboundOptions) bool {
return o.ListenOptions == other.ListenOptions &&
common.ComparableSliceEquals(o.Network, other.Network) &&
o.Method == other.Method &&
o.Password == other.Password
}

View file

@ -5,6 +5,8 @@ import (
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/sagernet/sing-box/common/badjson" "github.com/sagernet/sing-box/common/badjson"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
) )
func ToMap(v any) (*badjson.JSONObject, error) { func ToMap(v any) (*badjson.JSONObject, error) {
@ -33,6 +35,12 @@ func MergeObjects(objects ...any) (*badjson.JSONObject, error) {
} }
func MarshallObjects(objects ...any) ([]byte, error) { func MarshallObjects(objects ...any) ([]byte, error) {
objects = common.Filter(objects, func(v any) bool {
return v != nil
})
if len(objects) == 1 {
return json.Marshal(objects[0])
}
content, err := MergeObjects(objects...) content, err := MergeObjects(objects...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -53,6 +61,12 @@ func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error
for _, key := range parentContent.Keys() { for _, key := range parentContent.Keys() {
content.Remove(key) content.Remove(key)
} }
if object == nil {
if content.IsEmpty() {
return nil
}
return E.New("unexpected key: ", content.Keys()[0])
}
inputContent, err = content.MarshalJSON() inputContent, err = content.MarshalJSON()
if err != nil { if err != nil {
return err return err

View file

@ -1,27 +0,0 @@
package option
import "github.com/goccy/go-json"
type Listable[T comparable] []T
func (l Listable[T]) MarshalJSON() ([]byte, error) {
arrayList := []T(l)
if len(arrayList) == 1 {
return json.Marshal(arrayList[0])
}
return json.Marshal(arrayList)
}
func (l *Listable[T]) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*[]T)(l))
if err == nil {
return nil
}
var singleItem T
err = json.Unmarshal(bytes, &singleItem)
if err != nil {
return err
}
*l = []T{singleItem}
return nil
}

View file

@ -1,39 +0,0 @@
package option
import (
"github.com/goccy/go-json"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
)
type NetworkList []string
func (v *NetworkList) UnmarshalJSON(data []byte) error {
var networkList []string
err := json.Unmarshal(data, &networkList)
if err != nil {
var networkItem string
err = json.Unmarshal(data, &networkItem)
if err != nil {
return err
}
networkList = []string{networkItem}
}
for _, networkName := range networkList {
switch networkName {
case C.NetworkTCP, C.NetworkUDP:
break
default:
return E.New("unknown network: " + networkName)
}
}
*v = networkList
return nil
}
func (v *NetworkList) Build() []string {
if len(*v) == 0 {
return []string{C.NetworkTCP, C.NetworkUDP}
}
return *v
}

View file

@ -2,6 +2,7 @@ package option
import ( import (
"github.com/goccy/go-json" "github.com/goccy/go-json"
C "github.com/sagernet/sing-box/constant"
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"
) )
@ -11,6 +12,7 @@ type _Outbound struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
DirectOptions DirectOutboundOptions `json:"-"` DirectOptions DirectOutboundOptions `json:"-"`
SocksOptions SocksOutboundOptions `json:"-"` SocksOptions SocksOutboundOptions `json:"-"`
HTTPOptions HTTPOutboundOptions `json:"-"`
ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
} }
@ -19,12 +21,16 @@ type Outbound _Outbound
func (h Outbound) MarshalJSON() ([]byte, error) { func (h Outbound) MarshalJSON() ([]byte, error) {
var v any var v any
switch h.Type { switch h.Type {
case "direct": case C.TypeDirect:
v = h.DirectOptions v = h.DirectOptions
case "socks": case C.TypeSocks:
v = h.SocksOptions v = h.SocksOptions
case "shadowsocks": case C.TypeHTTP:
v = h.HTTPOptions
case C.TypeShadowsocks:
v = h.ShadowsocksOptions v = h.ShadowsocksOptions
case C.TypeBlock:
v = nil
default: default:
return nil, E.New("unknown outbound type: ", h.Type) return nil, E.New("unknown outbound type: ", h.Type)
} }
@ -38,12 +44,16 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
} }
var v any var v any
switch h.Type { switch h.Type {
case "direct": case C.TypeDirect:
v = &h.DirectOptions v = &h.DirectOptions
case "socks": case C.TypeSocks:
v = &h.SocksOptions v = &h.SocksOptions
case "shadowsocks": case C.TypeHTTP:
v = &h.HTTPOptions
case C.TypeShadowsocks:
v = &h.ShadowsocksOptions v = &h.ShadowsocksOptions
case C.TypeBlock:
v = nil
default: default:
return nil return nil
} }
@ -71,10 +81,8 @@ type OverrideStreamOptions struct {
UDPOverTCP bool `json:"udp_over_tcp,omitempty"` UDPOverTCP bool `json:"udp_over_tcp,omitempty"`
} }
type DirectOutboundOptions struct { func (o *OverrideStreamOptions) IsValid() bool {
DialerOptions return o != nil && (o.TLS || o.UDPOverTCP)
OverrideAddress string `json:"override_address,omitempty"`
OverridePort uint16 `json:"override_port,omitempty"`
} }
type ServerOptions struct { type ServerOptions struct {
@ -86,10 +94,24 @@ func (o ServerOptions) Build() M.Socksaddr {
return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
} }
type DirectOutboundOptions struct {
DialerOptions
OverrideAddress string `json:"override_address,omitempty"`
OverridePort uint16 `json:"override_port,omitempty"`
}
type SocksOutboundOptions struct { type SocksOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
}
type HTTPOutboundOptions struct {
DialerOptions
ServerOptions
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
} }
@ -97,6 +119,7 @@ type SocksOutboundOptions struct {
type ShadowsocksOutboundOptions struct { type ShadowsocksOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Method string `json:"method"` Method string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
Network NetworkList `json:"network,omitempty"`
} }

View file

@ -8,8 +8,9 @@ import (
) )
type RouteOptions struct { type RouteOptions struct {
GeoIP *GeoIPOptions `json:"geoip,omitempty"` GeoIP *GeoIPOptions `json:"geoip,omitempty"`
Rules []Rule `json:"rules,omitempty"` Rules []Rule `json:"rules,omitempty"`
DefaultDetour string `json:"default_detour,omitempty"`
} }
func (o RouteOptions) Equals(other RouteOptions) bool { func (o RouteOptions) Equals(other RouteOptions) bool {
@ -24,17 +25,17 @@ type GeoIPOptions struct {
} }
type _Rule struct { type _Rule struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
DefaultOptions *DefaultRule `json:"-"` DefaultOptions DefaultRule `json:"-"`
LogicalOptions *LogicalRule `json:"-"` LogicalOptions LogicalRule `json:"-"`
} }
type Rule _Rule type Rule _Rule
func (r Rule) Equals(other Rule) bool { func (r Rule) Equals(other Rule) bool {
return r.Type == other.Type && return r.Type == other.Type &&
common.PtrEquals(r.DefaultOptions, other.DefaultOptions) && r.DefaultOptions.Equals(other.DefaultOptions) &&
common.PtrEquals(r.LogicalOptions, other.LogicalOptions) r.LogicalOptions.Equals(other.LogicalOptions)
} }
func (r Rule) MarshalJSON() ([]byte, error) { func (r Rule) MarshalJSON() ([]byte, error) {

90
option/types.go Normal file
View file

@ -0,0 +1,90 @@
package option
import (
"net/netip"
"strings"
"github.com/goccy/go-json"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
)
type ListenAddress netip.Addr
func (a ListenAddress) MarshalJSON() ([]byte, error) {
addr := netip.Addr(a)
if !addr.IsValid() {
return json.Marshal("")
}
return json.Marshal(addr.String())
}
func (a *ListenAddress) UnmarshalJSON(content []byte) error {
var value string
err := json.Unmarshal(content, &value)
if err != nil {
return err
}
addr, err := netip.ParseAddr(value)
if err != nil {
return err
}
*a = ListenAddress(addr)
return nil
}
type NetworkList string
func (v *NetworkList) UnmarshalJSON(content []byte) error {
var networkList []string
err := json.Unmarshal(content, &networkList)
if err != nil {
var networkItem string
err = json.Unmarshal(content, &networkItem)
if err != nil {
return err
}
networkList = []string{networkItem}
}
for _, networkName := range networkList {
switch networkName {
case C.NetworkTCP, C.NetworkUDP:
break
default:
return E.New("unknown network: " + networkName)
}
}
*v = NetworkList(strings.Join(networkList, "\n"))
return nil
}
func (v NetworkList) Build() []string {
if v == "" {
return []string{C.NetworkTCP, C.NetworkUDP}
}
return strings.Split(string(v), "\n")
}
type Listable[T comparable] []T
func (l Listable[T]) MarshalJSON() ([]byte, error) {
arrayList := []T(l)
if len(arrayList) == 1 {
return json.Marshal(arrayList[0])
}
return json.Marshal(arrayList)
}
func (l *Listable[T]) UnmarshalJSON(content []byte) error {
err := json.Unmarshal(content, (*[]T)(l))
if err == nil {
return nil
}
var singleItem T
err = json.Unmarshal(content, &singleItem)
if err != nil {
return err
}
*l = []T{singleItem}
return nil
}

52
outbound/block.go Normal file
View file

@ -0,0 +1,52 @@
package outbound
import (
"context"
"io"
"net"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ adapter.Outbound = (*Block)(nil)
type Block struct {
myOutboundAdapter
}
func NewBlock(logger log.Logger, tag string) *Block {
return &Block{
myOutboundAdapter{
protocol: C.TypeBlock,
logger: logger,
tag: tag,
network: []string{C.NetworkTCP, C.NetworkUDP},
},
}
}
func (h *Block) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
h.logger.WithContext(ctx).Info("blocked connection to ", destination)
return nil, io.EOF
}
func (h *Block) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
h.logger.WithContext(ctx).Info("blocked packet connection to ", destination)
return nil, io.EOF
}
func (h *Block) NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error {
conn.Close()
h.logger.WithContext(ctx).Info("blocked connection to ", destination)
return nil
}
func (h *Block) NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error {
conn.Close()
h.logger.WithContext(ctx).Info("blocked packet connection to ", destination)
return nil
}

View file

@ -24,8 +24,12 @@ func New(router adapter.Router, logger log.Logger, index int, options option.Out
switch options.Type { switch options.Type {
case C.TypeDirect: case C.TypeDirect:
return NewDirect(router, outboundLogger, options.Tag, options.DirectOptions), nil return NewDirect(router, outboundLogger, options.Tag, options.DirectOptions), nil
case C.TypeBlock:
return NewBlock(outboundLogger, options.Tag), nil
case C.TypeSocks: case C.TypeSocks:
return NewSocks(router, outboundLogger, options.Tag, options.SocksOptions) return NewSocks(router, outboundLogger, options.Tag, options.SocksOptions)
case C.TypeHTTP:
return NewHTTP(router, outboundLogger, options.Tag, options.HTTPOptions), nil
case C.TypeShadowsocks: case C.TypeShadowsocks:
return NewShadowsocks(router, outboundLogger, options.Tag, options.ShadowsocksOptions) return NewShadowsocks(router, outboundLogger, options.Tag, options.ShadowsocksOptions)
default: default:

View file

@ -11,14 +11,13 @@ import (
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
) )
type myOutboundAdapter struct { type myOutboundAdapter struct {
protocol string protocol string
logger log.Logger logger log.Logger
tag string tag string
dialer N.Dialer network []string
} }
func (a *myOutboundAdapter) Type() string { func (a *myOutboundAdapter) Type() string {
@ -29,6 +28,10 @@ func (a *myOutboundAdapter) Tag() string {
return a.tag return a.tag
} }
func (a *myOutboundAdapter) Network() []string {
return a.network
}
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
_payload := buf.StackNew() _payload := buf.StackNew()
payload := common.Dup(_payload) payload := common.Dup(_payload)

View file

@ -14,7 +14,7 @@ func New(router adapter.Router, options option.DialerOptions) N.Dialer {
} else { } else {
dialer = newDetour(router, options) dialer = newDetour(router, options)
} }
if options.OverrideOptions != nil { if options.OverrideOptions.IsValid() {
dialer = newOverride(dialer, common.PtrValueOrDefault(options.OverrideOptions)) dialer = newOverride(dialer, common.PtrValueOrDefault(options.OverrideOptions))
} }
return dialer return dialer

View file

@ -22,9 +22,6 @@ type overrideDialer struct {
} }
func newOverride(upstream N.Dialer, options option.OverrideStreamOptions) N.Dialer { func newOverride(upstream N.Dialer, options option.OverrideStreamOptions) N.Dialer {
if !options.TLS && !options.UDPOverTCP {
return upstream
}
return &overrideDialer{ return &overrideDialer{
upstream, upstream,
options.TLS, options.TLS,

View file

@ -18,6 +18,7 @@ var _ adapter.Outbound = (*Direct)(nil)
type Direct struct { type Direct struct {
myOutboundAdapter myOutboundAdapter
dialer N.Dialer
overrideOption int overrideOption int
overrideDestination M.Socksaddr overrideDestination M.Socksaddr
} }
@ -28,8 +29,9 @@ func NewDirect(router adapter.Router, logger log.Logger, tag string, options opt
protocol: C.TypeDirect, protocol: C.TypeDirect,
logger: logger, logger: logger,
tag: tag, tag: tag,
dialer: dialer.New(router, options.DialerOptions), network: []string{C.NetworkTCP, C.NetworkUDP},
}, },
dialer: dialer.New(router, options.DialerOptions),
} }
if options.OverrideAddress != "" && options.OverridePort != 0 { if options.OverrideAddress != "" && options.OverridePort != 0 {
outbound.overrideOption = 1 outbound.overrideOption = 1

57
outbound/http.go Normal file
View file

@ -0,0 +1,57 @@
package outbound
import (
"context"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound/dialer"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/protocol/http"
)
var _ adapter.Outbound = (*HTTP)(nil)
type HTTP struct {
myOutboundAdapter
client *http.Client
}
func NewHTTP(router adapter.Router, logger log.Logger, tag string, options option.HTTPOutboundOptions) *HTTP {
return &HTTP{
myOutboundAdapter{
protocol: C.TypeHTTP,
logger: logger,
tag: tag,
network: []string{C.NetworkTCP},
},
http.NewClient(dialer.New(router, options.DialerOptions), M.ParseSocksaddrHostPort(options.Server, options.ServerPort), options.Username, options.Password),
}
}
func (h *HTTP) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
h.logger.WithContext(ctx).Info("outbound connection to ", destination)
return h.client.DialContext(ctx, network, destination)
}
func (h *HTTP) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}
func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error {
outConn, err := h.DialContext(ctx, C.NetworkTCP, destination)
if err != nil {
return err
}
return bufio.CopyConn(ctx, conn, outConn)
}
func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error {
return os.ErrInvalid
}

View file

@ -21,6 +21,7 @@ var _ adapter.Outbound = (*Shadowsocks)(nil)
type Shadowsocks struct { type Shadowsocks struct {
myOutboundAdapter myOutboundAdapter
dialer N.Dialer
method shadowsocks.Method method shadowsocks.Method
serverAddr M.Socksaddr serverAddr M.Socksaddr
} }
@ -31,8 +32,9 @@ func NewShadowsocks(router adapter.Router, logger log.Logger, tag string, option
protocol: C.TypeDirect, protocol: C.TypeDirect,
logger: logger, logger: logger,
tag: tag, tag: tag,
dialer: dialer.New(router, options.DialerOptions), network: options.Network.Build(),
}, },
dialer: dialer.New(router, options.DialerOptions),
} }
var err error var err error
outbound.method, err = shadowimpl.FetchMethod(options.Method, options.Password) outbound.method, err = shadowimpl.FetchMethod(options.Method, options.Password)

View file

@ -23,7 +23,7 @@ type Socks struct {
} }
func NewSocks(router adapter.Router, logger log.Logger, tag string, options option.SocksOutboundOptions) (*Socks, error) { func NewSocks(router adapter.Router, logger log.Logger, tag string, options option.SocksOutboundOptions) (*Socks, error) {
dialer := dialer.New(router, options.DialerOptions) detour := dialer.New(router, options.DialerOptions)
var version socks.Version var version socks.Version
var err error var err error
if options.Version != "" { if options.Version != "" {
@ -39,9 +39,9 @@ func NewSocks(router adapter.Router, logger log.Logger, tag string, options opti
protocol: C.TypeSocks, protocol: C.TypeSocks,
logger: logger, logger: logger,
tag: tag, tag: tag,
dialer: dialer, network: options.Network.Build(),
}, },
socks.NewClient(dialer, M.ParseSocksaddrHostPort(options.Server, options.ServerPort), version, options.Username, options.Password), socks.NewClient(detour, M.ParseSocksaddrHostPort(options.Server, options.ServerPort), version, options.Username, options.Password),
}, nil }, nil
} }

View file

@ -16,6 +16,7 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
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"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@ -23,11 +24,15 @@ import (
var _ adapter.Router = (*Router)(nil) var _ adapter.Router = (*Router)(nil)
type Router struct { type Router struct {
ctx context.Context ctx context.Context
logger log.Logger logger log.Logger
defaultOutbound adapter.Outbound
outboundByTag map[string]adapter.Outbound outboundByTag map[string]adapter.Outbound
rules []adapter.Rule rules []adapter.Rule
defaultDetour string
defaultOutboundForConnection adapter.Outbound
defaultOutboundForPacketConnection adapter.Outbound
needGeoDatabase bool needGeoDatabase bool
geoOptions option.GeoIPOptions geoOptions option.GeoIPOptions
@ -42,6 +47,7 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
rules: make([]adapter.Rule, 0, len(options.Rules)), rules: make([]adapter.Rule, 0, len(options.Rules)),
needGeoDatabase: hasGeoRule(options.Rules), needGeoDatabase: hasGeoRule(options.Rules),
geoOptions: common.PtrValueOrDefault(options.GeoIP), geoOptions: common.PtrValueOrDefault(options.GeoIP),
defaultDetour: options.DefaultDetour,
} }
for i, ruleOptions := range options.Rules { for i, ruleOptions := range options.Rules {
rule, err := NewRule(router, logger, ruleOptions) rule, err := NewRule(router, logger, ruleOptions)
@ -55,11 +61,12 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
func hasGeoRule(rules []option.Rule) bool { func hasGeoRule(rules []option.Rule) bool {
for _, rule := range rules { for _, rule := range rules {
if rule.DefaultOptions != nil { switch rule.Type {
if isGeoRule(common.PtrValueOrDefault(rule.DefaultOptions)) { case C.RuleTypeDefault:
if isGeoRule(rule.DefaultOptions) {
return true return true
} }
} else if rule.LogicalOptions != nil { case C.RuleTypeLogical:
for _, subRule := range rule.LogicalOptions.Rules { for _, subRule := range rule.LogicalOptions.Rules {
if isGeoRule(subRule) { if isGeoRule(subRule) {
return true return true
@ -78,17 +85,73 @@ func notPrivateNode(code string) bool {
return code == "private" return code == "private"
} }
func (r *Router) UpdateOutbounds(outbounds []adapter.Outbound) { func (r *Router) Initialize(outbounds []adapter.Outbound, defaultOutbound func() adapter.Outbound) error {
var defaultOutbound adapter.Outbound
outboundByTag := make(map[string]adapter.Outbound) outboundByTag := make(map[string]adapter.Outbound)
if len(outbounds) > 0 { for _, detour := range outbounds {
defaultOutbound = outbounds[0] outboundByTag[detour.Tag()] = detour
} }
for _, outbound := range outbounds { var defaultOutboundForConnection adapter.Outbound
outboundByTag[outbound.Tag()] = outbound var defaultOutboundForPacketConnection adapter.Outbound
if r.defaultDetour != "" {
detour, loaded := outboundByTag[r.defaultDetour]
if !loaded {
return E.New("default detour not found: ", r.defaultDetour)
}
if common.Contains(detour.Network(), C.NetworkTCP) {
defaultOutboundForConnection = detour
}
if common.Contains(detour.Network(), C.NetworkUDP) {
defaultOutboundForPacketConnection = detour
}
} }
r.defaultOutbound = defaultOutbound var index, packetIndex int
if defaultOutboundForConnection == nil {
for i, detour := range outbounds {
if common.Contains(detour.Network(), C.NetworkTCP) {
index = i
defaultOutboundForConnection = detour
break
}
}
}
if defaultOutboundForPacketConnection == nil {
for i, detour := range outbounds {
if common.Contains(detour.Network(), C.NetworkUDP) {
packetIndex = i
defaultOutboundForPacketConnection = detour
break
}
}
}
if defaultOutboundForConnection == nil || defaultOutboundForPacketConnection == nil {
detour := defaultOutbound()
if defaultOutboundForConnection == nil {
defaultOutboundForConnection = detour
}
if defaultOutboundForPacketConnection == nil {
defaultOutboundForPacketConnection = detour
}
}
if defaultOutboundForConnection != defaultOutboundForPacketConnection {
var description string
if defaultOutboundForConnection.Tag() != "" {
description = defaultOutboundForConnection.Tag()
} else {
description = F.ToString(index)
}
var packetDescription string
if defaultOutboundForPacketConnection.Tag() != "" {
packetDescription = defaultOutboundForPacketConnection.Tag()
} else {
packetDescription = F.ToString(packetIndex)
}
r.logger.Info("using ", defaultOutboundForConnection.Type(), "[", description, "] as default outbound for connection")
r.logger.Info("using ", defaultOutboundForPacketConnection.Type(), "[", packetDescription, "] as default outbound for packet connection")
}
r.defaultOutboundForConnection = defaultOutboundForConnection
r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection
r.outboundByTag = outboundByTag r.outboundByTag = outboundByTag
return nil
} }
func (r *Router) Start() error { func (r *Router) Start() error {
@ -158,7 +221,7 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error {
} }
detour = outbound detour = outbound
} else { } else {
detour = r.defaultOutbound detour = r.defaultOutboundForConnection
} }
if parentDir := filepath.Dir(savePath); parentDir != "" { if parentDir := filepath.Dir(savePath); parentDir != "" {
@ -190,27 +253,30 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error {
return err return err
} }
func (r *Router) DefaultOutbound() adapter.Outbound {
if r.defaultOutbound == nil {
panic("missing default outbound")
}
return r.defaultOutbound
}
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
outbound, loaded := r.outboundByTag[tag] outbound, loaded := r.outboundByTag[tag]
return outbound, loaded return outbound, loaded
} }
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return r.match(ctx, metadata).NewConnection(ctx, conn, metadata.Destination) detour := r.match(ctx, metadata, r.defaultOutboundForConnection)
if !common.Contains(detour.Network(), C.NetworkTCP) {
conn.Close()
return E.New("missing supported outbound, closing connection")
}
return detour.NewConnection(ctx, conn, metadata.Destination)
} }
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.match(ctx, metadata).NewPacketConnection(ctx, conn, metadata.Destination) detour := r.match(ctx, metadata, r.defaultOutboundForPacketConnection)
if !common.Contains(detour.Network(), C.NetworkUDP) {
conn.Close()
return E.New("missing supported outbound, closing packet connection")
}
return detour.NewPacketConnection(ctx, conn, metadata.Destination)
} }
func (r *Router) match(ctx context.Context, metadata adapter.InboundContext) adapter.Outbound { func (r *Router) match(ctx context.Context, metadata adapter.InboundContext, defaultOutbound adapter.Outbound) adapter.Outbound {
for i, rule := range r.rules { for i, rule := range r.rules {
if rule.Match(&metadata) { if rule.Match(&metadata) {
detour := rule.Outbound() detour := rule.Outbound()
@ -222,5 +288,5 @@ func (r *Router) match(ctx context.Context, metadata adapter.InboundContext) ada
} }
} }
r.logger.WithContext(ctx).Info("no match") r.logger.WithContext(ctx).Info("no match")
return r.defaultOutbound return defaultOutbound
} }

View file

@ -24,7 +24,7 @@ func NewRule(router adapter.Router, logger log.Logger, options option.Rule) (ada
if options.DefaultOptions.Outbound == "" { if options.DefaultOptions.Outbound == "" {
return nil, E.New("missing outbound field") return nil, E.New("missing outbound field")
} }
return NewDefaultRule(router, logger, common.PtrValueOrDefault(options.DefaultOptions)) return NewDefaultRule(router, logger, options.DefaultOptions)
case C.RuleTypeLogical: case C.RuleTypeLogical:
if !options.LogicalOptions.IsValid() { if !options.LogicalOptions.IsValid() {
return nil, E.New("missing conditions") return nil, E.New("missing conditions")
@ -32,7 +32,7 @@ func NewRule(router adapter.Router, logger log.Logger, options option.Rule) (ada
if options.LogicalOptions.Outbound == "" { if options.LogicalOptions.Outbound == "" {
return nil, E.New("missing outbound field") return nil, E.New("missing outbound field")
} }
return NewLogicalRule(router, logger, common.PtrValueOrDefault(options.LogicalOptions)) return NewLogicalRule(router, logger, options.LogicalOptions)
default: default:
return nil, E.New("unknown rule type: ", options.Type) return nil, E.New("unknown rule type: ", options.Type)
} }

View file

@ -7,7 +7,7 @@ import (
"github.com/sagernet/sing-box/inbound" "github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
outbound2 "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/route" "github.com/sagernet/sing-box/route"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -34,25 +34,30 @@ func NewService(ctx context.Context, options option.Options) (*Service, error) {
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
for i, inboundOptions := range options.Inbounds { for i, inboundOptions := range options.Inbounds {
var inboundService adapter.Inbound var in adapter.Inbound
inboundService, err = inbound.New(ctx, router, logger, i, inboundOptions) in, err = inbound.New(ctx, router, logger, i, inboundOptions)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]") return nil, E.Cause(err, "parse inbound[", i, "]")
} }
inbounds = append(inbounds, inboundService) inbounds = append(inbounds, in)
} }
for i, outboundOptions := range options.Outbounds { for i, outboundOptions := range options.Outbounds {
var outboundService adapter.Outbound var out adapter.Outbound
outboundService, err = outbound2.New(router, logger, i, outboundOptions) out, err = outbound.New(router, logger, i, outboundOptions)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]") return nil, E.Cause(err, "parse outbound[", i, "]")
} }
outbounds = append(outbounds, outboundService) outbounds = append(outbounds, out)
} }
if len(outbounds) == 0 { err = router.Initialize(outbounds, func() adapter.Outbound {
outbounds = append(outbounds, outbound2.NewDirect(nil, logger, "direct", option.DirectOutboundOptions{})) out, oErr := outbound.New(router, logger, 0, option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr)
outbounds = append(outbounds, out)
return out
})
if err != nil {
return nil, err
} }
router.UpdateOutbounds(outbounds)
return &Service{ return &Service{
router: router, router: router,
logger: logger, logger: logger,