mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-16 12:32:22 +08:00
Add tor outbound
This commit is contained in:
parent
bcefe8716f
commit
e4cece6095
|
@ -16,6 +16,7 @@ const (
|
|||
TypeNaive = "naive"
|
||||
TypeWireGuard = "wireguard"
|
||||
TypeHysteria = "hysteria"
|
||||
TypeTor = "tor"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#### 2022/08/21
|
||||
|
||||
* Add [Tor outbound](/configuration/outbound/tor)
|
||||
|
||||
#### 2022/08/20
|
||||
|
||||
* Attempt to unwrap ip-in-fqdn socksaddr
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
| `trojan` | [Trojan](./trojan) |
|
||||
| `wireguard` | [Wireguard](./wireguard) |
|
||||
| `hysteria` | [Hysteria](./hysteria) |
|
||||
| `tor` | [Tor](./tor) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `selector` | [Selector](./selector) |
|
||||
|
||||
|
|
108
docs/configuration/outbound/tor.md
Normal file
108
docs/configuration/outbound/tor.md
Normal file
|
@ -0,0 +1,108 @@
|
|||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "tor",
|
||||
"tag": "tor-out",
|
||||
|
||||
"executable_path": "/usr/bin/tor",
|
||||
"extra_args": [],
|
||||
"data_directory": "$HOME/.cache/tor",
|
||||
"torrc": {
|
||||
"ClientOnly": 1
|
||||
},
|
||||
|
||||
"detour": "upstream-out",
|
||||
"bind_interface": "en0",
|
||||
"routing_mark": 1234,
|
||||
"reuse_addr": false,
|
||||
"connect_timeout": "5s",
|
||||
"tcp_fast_open": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"fallback_delay": "300ms"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! info ""
|
||||
|
||||
Embedded tor is not included by default, see [Installation](/#Installation).
|
||||
|
||||
### Tor Fields
|
||||
|
||||
#### executable_path
|
||||
|
||||
The path to the Tor executable.
|
||||
|
||||
Embedded Tor will be ignored if set.
|
||||
|
||||
#### extra_args
|
||||
|
||||
List of extra arguments passed to the Tor instance when started.
|
||||
|
||||
#### data_directory
|
||||
|
||||
==Recommended==
|
||||
|
||||
The data directory of Tor.
|
||||
|
||||
Each start will be very slow if not specified.
|
||||
|
||||
#### torrc
|
||||
|
||||
Map of torrc options.
|
||||
|
||||
See [tor(1)](https://linux.die.net/man/1/tor)
|
||||
|
||||
### Dial Fields
|
||||
|
||||
#### detour
|
||||
|
||||
The tag of the upstream outbound.
|
||||
|
||||
Other dial fields will be ignored when enabled.
|
||||
|
||||
#### bind_interface
|
||||
|
||||
The network interface to bind to.
|
||||
|
||||
#### routing_mark
|
||||
|
||||
!!! error ""
|
||||
|
||||
Linux only
|
||||
|
||||
The iptables routing mark.
|
||||
|
||||
#### reuse_addr
|
||||
|
||||
Reuse listener address.
|
||||
|
||||
#### connect_timeout
|
||||
|
||||
Connect timeout, in golang's Duration format.
|
||||
|
||||
A duration string is a possibly signed sequence of
|
||||
decimal numbers, each with optional fraction and a unit suffix,
|
||||
such as "300ms", "-1.5h" or "2h45m".
|
||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the server domain name will be resolved to IP before connecting.
|
||||
|
||||
`dns.strategy` will be used if empty.
|
||||
|
||||
#### fallback_delay
|
||||
|
||||
The length of time to wait before spawning a RFC 6555 Fast Fallback connection.
|
||||
That is, is the amount of time to wait for IPv6 to succeed before assuming
|
||||
that IPv6 is misconfigured and falling back to IPv4 if `prefer_ipv4` is set.
|
||||
If zero, a default delay of 300ms is used.
|
||||
|
||||
Only take effect when `domain_strategy` is `prefer_ipv4` or `prefer_ipv6`.
|
|
@ -25,7 +25,7 @@
|
|||
| Shadowsocks AEAD 2022 outbound | X | X |
|
||||
| Shadowsocks UDP over TCP | X | X |
|
||||
| Multiplex (smux/yamux) | mux.cool | X |
|
||||
| WireGuard/Hysteria outbound | X | X |
|
||||
| Tor/WireGuard/Hysteria outbound | X | X |
|
||||
| Selector outbound and Clash API | X | ✔ |
|
||||
|
||||
#### Sniffing
|
||||
|
|
|
@ -18,14 +18,15 @@ Install with options:
|
|||
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
|
||||
```
|
||||
|
||||
| Build Tag | Description |
|
||||
|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `with_quic` | Build with QUIC support, see [QUIC and HTTP3 dns transports](./configuration/dns/server), [Naive inbound](./configuration/inbound/naive), [Hysteria Inbound](./configuration/inbound/hysteria) and [Hysteria Outbound](./configuration/outbound/hysteria). |
|
||||
| `with_wireguard` | Build with WireGuard support, see [WireGuard outbound](./configuration/outbound/wireguard). |
|
||||
| `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). |
|
||||
| `with_clash_api` | Build with Clash api support, see [Experimental](./configuration/experimental#clash-api-fields). |
|
||||
| `no_gvisor` | Build without gVisor tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
||||
| `with_lwip` (CGO required) | Build with LWIP tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
||||
| Build Tag | Description |
|
||||
|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `with_quic` | Build with QUIC support, see [QUIC and HTTP3 dns transports](./configuration/dns/server), [Naive inbound](./configuration/inbound/naive), [Hysteria Inbound](./configuration/inbound/hysteria) and [Hysteria Outbound](./configuration/outbound/hysteria). |
|
||||
| `with_wireguard` | Build with WireGuard support, see [WireGuard outbound](./configuration/outbound/wireguard). |
|
||||
| `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). |
|
||||
| `with_clash_api` | Build with Clash api support, see [Experimental](./configuration/experimental#clash-api-fields). |
|
||||
| `no_gvisor` | Build without gVisor tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
||||
| `with_embedded_tor` (CGO required) | Build with embedded Tor support, see [Tor outbound](./configuration/outbound/tor). |
|
||||
| `with_lwip` (CGO required) | Build with LWIP tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
||||
|
||||
The binary is built under $GOPATH/bin
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -3,6 +3,8 @@ module github.com/sagernet/sing-box
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
berty.tech/go-libtor v1.0.385
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/database64128/tfo-go v1.1.1
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/fsnotify/fsnotify v1.5.4
|
||||
|
|
12
go.sum
12
go.sum
|
@ -1,8 +1,13 @@
|
|||
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
||||
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
|
||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||
github.com/database64128/tfo-go v1.1.1 h1:jcaCQBkEZZxV1t2wfOwt41WJKzgcNtLV7nGOm+hmZ3w=
|
||||
github.com/database64128/tfo-go v1.1.1/go.mod h1:b1wrRNZr7NKZhWQ8LSTvqo1r2ppLdYXZLIUDCPOgJrI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -135,8 +140,10 @@ go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
|||
go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
|
||||
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||
|
@ -153,7 +160,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
|
@ -163,6 +172,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -174,6 +184,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -187,6 +198,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
|
|
|
@ -67,6 +67,7 @@ nav:
|
|||
- Trojan: configuration/outbound/trojan.md
|
||||
- WireGuard: configuration/outbound/wireguard.md
|
||||
- Hysteria: configuration/outbound/hysteria.md
|
||||
- Tor: configuration/outbound/tor.md
|
||||
- DNS: configuration/outbound/dns.md
|
||||
- Selector: configuration/outbound/selector.md
|
||||
- Route:
|
||||
|
|
|
@ -17,7 +17,8 @@ type _Outbound struct {
|
|||
VMessOptions VMessOutboundOptions `json:"-"`
|
||||
TrojanOptions TrojanOutboundOptions `json:"-"`
|
||||
WireGuardOptions WireGuardOutboundOptions `json:"-"`
|
||||
HysteriaOutbound HysteriaOutboundOptions `json:"-"`
|
||||
HysteriaOptions HysteriaOutboundOptions `json:"-"`
|
||||
TorOptions TorOutboundOptions `json:"-"`
|
||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||
}
|
||||
|
||||
|
@ -43,7 +44,9 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
|||
case C.TypeWireGuard:
|
||||
v = h.WireGuardOptions
|
||||
case C.TypeHysteria:
|
||||
v = h.HysteriaOutbound
|
||||
v = h.HysteriaOptions
|
||||
case C.TypeTor:
|
||||
v = h.TorOptions
|
||||
case C.TypeSelector:
|
||||
v = h.SelectorOptions
|
||||
default:
|
||||
|
@ -76,7 +79,9 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
|||
case C.TypeWireGuard:
|
||||
v = &h.WireGuardOptions
|
||||
case C.TypeHysteria:
|
||||
v = &h.HysteriaOutbound
|
||||
v = &h.HysteriaOptions
|
||||
case C.TypeTor:
|
||||
v = &h.TorOptions
|
||||
case C.TypeSelector:
|
||||
v = &h.SelectorOptions
|
||||
default:
|
||||
|
|
9
option/tor.go
Normal file
9
option/tor.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package option
|
||||
|
||||
type TorOutboundOptions struct {
|
||||
OutboundDialerOptions
|
||||
ExecutablePath string `json:"executable_path,omitempty"`
|
||||
ExtraArgs []string `json:"extra_args,omitempty"`
|
||||
DataDirectory string `json:"data_directory,omitempty"`
|
||||
Options map[string]string `json:"torrc,omitempty"`
|
||||
}
|
|
@ -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
131
outbound/proxy.go
Normal 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
203
outbound/tor.go
Normal 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
15
outbound/tor_embed.go
Normal 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
9
outbound/tor_external.go
Normal 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{}
|
||||
}
|
|
@ -267,7 +267,11 @@ func (r *DefaultRule) Outbound() string {
|
|||
}
|
||||
|
||||
func (r *DefaultRule) String() string {
|
||||
return strings.Join(F.MapToString(r.allItems), " ")
|
||||
if !r.invert {
|
||||
return strings.Join(F.MapToString(r.allItems), " ")
|
||||
} else {
|
||||
return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")"
|
||||
}
|
||||
}
|
||||
|
||||
var _ adapter.Rule = (*LogicalRule)(nil)
|
||||
|
|
|
@ -55,7 +55,7 @@ func TestHysteriaSelf(t *testing.T) {
|
|||
{
|
||||
Type: C.TypeHysteria,
|
||||
Tag: "hy-out",
|
||||
HysteriaOutbound: option.HysteriaOutboundOptions{
|
||||
HysteriaOptions: option.HysteriaOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
|
@ -159,7 +159,7 @@ func TestHysteriaOutbound(t *testing.T) {
|
|||
Outbounds: []option.Outbound{
|
||||
{
|
||||
Type: C.TypeHysteria,
|
||||
HysteriaOutbound: option.HysteriaOutboundOptions{
|
||||
HysteriaOptions: option.HysteriaOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
|
|
Loading…
Reference in New Issue
Block a user