mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-04 20:37:37 +03:00
refactor: Modular inbounds/outbounds
This commit is contained in:
parent
9f7683818f
commit
e233fd4fe5
152 changed files with 3116 additions and 2926 deletions
212
protocol/tor/outbound.go
Normal file
212
protocol/tor/outbound.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
package tor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"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"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/protocol/socks"
|
||||
|
||||
"github.com/cretz/bine/control"
|
||||
"github.com/cretz/bine/tor"
|
||||
)
|
||||
|
||||
func RegisterOutbound(registry *outbound.Registry) {
|
||||
outbound.Register[option.TorOutboundOptions](registry, C.TypeTor, NewOutbound)
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
proxy *ProxyListener
|
||||
startConf *tor.StartConf
|
||||
options map[string]string
|
||||
events chan control.Event
|
||||
instance *tor.Tor
|
||||
socksClient *socks.Client
|
||||
}
|
||||
|
||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (adapter.Outbound, error) {
|
||||
var startConf tor.StartConf
|
||||
startConf.DataDir = os.ExpandEnv(options.DataDirectory)
|
||||
startConf.TempDataDirBase = os.TempDir()
|
||||
startConf.ExtraArgs = options.ExtraArgs
|
||||
if options.DataDirectory != "" {
|
||||
dataDirAbs, _ := filepath.Abs(startConf.DataDir)
|
||||
if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.IsFile(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") {
|
||||
options.ExtraArgs = append(options.ExtraArgs, "--GeoIPFile", geoIPPath)
|
||||
}
|
||||
if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.IsFile(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") {
|
||||
options.ExtraArgs = append(options.ExtraArgs, "--GeoIPv6File", geoIP6Path)
|
||||
}
|
||||
}
|
||||
if options.ExecutablePath != "" {
|
||||
startConf.ExePath = options.ExecutablePath
|
||||
startConf.ProcessCreator = nil
|
||||
startConf.UseEmbeddedControlConn = false
|
||||
}
|
||||
if startConf.DataDir != "" {
|
||||
torrcFile := filepath.Join(startConf.DataDir, "torrc")
|
||||
err := rw.MkdirParent(torrcFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !rw.IsFile(torrcFile) {
|
||||
err := os.WriteFile(torrcFile, []byte(""), 0o600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
startConf.TorrcFile = torrcFile
|
||||
}
|
||||
outboundDialer, err := dialer.New(router, options.DialerOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Outbound{
|
||||
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, []string{N.NetworkTCP}, tag, options.DialerOptions),
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
proxy: NewProxyListener(ctx, logger, outboundDialer),
|
||||
startConf: &startConf,
|
||||
options: options.Options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Outbound) Start() error {
|
||||
err := t.start()
|
||||
if err != nil {
|
||||
t.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var torLogEvents = []control.EventCode{
|
||||
control.EventCodeLogDebug,
|
||||
control.EventCodeLogErr,
|
||||
control.EventCodeLogInfo,
|
||||
control.EventCodeLogNotice,
|
||||
control.EventCodeLogWarn,
|
||||
}
|
||||
|
||||
func (t *Outbound) start() error {
|
||||
torInstance, err := tor.Start(t.ctx, t.startConf)
|
||||
if err != nil {
|
||||
return E.New(strings.ToLower(err.Error()))
|
||||
}
|
||||
t.instance = torInstance
|
||||
t.events = make(chan control.Event, 8)
|
||||
err = torInstance.Control.AddEventListener(t.events, torLogEvents...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go t.recvLoop()
|
||||
err = t.proxy.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proxyPort := "127.0.0.1:" + F.ToString(t.proxy.Port())
|
||||
proxyUsername := t.proxy.Username()
|
||||
proxyPassword := t.proxy.Password()
|
||||
t.logger.Trace("created upstream proxy at ", proxyPort)
|
||||
t.logger.Trace("upstream proxy username ", proxyUsername)
|
||||
t.logger.Trace("upstream proxy password ", proxyPassword)
|
||||
confOptions := []*control.KeyVal{
|
||||
control.NewKeyVal("Socks5Proxy", proxyPort),
|
||||
control.NewKeyVal("Socks5ProxyUsername", proxyUsername),
|
||||
control.NewKeyVal("Socks5ProxyPassword", proxyPassword),
|
||||
}
|
||||
err = torInstance.Control.ResetConf(confOptions...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.options) > 0 {
|
||||
for key, value := range t.options {
|
||||
switch key {
|
||||
case "Socks5Proxy",
|
||||
"Socks5ProxyUsername",
|
||||
"Socks5ProxyPassword":
|
||||
continue
|
||||
}
|
||||
err = torInstance.Control.SetConf(control.NewKeyVal(key, value))
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ", key, "=", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
err = torInstance.EnableNetwork(t.ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := torInstance.Control.GetInfo("net/listeners/socks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(info) != 1 || info[0].Key != "net/listeners/socks" {
|
||||
return E.New("get socks proxy address")
|
||||
}
|
||||
t.logger.Trace("obtained tor socks5 address ", info[0].Val)
|
||||
// TODO: set password for tor socks5 server if supported
|
||||
t.socksClient = socks.NewClient(N.SystemDialer, M.ParseSocksaddr(info[0].Val), socks.Version5, "", "")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Outbound) recvLoop() {
|
||||
for rawEvent := range t.events {
|
||||
switch event := rawEvent.(type) {
|
||||
case *control.LogEvent:
|
||||
event.Raw = strings.ToLower(event.Raw)
|
||||
switch event.Severity {
|
||||
case control.EventCodeLogDebug, control.EventCodeLogInfo:
|
||||
t.logger.Trace(event.Raw)
|
||||
case control.EventCodeLogNotice:
|
||||
if strings.Contains(event.Raw, "disablenetwork") || strings.Contains(event.Raw, "socks listener") {
|
||||
t.logger.Trace(event.Raw)
|
||||
continue
|
||||
}
|
||||
t.logger.Info(event.Raw)
|
||||
case control.EventCodeLogWarn:
|
||||
t.logger.Warn(event.Raw)
|
||||
case control.EventCodeLogErr:
|
||||
t.logger.Error(event.Raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Outbound) Close() error {
|
||||
err := common.Close(
|
||||
common.PtrOrNil(t.proxy),
|
||||
common.PtrOrNil(t.instance),
|
||||
)
|
||||
if t.events != nil {
|
||||
close(t.events)
|
||||
t.events = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
t.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||
return t.socksClient.DialContext(ctx, network, destination)
|
||||
}
|
||||
|
||||
func (t *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
119
protocol/tor/proxy.go
Normal file
119
protocol/tor/proxy.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package tor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/protocol/socks"
|
||||
)
|
||||
|
||||
type ProxyListener struct {
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
dialer N.Dialer
|
||||
tcpListener *net.TCPListener
|
||||
username string
|
||||
password string
|
||||
authenticator *auth.Authenticator
|
||||
}
|
||||
|
||||
func NewProxyListener(ctx context.Context, logger log.ContextLogger, dialer N.Dialer) *ProxyListener {
|
||||
var usernameB [64]byte
|
||||
var passwordB [64]byte
|
||||
rand.Read(usernameB[:])
|
||||
rand.Read(passwordB[:])
|
||||
username := hex.EncodeToString(usernameB[:])
|
||||
password := hex.EncodeToString(passwordB[:])
|
||||
return &ProxyListener{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}),
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ProxyListener) Start() error {
|
||||
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.tcpListener = tcpListener
|
||||
go l.acceptLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *ProxyListener) Port() uint16 {
|
||||
if l.tcpListener == nil {
|
||||
panic("start listener first")
|
||||
}
|
||||
return M.SocksaddrFromNet(l.tcpListener.Addr()).Port
|
||||
}
|
||||
|
||||
func (l *ProxyListener) Username() string {
|
||||
return l.username
|
||||
}
|
||||
|
||||
func (l *ProxyListener) Password() string {
|
||||
return l.password
|
||||
}
|
||||
|
||||
func (l *ProxyListener) Close() error {
|
||||
return common.Close(l.tcpListener)
|
||||
}
|
||||
|
||||
func (l *ProxyListener) acceptLoop() {
|
||||
for {
|
||||
tcpConn, err := l.tcpListener.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx := log.ContextWithNewID(l.ctx)
|
||||
go func() {
|
||||
hErr := l.accept(ctx, tcpConn)
|
||||
if hErr != nil {
|
||||
if E.IsClosedOrCanceled(hErr) {
|
||||
l.logger.DebugContext(ctx, E.Cause(hErr, "proxy connection closed"))
|
||||
return
|
||||
}
|
||||
l.logger.ErrorContext(ctx, E.Cause(hErr, "proxy"))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: migrate to new api
|
||||
//
|
||||
//nolint:staticcheck
|
||||
func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error {
|
||||
return socks.HandleConnection(ctx, conn, l.authenticator, l, M.Metadata{})
|
||||
}
|
||||
|
||||
func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Network = N.NetworkTCP
|
||||
metadata.Destination = upstreamMetadata.Destination
|
||||
l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination)
|
||||
return outbound.NewConnection(ctx, l.dialer, conn, metadata)
|
||||
}
|
||||
|
||||
func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Network = N.NetworkUDP
|
||||
metadata.Destination = upstreamMetadata.Destination
|
||||
l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination)
|
||||
return outbound.NewPacketConnection(ctx, l.dialer, conn, metadata)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue