Add tor outbound

This commit is contained in:
世界 2022-08-21 00:59:49 +08:00
parent bcefe8716f
commit e4cece6095
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
18 changed files with 524 additions and 16 deletions

View file

@ -16,6 +16,7 @@ const (
TypeNaive = "naive" TypeNaive = "naive"
TypeWireGuard = "wireguard" TypeWireGuard = "wireguard"
TypeHysteria = "hysteria" TypeHysteria = "hysteria"
TypeTor = "tor"
) )
const ( const (

View file

@ -1,3 +1,7 @@
#### 2022/08/21
* Add [Tor outbound](/configuration/outbound/tor)
#### 2022/08/20 #### 2022/08/20
* Attempt to unwrap ip-in-fqdn socksaddr * Attempt to unwrap ip-in-fqdn socksaddr

View file

@ -24,6 +24,7 @@
| `trojan` | [Trojan](./trojan) | | `trojan` | [Trojan](./trojan) |
| `wireguard` | [Wireguard](./wireguard) | | `wireguard` | [Wireguard](./wireguard) |
| `hysteria` | [Hysteria](./hysteria) | | `hysteria` | [Hysteria](./hysteria) |
| `tor` | [Tor](./tor) |
| `dns` | [DNS](./dns) | | `dns` | [DNS](./dns) |
| `selector` | [Selector](./selector) | | `selector` | [Selector](./selector) |

View 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`.

View file

@ -25,7 +25,7 @@
| Shadowsocks AEAD 2022 outbound | X | X | | Shadowsocks AEAD 2022 outbound | X | X |
| Shadowsocks UDP over TCP | X | X | | Shadowsocks UDP over TCP | X | X |
| Multiplex (smux/yamux) | mux.cool | X | | Multiplex (smux/yamux) | mux.cool | X |
| WireGuard/Hysteria outbound | X | X | | Tor/WireGuard/Hysteria outbound | X | X |
| Selector outbound and Clash API | X | ✔ | | Selector outbound and Clash API | X | ✔ |
#### Sniffing #### Sniffing

View file

@ -18,14 +18,15 @@ Install with options:
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
``` ```
| Build Tag | Description | | 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_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_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_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). | | `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). | | `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). | | `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 The binary is built under $GOPATH/bin

2
go.mod
View file

@ -3,6 +3,8 @@ module github.com/sagernet/sing-box
go 1.18 go 1.18
require ( 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/database64128/tfo-go v1.1.1
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/fsnotify/fsnotify v1.5.4 github.com/fsnotify/fsnotify v1.5.4

12
go.sum
View file

@ -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 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 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 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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/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 h1:jcaCQBkEZZxV1t2wfOwt41WJKzgcNtLV7nGOm+hmZ3w=
github.com/database64128/tfo-go v1.1.1/go.mod h1:b1wrRNZr7NKZhWQ8LSTvqo1r2ppLdYXZLIUDCPOgJrI= 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= 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 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U= 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-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-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-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 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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= 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-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-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-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-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-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 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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/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-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-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-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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/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-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-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-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-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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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.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.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.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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=

View file

@ -67,6 +67,7 @@ nav:
- Trojan: configuration/outbound/trojan.md - Trojan: configuration/outbound/trojan.md
- WireGuard: configuration/outbound/wireguard.md - WireGuard: configuration/outbound/wireguard.md
- Hysteria: configuration/outbound/hysteria.md - Hysteria: configuration/outbound/hysteria.md
- Tor: configuration/outbound/tor.md
- DNS: configuration/outbound/dns.md - DNS: configuration/outbound/dns.md
- Selector: configuration/outbound/selector.md - Selector: configuration/outbound/selector.md
- Route: - Route:

View file

@ -17,7 +17,8 @@ type _Outbound struct {
VMessOptions VMessOutboundOptions `json:"-"` VMessOptions VMessOutboundOptions `json:"-"`
TrojanOptions TrojanOutboundOptions `json:"-"` TrojanOptions TrojanOutboundOptions `json:"-"`
WireGuardOptions WireGuardOutboundOptions `json:"-"` WireGuardOptions WireGuardOutboundOptions `json:"-"`
HysteriaOutbound HysteriaOutboundOptions `json:"-"` HysteriaOptions HysteriaOutboundOptions `json:"-"`
TorOptions TorOutboundOptions `json:"-"`
SelectorOptions SelectorOutboundOptions `json:"-"` SelectorOptions SelectorOutboundOptions `json:"-"`
} }
@ -43,7 +44,9 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
case C.TypeWireGuard: case C.TypeWireGuard:
v = h.WireGuardOptions v = h.WireGuardOptions
case C.TypeHysteria: case C.TypeHysteria:
v = h.HysteriaOutbound v = h.HysteriaOptions
case C.TypeTor:
v = h.TorOptions
case C.TypeSelector: case C.TypeSelector:
v = h.SelectorOptions v = h.SelectorOptions
default: default:
@ -76,7 +79,9 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
case C.TypeWireGuard: case C.TypeWireGuard:
v = &h.WireGuardOptions v = &h.WireGuardOptions
case C.TypeHysteria: case C.TypeHysteria:
v = &h.HysteriaOutbound v = &h.HysteriaOptions
case C.TypeTor:
v = &h.TorOptions
case C.TypeSelector: case C.TypeSelector:
v = &h.SelectorOptions v = &h.SelectorOptions
default: default:

9
option/tor.go Normal file
View 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"`
}

View file

@ -34,7 +34,9 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
case C.TypeWireGuard: case C.TypeWireGuard:
return NewWireGuard(ctx, router, logger, options.Tag, options.WireGuardOptions) return NewWireGuard(ctx, router, logger, options.Tag, options.WireGuardOptions)
case C.TypeHysteria: 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: case C.TypeSelector:
return NewSelector(router, logger, options.Tag, options.SelectorOptions) return NewSelector(router, logger, options.Tag, options.SelectorOptions)
default: 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{}
}

View file

@ -267,7 +267,11 @@ func (r *DefaultRule) Outbound() string {
} }
func (r *DefaultRule) String() 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) var _ adapter.Rule = (*LogicalRule)(nil)

View file

@ -55,7 +55,7 @@ func TestHysteriaSelf(t *testing.T) {
{ {
Type: C.TypeHysteria, Type: C.TypeHysteria,
Tag: "hy-out", Tag: "hy-out",
HysteriaOutbound: option.HysteriaOutboundOptions{ HysteriaOptions: option.HysteriaOutboundOptions{
ServerOptions: option.ServerOptions{ ServerOptions: option.ServerOptions{
Server: "127.0.0.1", Server: "127.0.0.1",
ServerPort: serverPort, ServerPort: serverPort,
@ -159,7 +159,7 @@ func TestHysteriaOutbound(t *testing.T) {
Outbounds: []option.Outbound{ Outbounds: []option.Outbound{
{ {
Type: C.TypeHysteria, Type: C.TypeHysteria,
HysteriaOutbound: option.HysteriaOutboundOptions{ HysteriaOptions: option.HysteriaOutboundOptions{
ServerOptions: option.ServerOptions{ ServerOptions: option.ServerOptions{
Server: "127.0.0.1", Server: "127.0.0.1",
ServerPort: serverPort, ServerPort: serverPort,