mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-07 17:58:49 +08:00
Compare commits
7 Commits
dev-next
...
v1.10.0-al
Author | SHA1 | Date | |
---|---|---|---|
![]() |
28880453a2 | ||
![]() |
6875a33c28 | ||
![]() |
d6d6f72fea | ||
![]() |
f3bfd1562b | ||
![]() |
4b0264142c | ||
![]() |
dd60035502 | ||
![]() |
5735227174 |
113
common/sniff/bittorrent.go
Normal file
113
common/sniff/bittorrent.go
Normal file
@ -0,0 +1,113 @@
|
||||
package sniff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
const (
|
||||
trackerConnectFlag = iota
|
||||
trackerAnnounceFlag
|
||||
trackerScrapeFlag
|
||||
|
||||
trackerProtocolID = 0x41727101980
|
||||
|
||||
trackerConnectMinSize = 16
|
||||
trackerAnnounceMinSize = 20
|
||||
trackerScrapeMinSize = 8
|
||||
)
|
||||
|
||||
// BitTorrent detects if the stream is a BitTorrent connection.
|
||||
// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html
|
||||
func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||
var first byte
|
||||
err := binary.Read(reader, binary.BigEndian, &first)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if first != 19 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
var protocol [19]byte
|
||||
_, err = reader.Read(protocol[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(protocol[:]) != "BitTorrent protocol" {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UTP detects if the packet is a uTP connection packet.
|
||||
// For the uTP protocol specification, see
|
||||
// 1. https://www.bittorrent.org/beps/bep_0029.html
|
||||
// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112
|
||||
func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||
// A valid uTP packet must be at least 20 bytes long.
|
||||
if len(packet) < 20 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
version := packet[0] & 0x0F
|
||||
ty := packet[0] >> 4
|
||||
if version != 1 || ty > 4 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
// Validate the extensions
|
||||
extension := packet[1]
|
||||
reader := bytes.NewReader(packet[20:])
|
||||
for extension != 0 {
|
||||
err := binary.Read(reader, binary.BigEndian, &extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var length byte
|
||||
err = binary.Read(reader, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = reader.Seek(int64(length), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UDPTracker detects if the packet is a UDP Tracker Protocol packet.
|
||||
// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html
|
||||
func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||
switch {
|
||||
case len(packet) >= trackerConnectMinSize &&
|
||||
binary.BigEndian.Uint64(packet[:8]) == trackerProtocolID &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerConnectFlag:
|
||||
fallthrough
|
||||
case len(packet) >= trackerAnnounceMinSize &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerAnnounceFlag:
|
||||
fallthrough
|
||||
case len(packet) >= trackerScrapeMinSize &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerScrapeFlag:
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
default:
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
}
|
81
common/sniff/bittorrent_test.go
Normal file
81
common/sniff/bittorrent_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSniffBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
packets := []string{
|
||||
"13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1",
|
||||
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638",
|
||||
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130",
|
||||
}
|
||||
|
||||
for _, pkt := range packets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffUTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
packets := []string{
|
||||
"010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8",
|
||||
"01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb",
|
||||
"21001ecb6817f2805d044fd700100000dbd03029",
|
||||
"410277ef0b1fb1f60000000000040000c233000000080000000000000000",
|
||||
}
|
||||
|
||||
for _, pkt := range packets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, err := sniff.UTP(context.TODO(), pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffUDPTracker(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
connectPackets := []string{
|
||||
// connect packets
|
||||
"00000417271019800000000078e90560",
|
||||
"00000417271019800000000022c5d64d",
|
||||
"000004172710198000000000b3863541",
|
||||
|
||||
// announce packets
|
||||
"3d7592ead4b8c9e300000001b871a3820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
"3d7592ead4b8c9e30000000188deed1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
"3d7592ead4b8c9e300000001ceb948ad0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a3362cdb7020ff920e5aa642c3d4066950dd1f01f4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
|
||||
// scrape packets
|
||||
"3d7592ead4b8c9e300000002d2f4bba5a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
|
||||
"3d7592ead4b8c9e300000002441243292aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
|
||||
"3d7592ead4b8c9e300000002b2aa461b1ad1fa9661cf3fe45fb2504ad52ec6c67758e294",
|
||||
}
|
||||
|
||||
for _, pkt := range connectPackets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, err := sniff.UDPTracker(context.TODO(), pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
ProtocolTLS = "tls"
|
||||
ProtocolHTTP = "http"
|
||||
ProtocolQUIC = "quic"
|
||||
ProtocolDNS = "dns"
|
||||
ProtocolSTUN = "stun"
|
||||
ProtocolTLS = "tls"
|
||||
ProtocolHTTP = "http"
|
||||
ProtocolQUIC = "quic"
|
||||
ProtocolDNS = "dns"
|
||||
ProtocolSTUN = "stun"
|
||||
ProtocolBitTorrent = "bittorrent"
|
||||
)
|
||||
|
@ -2,6 +2,51 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.10.0-alpha.4
|
||||
|
||||
* Fix auto redirect **1**
|
||||
* Improve auto route on linux **2**
|
||||
|
||||
**1**:
|
||||
|
||||
Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers.
|
||||
|
||||
**2**:
|
||||
|
||||
Tun inbounds with `auto_route` and `strict_route` now works as expected on routers and servers,
|
||||
but the usages of [exclude_interface](/configuration/inbound/tun/#exclude_interface) need to be updated.
|
||||
|
||||
#### 1.10.0-alpha.2
|
||||
|
||||
* Move auto redirect to Tun **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Linux support are added.
|
||||
|
||||
See [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
#### 1.10.0-alpha.1
|
||||
|
||||
* Add tailing comma support in JSON configuration
|
||||
* Add simple auto redirect for Android **1**
|
||||
* Add BitTorrent sniffer **2**
|
||||
|
||||
**1**:
|
||||
|
||||
It allows you to use redirect inbound in the sing-box Android client
|
||||
and automatically configures IPv4 TCP redirection via su.
|
||||
|
||||
This may alleviate the symptoms of some OCD patients who think that
|
||||
redirect can effectively save power compared to the system HTTP Proxy.
|
||||
|
||||
See [Redirect](/configuration/inbound/redirect/).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Protocol Sniff](/configuration/route/sniff/).
|
||||
|
||||
### 1.9.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
@ -2,6 +2,10 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||
@ -29,6 +33,7 @@ icon: material/new-box
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"auto_redirect": false,
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
@ -145,9 +150,10 @@ Enforce strict routing rules when `auto_route` is enabled:
|
||||
*In Linux*:
|
||||
|
||||
* Let unsupported network unreachable
|
||||
* Make ICMP traffic route to tun instead of upstream interfaces
|
||||
* Route all connections to tun
|
||||
|
||||
It prevents address leaks and makes DNS hijacking work on Android.
|
||||
It prevents IP address leaks and makes DNS hijacking work on Android.
|
||||
|
||||
*In Windows*:
|
||||
|
||||
@ -156,6 +162,34 @@ It prevents address leaks and makes DNS hijacking work on Android.
|
||||
|
||||
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
||||
|
||||
#### auto_redirect
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux.
|
||||
|
||||
Automatically configure iptables to redirect TCP connections.
|
||||
|
||||
*In Android*:
|
||||
|
||||
* Only IPv4 is supported
|
||||
* Only local connections are forwarded
|
||||
|
||||
To share your VPN connection over hotspot or repeater, use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||
|
||||
*In Linux*:
|
||||
|
||||
* iptables is required (optional ip6tables)
|
||||
* `iptables_nat` module is required
|
||||
|
||||
For OpenWrt 23.05, the following additional packages are required:
|
||||
|
||||
```bash
|
||||
iptables-nft iptables-mod-nat-extra ip6tables-nft
|
||||
```
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
@ -214,6 +248,10 @@ Conflict with `exclude_interface`.
|
||||
|
||||
#### exclude_interface
|
||||
|
||||
!!! warning ""
|
||||
|
||||
When `strict_route` enabled, return traffic to excluded interfaces will not be automatically excluded, so add them as well (example: `br-lan` and `pppoe-wan`).
|
||||
|
||||
Exclude interfaces in route.
|
||||
|
||||
Conflict with `include_interface`.
|
||||
|
@ -2,6 +2,10 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||
@ -29,6 +33,7 @@ icon: material/new-box
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"auto_redirect": false,
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
@ -145,9 +150,10 @@ tun 接口的 IPv6 前缀。
|
||||
*在 Linux 中*:
|
||||
|
||||
* 让不支持的网络无法到达
|
||||
* 使 ICMP 流量路由到 tun 而不是上游接口
|
||||
* 将所有连接路由到 tun
|
||||
|
||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||
|
||||
*在 Windows 中*:
|
||||
|
||||
@ -157,6 +163,34 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||
|
||||
#### auto_redirect
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
自动配置 iptables 以重定向 TCP 连接。
|
||||
|
||||
*在 Android 中*:
|
||||
|
||||
* 仅支持 IPv4
|
||||
* 仅转发本地连接
|
||||
|
||||
要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||
|
||||
*在 Linux 中*:
|
||||
|
||||
* 需要 iptables(可选 ip6tables)
|
||||
* 需要 `iptables_nat` 模块
|
||||
|
||||
对于 OpenWrt 23.05,需要以下附加软件包:
|
||||
|
||||
```bash
|
||||
iptables-nft iptables-mod-nat-extra ip6tables-nft
|
||||
```
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
@ -211,6 +245,10 @@ TCP/IP 栈。
|
||||
|
||||
#### exclude_interface
|
||||
|
||||
!!! warning ""
|
||||
|
||||
当 `strict_route` 启用,到被排除接口的回程流量将不会被自动排除,因此也要添加它们(例:`br-lan` 与 `pppoe-wan`)。
|
||||
|
||||
排除路由的接口。
|
||||
|
||||
与 `include_interface` 冲突。
|
||||
|
@ -2,10 +2,11 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
||||
|
||||
#### Supported Protocols
|
||||
|
||||
| Network | Protocol | Domain Name |
|
||||
|:-------:|:--------:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| Network | Protocol | Domain Name |
|
||||
|:-------:|:-----------:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| TCP/UDP | BitTorrent | / |
|
@ -2,10 +2,11 @@
|
||||
|
||||
#### 支持的协议
|
||||
|
||||
| 网络 | 协议 | 域名 |
|
||||
|:-------:|:----:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| 网络 | 协议 | 域名 |
|
||||
|:-------:|:-----------:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| TCP/UDP | BitTorrent | / |
|
@ -4,10 +4,6 @@ icon: material/arrange-bring-forward
|
||||
|
||||
## 1.9.0
|
||||
|
||||
!!! warning "Unstable"
|
||||
|
||||
This version is still under development, and the following migration guide may be changed in the future.
|
||||
|
||||
### `domain_suffix` behavior update
|
||||
|
||||
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
||||
|
@ -4,10 +4,6 @@ icon: material/arrange-bring-forward
|
||||
|
||||
## 1.9.0
|
||||
|
||||
!!! warning "不稳定的"
|
||||
|
||||
该版本仍在开发中,迁移指南可能将在未来更改。
|
||||
|
||||
### `domain_suffix` 行为更新
|
||||
|
||||
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
||||
|
6
go.mod
6
go.mod
@ -26,14 +26,14 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
||||
github.com/sagernet/quic-go v0.43.1-beta.1
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.4.0-beta.20
|
||||
github.com/sagernet/sing v0.5.0-alpha.7
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.18
|
||||
github.com/sagernet/sing-mux v0.2.0
|
||||
github.com/sagernet/sing-quic v0.2.0-beta.5
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/sing-tun v0.3.0-beta.6
|
||||
github.com/sagernet/sing-tun v0.4.0-beta.4
|
||||
github.com/sagernet/sing-vmess v0.1.8
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
|
||||
@ -45,6 +45,7 @@ require (
|
||||
go.uber.org/zap v1.27.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
@ -84,7 +85,6 @@ require (
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@ -106,8 +106,8 @@ github.com/sagernet/quic-go v0.43.1-beta.1/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhY
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.4.0-beta.20 h1:8rEepj4LMcR0Wd389fJIziv/jr3MBtX5qXBHsfxJ+dY=
|
||||
github.com/sagernet/sing v0.4.0-beta.20/go.mod h1:PFQKbElc2Pke7faBLv8oEba5ehtKO21Ho+TkYemTI3Y=
|
||||
github.com/sagernet/sing v0.5.0-alpha.7 h1:yxjiH0vQAotu87JNJ9B0BGO0OJqsFjug84xcYdwyDm4=
|
||||
github.com/sagernet/sing v0.5.0-alpha.7/go.mod h1:Xh4KO9nGdvm4K/LVg9Xn9jSxJdqe9KcXbAzNC1S2qfw=
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIpIHFSOP0za8=
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24=
|
||||
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
||||
@ -120,8 +120,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/sing-tun v0.3.0-beta.6 h1:L11kMrM7UfUW0pzQiU66Fffh4o86KZc1SFGbkYi8Ma8=
|
||||
github.com/sagernet/sing-tun v0.3.0-beta.6/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
|
||||
github.com/sagernet/sing-tun v0.4.0-beta.4 h1:94OB9fQupn0qBPvlDzKRAq1lSIPHAvVBaw69/jEZfgQ=
|
||||
github.com/sagernet/sing-tun v0.4.0-beta.4/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
|
||||
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
|
||||
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
|
@ -37,6 +37,7 @@ type Tun struct {
|
||||
tunStack tun.Stack
|
||||
platformInterface platform.Interface
|
||||
platformOptions option.TunPlatformOptions
|
||||
autoRedirect *tunAutoRedirect
|
||||
}
|
||||
|
||||
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
||||
@ -50,9 +51,9 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
} else {
|
||||
udpTimeout = C.UDPTimeout
|
||||
}
|
||||
var err error
|
||||
includeUID := uidToRange(options.IncludeUID)
|
||||
if len(options.IncludeUIDRange) > 0 {
|
||||
var err error
|
||||
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse include_uid_range")
|
||||
@ -60,13 +61,13 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
}
|
||||
excludeUID := uidToRange(options.ExcludeUID)
|
||||
if len(options.ExcludeUIDRange) > 0 {
|
||||
var err error
|
||||
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse exclude_uid_range")
|
||||
}
|
||||
}
|
||||
return &Tun{
|
||||
|
||||
inbound := &Tun{
|
||||
tag: tag,
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
@ -99,7 +100,17 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
stack: options.Stack,
|
||||
platformInterface: platformInterface,
|
||||
platformOptions: common.PtrValueOrDefault(options.Platform),
|
||||
}, nil
|
||||
}
|
||||
if options.AutoRedirect {
|
||||
if !options.AutoRoute {
|
||||
return nil, E.New("`auto_route` is required by `auto_redirect`")
|
||||
}
|
||||
inbound.autoRedirect, err = newAutoRedirect(inbound)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize auto redirect")
|
||||
}
|
||||
}
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] {
|
||||
@ -195,6 +206,14 @@ func (t *Tun) Start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.autoRedirect != nil {
|
||||
monitor.Start("initiating auto redirect")
|
||||
err = t.autoRedirect.Start(t.tunOptions.Name)
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "auto redirect")
|
||||
}
|
||||
}
|
||||
t.logger.Info("started at ", t.tunOptions.Name)
|
||||
return nil
|
||||
}
|
||||
@ -203,6 +222,7 @@ func (t *Tun) Close() error {
|
||||
return common.Close(
|
||||
t.tunStack,
|
||||
t.tunIf,
|
||||
common.PtrOrNil(t.autoRedirect),
|
||||
)
|
||||
}
|
||||
|
||||
|
406
inbound/tun_auto_redirect.go
Normal file
406
inbound/tun_auto_redirect.go
Normal file
@ -0,0 +1,406 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/redir"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
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/x/list"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
tableNameOutput = "sing-box-output"
|
||||
tableNameForward = "sing-box-forward"
|
||||
tableNamePreRouteing = "sing-box-prerouting"
|
||||
)
|
||||
|
||||
type tunAutoRedirect struct {
|
||||
myInboundAdapter
|
||||
tunOptions *tun.Options
|
||||
interfaceFinder control.InterfaceFinder
|
||||
networkMonitor tun.NetworkUpdateMonitor
|
||||
networkCallback *list.Element[tun.NetworkUpdateCallback]
|
||||
enableIPv4 bool
|
||||
enableIPv6 bool
|
||||
localAddresses4 []netip.Prefix
|
||||
localAddresses6 []netip.Prefix
|
||||
iptablesPath string
|
||||
ip6tablesPath string
|
||||
androidSu bool
|
||||
suPath string
|
||||
}
|
||||
|
||||
func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
|
||||
if !C.IsLinux {
|
||||
return nil, E.New("only supported on linux")
|
||||
}
|
||||
server := &tunAutoRedirect{
|
||||
myInboundAdapter: myInboundAdapter{
|
||||
protocol: C.TypeRedirect,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: t.ctx,
|
||||
router: t.router,
|
||||
logger: t.logger,
|
||||
tag: t.tag,
|
||||
listenOptions: option.ListenOptions{
|
||||
InboundOptions: t.inboundOptions,
|
||||
},
|
||||
},
|
||||
tunOptions: &t.tunOptions,
|
||||
interfaceFinder: t.router.InterfaceFinder(),
|
||||
networkMonitor: t.router.NetworkMonitor(),
|
||||
}
|
||||
server.connHandler = server
|
||||
if len(t.tunOptions.Inet4Address) > 0 {
|
||||
server.enableIPv4 = true
|
||||
if C.IsAndroid {
|
||||
server.iptablesPath = "/system/bin/iptables"
|
||||
userId := os.Getuid()
|
||||
if userId != 0 {
|
||||
var (
|
||||
suPath string
|
||||
err error
|
||||
)
|
||||
if t.platformInterface != nil {
|
||||
suPath, err = exec.LookPath("/bin/su")
|
||||
} else {
|
||||
suPath, err = exec.LookPath("su")
|
||||
}
|
||||
if err == nil {
|
||||
server.androidSu = true
|
||||
server.suPath = suPath
|
||||
} else {
|
||||
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iptablesPath, err := exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "iptables is required")
|
||||
}
|
||||
server.iptablesPath = iptablesPath
|
||||
}
|
||||
}
|
||||
if !C.IsAndroid && len(t.tunOptions.Inet6Address) > 0 {
|
||||
err := server.initializeIP6Tables()
|
||||
if err != nil {
|
||||
t.logger.Debug("device has no ip6tables nat support: ", err)
|
||||
}
|
||||
}
|
||||
var listenAddr netip.Addr
|
||||
if C.IsAndroid {
|
||||
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||
} else if server.enableIPv6 {
|
||||
listenAddr = netip.IPv6Unspecified()
|
||||
} else {
|
||||
listenAddr = netip.IPv4Unspecified()
|
||||
}
|
||||
server.listenOptions.Listen = option.NewListenAddress(listenAddr)
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) initializeIP6Tables() error {
|
||||
ip6tablesPath, err := exec.LookPath("ip6tables")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
/*output, err := exec.Command(ip6tablesPath, "-t nat -L", tableNameOutput).CombinedOutput()
|
||||
switch exitErr := err.(type) {
|
||||
case nil:
|
||||
case *exec.ExitError:
|
||||
if exitErr.ExitCode() != 1 {
|
||||
return E.Extend(err, string(output))
|
||||
}
|
||||
default:
|
||||
return err
|
||||
}*/
|
||||
t.ip6tablesPath = ip6tablesPath
|
||||
t.enableIPv6 = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) Start(tunName string) error {
|
||||
err := t.myInboundAdapter.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start redirect server")
|
||||
}
|
||||
if t.enableIPv4 {
|
||||
t.cleanupIPTables(t.iptablesPath)
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
t.cleanupIPTables(t.ip6tablesPath)
|
||||
}
|
||||
err = t.updateInterfaces(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.enableIPv4 {
|
||||
err = t.setupIPTables(t.iptablesPath, tunName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
err = t.setupIPTables(t.ip6tablesPath, tunName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
t.networkCallback = t.networkMonitor.RegisterCallback(func() {
|
||||
rErr := t.updateInterfaces(true)
|
||||
if rErr != nil {
|
||||
t.logger.Error("recreate prerouting rules: ", rErr)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) updateInterfaces(recreate bool) error {
|
||||
addresses := common.Filter(common.FlatMap(common.Filter(t.interfaceFinder.Interfaces(), func(it control.Interface) bool {
|
||||
return it.Name != t.tunOptions.Name
|
||||
}), func(it control.Interface) []netip.Prefix {
|
||||
return it.Addresses
|
||||
}), func(it netip.Prefix) bool {
|
||||
address := it.Addr()
|
||||
return !(address.IsLoopback() || address.IsLinkLocalUnicast())
|
||||
})
|
||||
oldLocalAddresses4 := t.localAddresses4
|
||||
oldLocalAddresses6 := t.localAddresses6
|
||||
localAddresses4 := common.Filter(addresses, func(it netip.Prefix) bool { return it.Addr().Is4() })
|
||||
localAddresses6 := common.Filter(addresses, func(it netip.Prefix) bool { return it.Addr().Is6() })
|
||||
t.localAddresses4 = localAddresses4
|
||||
t.localAddresses6 = localAddresses6
|
||||
if !recreate || t.androidSu {
|
||||
return nil
|
||||
}
|
||||
if t.enableIPv4 {
|
||||
if !slices.Equal(localAddresses4, oldLocalAddresses4) {
|
||||
err := t.setupIPTablesPreRouting(t.iptablesPath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
if !slices.Equal(localAddresses6, oldLocalAddresses6) {
|
||||
err := t.setupIPTablesPreRouting(t.ip6tablesPath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) Close() error {
|
||||
t.networkMonitor.UnregisterCallback(t.networkCallback)
|
||||
if t.enableIPv4 {
|
||||
t.cleanupIPTables(t.iptablesPath)
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
t.cleanupIPTables(t.ip6tablesPath)
|
||||
}
|
||||
return t.myInboundAdapter.Close()
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
destination, err := redir.GetOriginalDestination(conn)
|
||||
if err != nil {
|
||||
return E.Cause(err, "get redirect destination")
|
||||
}
|
||||
metadata.Destination = M.SocksaddrFromNetIP(destination)
|
||||
return t.newConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupIPTables(iptablesPath string, tunName string) error {
|
||||
// OUTPUT
|
||||
err := t.runShell(iptablesPath, "-t nat -N", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNameOutput,
|
||||
"-p tcp -o", tunName,
|
||||
"-j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -I OUTPUT -j", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !t.androidSu {
|
||||
// FORWARD
|
||||
err = t.runShell(iptablesPath, "-N", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-i", tunName, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-o", tunName, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-I FORWARD -j", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// PREROUTING
|
||||
err = t.setupIPTablesPreRouting(iptablesPath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupIPTablesPreRouting(iptablesPath string, recreate bool) error {
|
||||
var err error
|
||||
if !recreate {
|
||||
err = t.runShell(iptablesPath, "-t nat -N", tableNamePreRouteing)
|
||||
} else {
|
||||
err = t.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
routeAddress []netip.Prefix
|
||||
routeExcludeAddress []netip.Prefix
|
||||
)
|
||||
if t.iptablesPath == iptablesPath {
|
||||
routeAddress = t.tunOptions.Inet4RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet4RouteExcludeAddress
|
||||
} else {
|
||||
routeAddress = t.tunOptions.Inet6RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet6RouteExcludeAddress
|
||||
}
|
||||
if len(routeAddress) > 0 && (len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0) {
|
||||
return E.New("`*_route_address` is conflict with `include_interface` or `include_uid`")
|
||||
}
|
||||
if len(routeExcludeAddress) > 0 {
|
||||
for _, address := range routeExcludeAddress {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(t.tunOptions.ExcludeInterface) > 0 {
|
||||
for _, name := range t.tunOptions.ExcludeInterface {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(t.tunOptions.ExcludeUID) > 0 {
|
||||
for _, uid := range t.tunOptions.ExcludeUID {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
var addresses []netip.Prefix
|
||||
if t.iptablesPath == iptablesPath {
|
||||
addresses = t.localAddresses4
|
||||
} else {
|
||||
addresses = t.localAddresses6
|
||||
}
|
||||
for _, address := range addresses {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, "-d", address.String(), "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range t.tunOptions.IncludeInterface {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range t.tunOptions.IncludeUID {
|
||||
for i := uidRange.Start; i <= uidRange.End; i++ {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", i, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -I PREROUTING -j", tableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) cleanupIPTables(iptablesPath string) {
|
||||
_ = t.runShell(iptablesPath, "-t nat -D OUTPUT -j", tableNameOutput)
|
||||
_ = t.runShell(iptablesPath, "-t nat -F", tableNameOutput)
|
||||
_ = t.runShell(iptablesPath, "-t nat -X", tableNameOutput)
|
||||
if !t.androidSu {
|
||||
_ = t.runShell(iptablesPath, "-D FORWARD -j", tableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-F", tableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-X", tableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-t nat -D PREROUTING -j", tableNamePreRouteing)
|
||||
_ = t.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
|
||||
_ = t.runShell(iptablesPath, "-t nat -X", tableNamePreRouteing)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) runShell(commands ...any) error {
|
||||
commandStr := strings.Join(F.MapToString(commands), " ")
|
||||
var command *exec.Cmd
|
||||
if t.androidSu {
|
||||
command = exec.Command(t.suPath, "-c", commandStr)
|
||||
} else {
|
||||
commandArray := strings.Split(commandStr, " ")
|
||||
command = exec.Command(commandArray[0], commandArray[1:]...)
|
||||
}
|
||||
combinedOutput, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
return E.Extend(err, F.ToString(commandStr, ": ", string(combinedOutput)))
|
||||
}
|
||||
return nil
|
||||
}
|
@ -9,6 +9,7 @@ type TunInboundOptions struct {
|
||||
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
|
||||
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
|
||||
AutoRoute bool `json:"auto_route,omitempty"`
|
||||
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
||||
StrictRoute bool `json:"strict_route,omitempty"`
|
||||
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
|
||||
|
@ -850,7 +850,16 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
|
||||
if metadata.InboundOptions.SniffEnabled {
|
||||
buffer := buf.NewPacket()
|
||||
sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost)
|
||||
sniffMetadata, err := sniff.PeekStream(
|
||||
ctx,
|
||||
conn,
|
||||
buffer,
|
||||
time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||
sniff.StreamDomainNameQuery,
|
||||
sniff.TLSClientHello,
|
||||
sniff.HTTPHost,
|
||||
sniff.BitTorrent,
|
||||
)
|
||||
if sniffMetadata != nil {
|
||||
metadata.Protocol = sniffMetadata.Protocol
|
||||
metadata.Domain = sniffMetadata.Domain
|
||||
@ -977,7 +986,15 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
metadata.Destination = destination
|
||||
}
|
||||
if metadata.InboundOptions.SniffEnabled {
|
||||
sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage)
|
||||
sniffMetadata, _ := sniff.PeekPacket(
|
||||
ctx,
|
||||
buffer.Bytes(),
|
||||
sniff.DomainNameQuery,
|
||||
sniff.QUICClientHello,
|
||||
sniff.STUNMessage,
|
||||
sniff.UTP,
|
||||
sniff.UDPTracker,
|
||||
)
|
||||
if sniffMetadata != nil {
|
||||
metadata.Protocol = sniffMetadata.Protocol
|
||||
metadata.Domain = sniffMetadata.Domain
|
||||
|
Loading…
x
Reference in New Issue
Block a user