Add tor outbound

This commit is contained in:
世界
2022-08-21 00:59:49 +08:00
parent bcefe8716f
commit e4cece6095
18 changed files with 524 additions and 16 deletions

View File

@@ -34,7 +34,9 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
case C.TypeWireGuard:
return NewWireGuard(ctx, router, logger, options.Tag, options.WireGuardOptions)
case C.TypeHysteria:
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOutbound)
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
case C.TypeTor:
return NewTor(ctx, router, logger, options.Tag, options.TorOptions)
case C.TypeSelector:
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
default:

131
outbound/proxy.go Normal file
View File

@@ -0,0 +1,131 @@
package outbound
import (
std_bufio "bufio"
"context"
"encoding/hex"
"math/rand"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
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/common/rw"
"github.com/sagernet/sing/protocol/http"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/protocol/socks/socks4"
"github.com/sagernet/sing/protocol/socks/socks5"
)
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"))
}
}()
}
}
func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error {
headerType, err := rw.ReadByte(conn)
if err != nil {
return err
}
switch headerType {
case socks4.Version, socks5.Version:
return socks.HandleConnection0(ctx, conn, headerType, l.authenticator, l, M.Metadata{})
}
reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType})))
return http.HandleConnection(ctx, conn, reader, 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 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 NewPacketConnection(ctx, l.dialer, conn, metadata)
}

203
outbound/tor.go Normal file
View File

@@ -0,0 +1,203 @@
package outbound
import (
"context"
"net"
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing-box/adapter"
"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"
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"
)
var _ adapter.Outbound = (*Tor)(nil)
type Tor struct {
myOutboundAdapter
ctx context.Context
proxy *ProxyListener
startConf *tor.StartConf
options map[string]string
events chan control.Event
instance *tor.Tor
socksClient *socks.Client
}
func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (*Tor, error) {
startConf := newConfig()
startConf.DataDir = os.ExpandEnv(options.DataDirectory)
startConf.TempDataDirBase = os.TempDir()
if options.ExecutablePath != "" {
startConf.ExePath = options.ExecutablePath
startConf.ExtraArgs = options.ExtraArgs
startConf.ProcessCreator = nil
startConf.UseEmbeddedControlConn = false
}
if startConf.DataDir != "" {
torrcFile := filepath.Join(startConf.DataDir, "torrc")
if !rw.FileExists(torrcFile) {
err := rw.WriteFile(torrcFile, []byte(""))
if err != nil {
return nil, err
}
}
startConf.TorrcFile = torrcFile
}
return &Tor{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeTor,
network: []string{N.NetworkTCP},
router: router,
logger: logger,
tag: tag,
},
ctx: ctx,
proxy: NewProxyListener(ctx, logger, dialer.NewOutbound(router, options.OutboundDialerOptions)),
startConf: &startConf,
options: options.Options,
}, nil
}
func (t *Tor) 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 *Tor) 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 *Tor) 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 *Tor) 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 *Tor) 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 *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}
func (t *Tor) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewConnection(ctx, t, conn, metadata)
}
func (t *Tor) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return os.ErrInvalid
}

15
outbound/tor_embed.go Normal file
View File

@@ -0,0 +1,15 @@
//go:build with_embedded_tor
package outbound
import (
"berty.tech/go-libtor"
"github.com/cretz/bine/tor"
)
func newConfig() tor.StartConf {
return tor.StartConf{
ProcessCreator: libtor.Creator,
UseEmbeddedControlConn: true,
}
}

9
outbound/tor_external.go Normal file
View File

@@ -0,0 +1,9 @@
//go:build !with_embedded_tor
package outbound
import "github.com/cretz/bine/tor"
func newConfig() tor.StartConf {
return tor.StartConf{}
}