github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/outbound/tor.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/inazumav/sing-box/adapter"
    11  	"github.com/inazumav/sing-box/common/dialer"
    12  	C "github.com/inazumav/sing-box/constant"
    13  	"github.com/inazumav/sing-box/log"
    14  	"github.com/inazumav/sing-box/option"
    15  	"github.com/sagernet/sing/common"
    16  	E "github.com/sagernet/sing/common/exceptions"
    17  	F "github.com/sagernet/sing/common/format"
    18  	M "github.com/sagernet/sing/common/metadata"
    19  	N "github.com/sagernet/sing/common/network"
    20  	"github.com/sagernet/sing/common/rw"
    21  	"github.com/sagernet/sing/protocol/socks"
    22  
    23  	"github.com/cretz/bine/control"
    24  	"github.com/cretz/bine/tor"
    25  )
    26  
    27  var _ adapter.Outbound = (*Tor)(nil)
    28  
    29  type Tor struct {
    30  	myOutboundAdapter
    31  	ctx         context.Context
    32  	proxy       *ProxyListener
    33  	startConf   *tor.StartConf
    34  	options     map[string]string
    35  	events      chan control.Event
    36  	instance    *tor.Tor
    37  	socksClient *socks.Client
    38  }
    39  
    40  func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (*Tor, error) {
    41  	startConf := newConfig()
    42  	startConf.DataDir = os.ExpandEnv(options.DataDirectory)
    43  	startConf.TempDataDirBase = os.TempDir()
    44  	startConf.ExtraArgs = options.ExtraArgs
    45  	if options.DataDirectory != "" {
    46  		dataDirAbs, _ := filepath.Abs(startConf.DataDir)
    47  		if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.FileExists(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") {
    48  			options.ExtraArgs = append(options.ExtraArgs, "--GeoIPFile", geoIPPath)
    49  		}
    50  		if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.FileExists(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") {
    51  			options.ExtraArgs = append(options.ExtraArgs, "--GeoIPv6File", geoIP6Path)
    52  		}
    53  	}
    54  	if options.ExecutablePath != "" {
    55  		startConf.ExePath = options.ExecutablePath
    56  		startConf.ProcessCreator = nil
    57  		startConf.UseEmbeddedControlConn = false
    58  	}
    59  	if startConf.DataDir != "" {
    60  		torrcFile := filepath.Join(startConf.DataDir, "torrc")
    61  		if !rw.FileExists(torrcFile) {
    62  			err := rw.WriteFile(torrcFile, []byte(""))
    63  			if err != nil {
    64  				return nil, err
    65  			}
    66  		}
    67  		startConf.TorrcFile = torrcFile
    68  	}
    69  	outboundDialer, err := dialer.New(router, options.DialerOptions)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return &Tor{
    74  		myOutboundAdapter: myOutboundAdapter{
    75  			protocol:     C.TypeTor,
    76  			network:      []string{N.NetworkTCP},
    77  			router:       router,
    78  			logger:       logger,
    79  			tag:          tag,
    80  			dependencies: withDialerDependency(options.DialerOptions),
    81  		},
    82  		ctx:       ctx,
    83  		proxy:     NewProxyListener(ctx, logger, outboundDialer),
    84  		startConf: &startConf,
    85  		options:   options.Options,
    86  	}, nil
    87  }
    88  
    89  func (t *Tor) Start() error {
    90  	err := t.start()
    91  	if err != nil {
    92  		t.Close()
    93  	}
    94  	return err
    95  }
    96  
    97  var torLogEvents = []control.EventCode{
    98  	control.EventCodeLogDebug,
    99  	control.EventCodeLogErr,
   100  	control.EventCodeLogInfo,
   101  	control.EventCodeLogNotice,
   102  	control.EventCodeLogWarn,
   103  }
   104  
   105  func (t *Tor) start() error {
   106  	torInstance, err := tor.Start(t.ctx, t.startConf)
   107  	if err != nil {
   108  		return E.New(strings.ToLower(err.Error()))
   109  	}
   110  	t.instance = torInstance
   111  	t.events = make(chan control.Event, 8)
   112  	err = torInstance.Control.AddEventListener(t.events, torLogEvents...)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	go t.recvLoop()
   117  	err = t.proxy.Start()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	proxyPort := "127.0.0.1:" + F.ToString(t.proxy.Port())
   122  	proxyUsername := t.proxy.Username()
   123  	proxyPassword := t.proxy.Password()
   124  	t.logger.Trace("created upstream proxy at ", proxyPort)
   125  	t.logger.Trace("upstream proxy username ", proxyUsername)
   126  	t.logger.Trace("upstream proxy password ", proxyPassword)
   127  	confOptions := []*control.KeyVal{
   128  		control.NewKeyVal("Socks5Proxy", proxyPort),
   129  		control.NewKeyVal("Socks5ProxyUsername", proxyUsername),
   130  		control.NewKeyVal("Socks5ProxyPassword", proxyPassword),
   131  	}
   132  	err = torInstance.Control.ResetConf(confOptions...)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	if len(t.options) > 0 {
   137  		for key, value := range t.options {
   138  			switch key {
   139  			case "Socks5Proxy",
   140  				"Socks5ProxyUsername",
   141  				"Socks5ProxyPassword":
   142  				continue
   143  			}
   144  			err = torInstance.Control.SetConf(control.NewKeyVal(key, value))
   145  			if err != nil {
   146  				return E.Cause(err, "set ", key, "=", value)
   147  			}
   148  		}
   149  	}
   150  	err = torInstance.EnableNetwork(t.ctx, true)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	info, err := torInstance.Control.GetInfo("net/listeners/socks")
   155  	if err != nil {
   156  		return err
   157  	}
   158  	if len(info) != 1 || info[0].Key != "net/listeners/socks" {
   159  		return E.New("get socks proxy address")
   160  	}
   161  	t.logger.Trace("obtained tor socks5 address ", info[0].Val)
   162  	// TODO: set password for tor socks5 server if supported
   163  	t.socksClient = socks.NewClient(N.SystemDialer, M.ParseSocksaddr(info[0].Val), socks.Version5, "", "")
   164  	return nil
   165  }
   166  
   167  func (t *Tor) recvLoop() {
   168  	for rawEvent := range t.events {
   169  		switch event := rawEvent.(type) {
   170  		case *control.LogEvent:
   171  			event.Raw = strings.ToLower(event.Raw)
   172  			switch event.Severity {
   173  			case control.EventCodeLogDebug, control.EventCodeLogInfo:
   174  				t.logger.Trace(event.Raw)
   175  			case control.EventCodeLogNotice:
   176  				if strings.Contains(event.Raw, "disablenetwork") || strings.Contains(event.Raw, "socks listener") {
   177  					t.logger.Trace(event.Raw)
   178  					continue
   179  				}
   180  				t.logger.Info(event.Raw)
   181  			case control.EventCodeLogWarn:
   182  				t.logger.Warn(event.Raw)
   183  			case control.EventCodeLogErr:
   184  				t.logger.Error(event.Raw)
   185  			}
   186  		}
   187  	}
   188  }
   189  
   190  func (t *Tor) Close() error {
   191  	err := common.Close(
   192  		common.PtrOrNil(t.proxy),
   193  		common.PtrOrNil(t.instance),
   194  	)
   195  	if t.events != nil {
   196  		close(t.events)
   197  		t.events = nil
   198  	}
   199  	return err
   200  }
   201  
   202  func (t *Tor) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
   203  	t.logger.InfoContext(ctx, "outbound connection to ", destination)
   204  	return t.socksClient.DialContext(ctx, network, destination)
   205  }
   206  
   207  func (t *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
   208  	return nil, os.ErrInvalid
   209  }
   210  
   211  func (t *Tor) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
   212  	return NewConnection(ctx, t, conn, metadata)
   213  }
   214  
   215  func (t *Tor) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
   216  	return os.ErrInvalid
   217  }