github.com/sagernet/sing-box@v1.9.0-rc.20/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/sagernet/sing-box/adapter" 11 "github.com/sagernet/sing-box/common/dialer" 12 C "github.com/sagernet/sing-box/constant" 13 "github.com/sagernet/sing-box/log" 14 "github.com/sagernet/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 }