mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
Compare commits
1 Commits
v1.2.2
...
dev-l3-rou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9092139e73 |
@@ -117,6 +117,8 @@ nfpms:
|
|||||||
dst: /etc/systemd/system/sing-box@.service
|
dst: /etc/systemd/system/sing-box@.service
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
scripts:
|
||||||
|
postremove: release/config/postremove.sh
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ RUN set -ex \
|
|||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
&& go build -v -trimpath -tags with_quic,with_wireguard,with_reality_server,with_acme \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
3
LICENSE
3
LICENSE
@@ -12,6 +12,3 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
|
|||||||
@@ -25,7 +25,4 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
```
|
```
|
||||||
@@ -27,7 +27,7 @@ type InjectableInbound interface {
|
|||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion int
|
IPVersion uint8
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,3 +18,8 @@ type Outbound interface {
|
|||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPOutbound interface {
|
||||||
|
Outbound
|
||||||
|
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ type Router interface {
|
|||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
|
||||||
|
|
||||||
|
NatRequired(outbound string) bool
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -39,7 +42,9 @@ type Router interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
IPRules() []IPRule
|
||||||
|
|
||||||
TimeService
|
TimeService
|
||||||
|
|
||||||
@@ -78,6 +83,11 @@ type DNSRule interface {
|
|||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPRule interface {
|
||||||
|
Rule
|
||||||
|
Action() tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
InterfaceUpdated() error
|
InterfaceUpdated() error
|
||||||
}
|
}
|
||||||
|
|||||||
49
box.go
49
box.go
@@ -238,6 +238,7 @@ func (s *Box) Start() error {
|
|||||||
|
|
||||||
func (s *Box) preStart() error {
|
func (s *Box) preStart() error {
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices {
|
||||||
|
s.logger.Trace("pre-starting ", serviceName)
|
||||||
err := adapter.PreStart(service)
|
err := adapter.PreStart(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "pre-start ", serviceName)
|
return E.Cause(err, "pre-start ", serviceName)
|
||||||
@@ -245,14 +246,15 @@ func (s *Box) preStart() error {
|
|||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
if starter, isStarter := out.(common.Starter); isStarter {
|
if starter, isStarter := out.(common.Starter); isStarter {
|
||||||
|
var tag string
|
||||||
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("initializing outbound ", tag)
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var tag string
|
|
||||||
if out.Tag() == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = out.Tag()
|
|
||||||
}
|
|
||||||
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,27 +268,30 @@ func (s *Box) start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices {
|
||||||
|
s.logger.Trace("starting ", serviceName)
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "start ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, in := range s.inbounds {
|
for i, in := range s.inbounds {
|
||||||
|
var tag string
|
||||||
|
if in.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = in.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("initializing inbound ", tag)
|
||||||
err = in.Start()
|
err = in.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var tag string
|
|
||||||
if in.Tag() == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = in.Tag()
|
|
||||||
}
|
|
||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.postServices {
|
for serviceName, service := range s.postServices {
|
||||||
|
s.logger.Trace("start ", serviceName)
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "starting ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -302,29 +307,47 @@ func (s *Box) Close() error {
|
|||||||
var errors error
|
var errors error
|
||||||
for serviceName, service := range s.postServices {
|
for serviceName, service := range s.postServices {
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
|
s.logger.Trace("closing ", serviceName)
|
||||||
return E.Cause(err, "close ", serviceName)
|
return E.Cause(err, "close ", serviceName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for i, in := range s.inbounds {
|
for i, in := range s.inbounds {
|
||||||
|
var tag string
|
||||||
|
if in.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = in.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("closing inbound ", tag)
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
|
var tag string
|
||||||
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("closing outbound ", tag)
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||||
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
s.logger.Trace("closing router")
|
||||||
if err := common.Close(s.router); err != nil {
|
if err := common.Close(s.router); err != nil {
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
return E.Cause(err, "close router")
|
return E.Cause(err, "close router")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices {
|
||||||
|
s.logger.Trace("closing ", serviceName)
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", serviceName)
|
return E.Cause(err, "close ", serviceName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
s.logger.Trace("closing logger")
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
if err := common.Close(s.logFactory); err != nil {
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
return E.Cause(err, "close log factory")
|
return E.Cause(err, "close log factory")
|
||||||
|
|||||||
48
common/canceler/instance.go
Normal file
48
common/canceler/instance.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package canceler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instance struct {
|
||||||
|
ctx context.Context
|
||||||
|
cancelFunc context.CancelFunc
|
||||||
|
timer *time.Timer
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance {
|
||||||
|
instance := &Instance{
|
||||||
|
ctx,
|
||||||
|
cancelFunc,
|
||||||
|
time.NewTimer(timeout),
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
go instance.wait()
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Update() bool {
|
||||||
|
if !i.timer.Stop() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !i.timer.Reset(i.timeout) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) wait() {
|
||||||
|
select {
|
||||||
|
case <-i.timer.C:
|
||||||
|
case <-i.ctx.Done():
|
||||||
|
}
|
||||||
|
i.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Close() error {
|
||||||
|
i.timer.Stop()
|
||||||
|
i.cancelFunc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
49
common/canceler/packet.go
Normal file
49
common/canceler/packet.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package canceler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
N.PacketConn
|
||||||
|
instance *Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
instance := New(ctx, cancel, timeout)
|
||||||
|
return ctx, &PacketConn{conn, instance}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = c.PacketConn.ReadPacket(buffer)
|
||||||
|
if err == nil {
|
||||||
|
c.instance.Update()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
err := c.PacketConn.WritePacket(buffer, destination)
|
||||||
|
if err == nil {
|
||||||
|
c.instance.Update()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
c.PacketConn,
|
||||||
|
c.instance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
@@ -27,7 +27,12 @@ type slowOpenConn struct {
|
|||||||
|
|
||||||
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
return dialer.DialContext(ctx, network, destination.String(), nil)
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP, N.NetworkUDP:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.String())
|
||||||
|
default:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &slowOpenConn{
|
return &slowOpenConn{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
@@ -119,10 +124,6 @@ func (c *slowOpenConn) LazyHeadroom() bool {
|
|||||||
return c.conn == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) NeedHandshake() bool {
|
|
||||||
return c.conn == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
|
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
return bufio.Copy(c.conn, r)
|
return bufio.Copy(c.conn, r)
|
||||||
|
|||||||
@@ -1,43 +1,3 @@
|
|||||||
#### 1.2.2
|
|
||||||
|
|
||||||
* Accept `any` outbound in dns rule **1**
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains to `domain` rule.
|
|
||||||
|
|
||||||
#### 1.2.1
|
|
||||||
|
|
||||||
* Fix missing default host in v2ray http transport`s request
|
|
||||||
* Flush DNS cache for macOS when tun start/close
|
|
||||||
* Fix tun's DNS hijacking compatibility with systemd-resolved
|
|
||||||
|
|
||||||
#### 1.2.0
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
Important changes since 1.1:
|
|
||||||
|
|
||||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
|
||||||
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
|
||||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
|
||||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
|
||||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
|
||||||
* Add [reality TLS](/configuration/shared/tls) support
|
|
||||||
* Add [NTP service](/configuration/ntp)
|
|
||||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
|
||||||
* Add SSH [host key validation](/configuration/outbound/ssh) support
|
|
||||||
* Add [query_type](/configuration/dns/rule) DNS rule item
|
|
||||||
* Add fallback support for v2ray transport
|
|
||||||
* Add custom TLS server support for http based v2ray transports
|
|
||||||
* Add health check support for http-based v2ray transports
|
|
||||||
* Add multiple configuration support
|
|
||||||
|
|
||||||
#### 1.2-rc1
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.2-beta10
|
#### 1.2-beta10
|
||||||
|
|
||||||
* Add multiple configuration support **1**
|
* Add multiple configuration support **1**
|
||||||
@@ -45,11 +5,9 @@ Important changes since 1.1:
|
|||||||
|
|
||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
|
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C` to load all configuration files in a directory.
|
||||||
to load all configuration files in a directory.
|
|
||||||
|
|
||||||
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
|
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file name.
|
||||||
name.
|
|
||||||
|
|
||||||
#### 1.1.7
|
#### 1.1.7
|
||||||
|
|
||||||
|
|||||||
@@ -232,8 +232,6 @@ Invert match result.
|
|||||||
|
|
||||||
Match outbound.
|
Match outbound.
|
||||||
|
|
||||||
`any` can be used as a value to match any outbound.
|
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
@@ -257,3 +255,17 @@ Disable cache and save cache in this query.
|
|||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included default rules.
|
Included default rules.
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
Invert match result.
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Tag of the target dns server.
|
||||||
|
|
||||||
|
#### disable_cache
|
||||||
|
|
||||||
|
Disable cache and save cache in this query.
|
||||||
@@ -231,8 +231,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配出站。
|
匹配出站。
|
||||||
|
|
||||||
`any` 可作为值用于匹配任意出站。
|
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
@@ -256,3 +254,17 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
包括的默认规则。
|
包括的默认规则。
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
反选匹配结果。
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
目标 DNS 服务器的标签。
|
||||||
|
|
||||||
|
#### disable_cache
|
||||||
|
|
||||||
|
在此查询中禁用缓存。
|
||||||
@@ -40,8 +40,4 @@ No authentication required if empty.
|
|||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
|
||||||
|
|
||||||
Automatically set system proxy configuration when start and clean up when stop.
|
Automatically set system proxy configuration when start and clean up when stop.
|
||||||
|
|||||||
@@ -40,8 +40,4 @@ HTTP 用户
|
|||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
|
||||||
|
|
||||||
启动时自动设置系统代理,停止时自动清理。
|
启动时自动设置系统代理,停止时自动清理。
|
||||||
@@ -37,8 +37,4 @@ No authentication required if empty.
|
|||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
|
||||||
|
|
||||||
Automatically set system proxy configuration when start and clean up when stop.
|
Automatically set system proxy configuration when start and clean up when stop.
|
||||||
@@ -37,8 +37,4 @@ SOCKS 和 HTTP 用户
|
|||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
|
||||||
|
|
||||||
启动时自动设置系统代理,停止时自动清理。
|
启动时自动设置系统代理,停止时自动清理。
|
||||||
@@ -107,7 +107,8 @@ Enforce strict routing rules when `auto_route` is enabled:
|
|||||||
* Let unsupported network unreachable
|
* Let unsupported network unreachable
|
||||||
* Route all connections to tun
|
* Route all connections to tun
|
||||||
|
|
||||||
It prevents address leaks and makes DNS hijacking work on Android, but your device will not be accessible by others.
|
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
|
||||||
|
not be accessible by others.
|
||||||
|
|
||||||
*In Windows*:
|
*In Windows*:
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
* 让不支持的网络无法到达
|
* 让不支持的网络无法到达
|
||||||
* 将所有连接路由到 tun
|
* 将所有连接路由到 tun
|
||||||
|
|
||||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作,但你的设备将无法其他设备被访问。
|
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
|
||||||
|
|
||||||
*在 Windows 中*:
|
*在 Windows 中*:
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#### Install
|
#### Install
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone -b main https://github.com/SagerNet/sing-box
|
git clone https://github.com/SagerNet/sing-box
|
||||||
cd sing-box
|
cd sing-box
|
||||||
./release/local/install_go.sh # skip if you have golang already installed
|
./release/local/install_go.sh # skip if you have go1.19 already installed
|
||||||
./release/local/install.sh
|
./release/local/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#### 安装
|
#### 安装
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone -b main https://github.com/SagerNet/sing-box
|
git clone https://github.com/SagerNet/sing-box
|
||||||
cd sing-box
|
cd sing-box
|
||||||
./release/local/install_go.sh # 如果已安装 golang 则跳过
|
./release/local/install_go.sh # 如果已安装 go1.19 则跳过
|
||||||
./release/local/install.sh
|
./release/local/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,7 @@
|
|||||||
"disable_cache": true
|
"disable_cache": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"outbound": "any",
|
"domain": "mydomain.com",
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "cn",
|
"geosite": "cn",
|
||||||
"server": "local"
|
"server": "local"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ the public internet.
|
|||||||
|
|
||||||
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
|
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
|
||||||
|
|
||||||
|
##### on Linux
|
||||||
|
|
||||||
|
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled and `strict_route` disabled.
|
||||||
|
|
||||||
#### System proxy
|
#### System proxy
|
||||||
|
|
||||||
##### on Linux
|
##### on Linux
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
`auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启或 `strict_route` 禁用。
|
`auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启或 `strict_route` 禁用。
|
||||||
|
|
||||||
|
##### Linux
|
||||||
|
|
||||||
|
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启且 `strict_route` 禁用。
|
||||||
|
|
||||||
#### 系统代理
|
#### 系统代理
|
||||||
|
|
||||||
##### Linux
|
##### Linux
|
||||||
|
|||||||
@@ -25,7 +25,4 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -25,7 +25,4 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
```
|
```
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -17,19 +17,19 @@ require (
|
|||||||
github.com/insomniacslk/dhcp v0.0.0-20230307103557-e252950ab961
|
github.com/insomniacslk/dhcp v0.0.0-20230307103557-e252950ab961
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mholt/acmez v1.1.0
|
github.com/mholt/acmez v1.1.0
|
||||||
github.com/miekg/dns v1.1.53
|
github.com/miekg/dns v1.1.52
|
||||||
github.com/ooni/go-libtor v1.1.7
|
github.com/ooni/go-libtor v1.1.7
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0
|
github.com/oschwald/maxminddb-golang v1.10.0
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
||||||
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
|
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
||||||
github.com/sagernet/reality v0.0.0-20230323230523-5fa25e693e7f
|
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8
|
||||||
github.com/sagernet/sing v0.2.1
|
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230331013337-06044a57b1da
|
github.com/sagernet/sing-dns v0.1.4
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.0
|
github.com/sagernet/sing-shadowsocks v0.2.0
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0
|
github.com/sagernet/sing-shadowtls v0.1.0
|
||||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab
|
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558
|
||||||
github.com/sagernet/sing-vmess v0.1.3
|
github.com/sagernet/sing-vmess v0.1.3
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||||
@@ -43,11 +43,11 @@ require (
|
|||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.7.0
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
|
||||||
golang.org/x/net v0.8.0
|
golang.org/x/net v0.8.0
|
||||||
golang.org/x/sys v0.6.0
|
golang.org/x/sys v0.6.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
||||||
google.golang.org/grpc v1.54.0
|
google.golang.org/grpc v1.53.0
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.30.0
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
||||||
)
|
)
|
||||||
|
|||||||
28
go.sum
28
go.sum
@@ -70,8 +70,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG
|
|||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
|
github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
|
||||||
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
||||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
|
||||||
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
@@ -107,20 +107,20 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E
|
|||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8=
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8=
|
||||||
github.com/sagernet/reality v0.0.0-20230323230523-5fa25e693e7f h1:plVtFF9NVw5Py4jH/KQuWxojdMFDroTsQ1PVJcU9djM=
|
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 h1:4M3+0/kqvJuTsimEORH0/vUYTpMDUETBH2zNUU38SUw=
|
||||||
github.com/sagernet/reality v0.0.0-20230323230523-5fa25e693e7f/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.2.1 h1:r0STYeyfKBBtoAHsBtW1dQonxG+3Qidde7/1VAMhdn8=
|
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d h1:ktk03rtgPqTDyUd2dWg1uzyr5RnptX8grSMvIzedJlQ=
|
||||||
github.com/sagernet/sing v0.2.1/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
|
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230331013337-06044a57b1da h1:pZV4DRBArbgkajeCZWn3VqwLF+Wl7HOlAt5aSJuuKDk=
|
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230331013337-06044a57b1da/go.mod h1:8x+rlRnPE/5/IagjlAUqR9TceRYRL2WyqmP5QYK3dkI=
|
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.0 h1:ILDWL7pwWfkPLEbviE/MyCgfjaBmJY/JVVY+5jhSb58=
|
github.com/sagernet/sing-shadowsocks v0.2.0 h1:ILDWL7pwWfkPLEbviE/MyCgfjaBmJY/JVVY+5jhSb58=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.0/go.mod h1:ysYzszRLpNzJSorvlWRMuzU6Vchsp7sd52q+JNY4axw=
|
github.com/sagernet/sing-shadowsocks v0.2.0/go.mod h1:ysYzszRLpNzJSorvlWRMuzU6Vchsp7sd52q+JNY4axw=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
||||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab h1:a9oeWuPBuIZ70qMhIIH6RrYhp886xN9jJIwsuu4ZFUo=
|
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558 h1:c5Rm6BTOclEeayS6G9+1rI1kTeilCsn0ALSFbOdlgRE=
|
||||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab/go.mod h1:4YxIDEkkCjGXDOTMPw1SXpLmCQUFAWuaQN250oo+928=
|
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558/go.mod h1:cqnZEm+2ArgP4Gq1NcQfVFm9CZaeGw21mG9AcnYOTiU=
|
||||||
github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
|
github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
|
||||||
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
|
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
@@ -170,8 +170,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
@@ -223,8 +223,8 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzI
|
|||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
||||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/canceler"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -19,7 +21,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common/ranges"
|
"github.com/sagernet/sing/common/ranges"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Inbound = (*Tun)(nil)
|
var (
|
||||||
|
_ adapter.Inbound = (*Tun)(nil)
|
||||||
|
_ tun.Router = (*Tun)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
tag string
|
tag string
|
||||||
@@ -38,10 +43,6 @@ type Tun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
||||||
tunName := options.InterfaceName
|
|
||||||
if tunName == "" {
|
|
||||||
tunName = tun.CalculateInterfaceName("")
|
|
||||||
}
|
|
||||||
tunMTU := options.MTU
|
tunMTU := options.MTU
|
||||||
if tunMTU == 0 {
|
if tunMTU == 0 {
|
||||||
tunMTU = 9000
|
tunMTU = 9000
|
||||||
@@ -75,7 +76,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
inboundOptions: options.InboundOptions,
|
inboundOptions: options.InboundOptions,
|
||||||
tunOptions: tun.Options{
|
tunOptions: tun.Options{
|
||||||
Name: tunName,
|
Name: options.InterfaceName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
||||||
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
||||||
@@ -141,12 +142,17 @@ func (t *Tun) Tag() string {
|
|||||||
|
|
||||||
func (t *Tun) Start() error {
|
func (t *Tun) Start() error {
|
||||||
if C.IsAndroid && t.platformInterface == nil {
|
if C.IsAndroid && t.platformInterface == nil {
|
||||||
|
t.logger.Trace("building android rules")
|
||||||
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
||||||
}
|
}
|
||||||
|
if t.tunOptions.Name == "" {
|
||||||
|
t.tunOptions.Name = tun.CalculateInterfaceName("")
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
tunInterface tun.Tun
|
tunInterface tun.Tun
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
t.logger.Trace("opening interface")
|
||||||
if t.platformInterface != nil {
|
if t.platformInterface != nil {
|
||||||
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
|
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
|
||||||
} else {
|
} else {
|
||||||
@@ -155,7 +161,12 @@ func (t *Tun) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "configure tun interface")
|
return E.Cause(err, "configure tun interface")
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("creating stack")
|
||||||
t.tunIf = tunInterface
|
t.tunIf = tunInterface
|
||||||
|
var tunRouter tun.Router
|
||||||
|
if len(t.router.IPRules()) > 0 {
|
||||||
|
tunRouter = t
|
||||||
|
}
|
||||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||||
Context: t.ctx,
|
Context: t.ctx,
|
||||||
Tun: tunInterface,
|
Tun: tunInterface,
|
||||||
@@ -165,6 +176,7 @@ func (t *Tun) Start() error {
|
|||||||
Inet6Address: t.tunOptions.Inet6Address,
|
Inet6Address: t.tunOptions.Inet6Address,
|
||||||
EndpointIndependentNat: t.endpointIndependentNat,
|
EndpointIndependentNat: t.endpointIndependentNat,
|
||||||
UDPTimeout: t.udpTimeout,
|
UDPTimeout: t.udpTimeout,
|
||||||
|
Router: tunRouter,
|
||||||
Handler: t,
|
Handler: t,
|
||||||
Logger: t.logger,
|
Logger: t.logger,
|
||||||
UnderPlatform: t.platformInterface != nil,
|
UnderPlatform: t.platformInterface != nil,
|
||||||
@@ -172,6 +184,7 @@ func (t *Tun) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("starting stack")
|
||||||
err = t.tunStack.Start()
|
err = t.tunStack.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -187,6 +200,21 @@ func (t *Tun) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tun) RouteConnection(session tun.RouteSession, conn tun.RouteContext) tun.RouteAction {
|
||||||
|
ctx := log.ContextWithNewID(t.ctx)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
metadata.Inbound = t.tag
|
||||||
|
metadata.InboundType = C.TypeTun
|
||||||
|
metadata.IPVersion = session.IPVersion
|
||||||
|
metadata.Network = tun.NetworkName(session.Network)
|
||||||
|
metadata.Source = M.SocksaddrFromNetIP(session.Source)
|
||||||
|
metadata.Destination = M.SocksaddrFromNetIP(session.Destination)
|
||||||
|
metadata.InboundOptions = t.inboundOptions
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection from ", metadata.Source)
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection to ", metadata.Destination)
|
||||||
|
return t.router.RouteIPConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
@@ -206,6 +234,9 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
|
|||||||
|
|
||||||
func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
|
func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
|
if tun.NeedTimeoutFromContext(ctx) {
|
||||||
|
ctx, conn = canceler.NewPacketConn(ctx, conn, time.Duration(t.udpTimeout)*time.Second)
|
||||||
|
}
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
metadata.Inbound = t.tag
|
metadata.Inbound = t.tag
|
||||||
metadata.InboundType = C.TypeTun
|
metadata.InboundType = C.TypeTun
|
||||||
|
|||||||
@@ -36,16 +36,15 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
|
|||||||
if tag != "" {
|
if tag != "" {
|
||||||
message = tag + ": " + message
|
message = tag + ": " + message
|
||||||
}
|
}
|
||||||
var id ID
|
var id uint32
|
||||||
var hasId bool
|
var hasId bool
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
id, hasId = IDFromContext(ctx)
|
id, hasId = IDFromContext(ctx)
|
||||||
}
|
}
|
||||||
if hasId {
|
if hasId {
|
||||||
activeDuration := formatDuration(time.Since(id.CreatedAt))
|
|
||||||
if !f.DisableColors {
|
if !f.DisableColors {
|
||||||
var color aurora.Color
|
var color aurora.Color
|
||||||
color = aurora.Color(uint8(id.ID))
|
color = aurora.Color(uint8(id))
|
||||||
color %= 215
|
color %= 215
|
||||||
row := uint(color / 36)
|
row := uint(color / 36)
|
||||||
column := uint(color % 36)
|
column := uint(color % 36)
|
||||||
@@ -63,9 +62,9 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
|
|||||||
color += 16
|
color += 16
|
||||||
color = color << 16
|
color = color << 16
|
||||||
color |= 1 << 14
|
color |= 1 << 14
|
||||||
message = F.ToString("[", aurora.Colorize(id.ID, color).String(), " ", activeDuration, "] ", message)
|
message = F.ToString("[", aurora.Colorize(id, color).String(), "] ", message)
|
||||||
} else {
|
} else {
|
||||||
message = F.ToString("[", id.ID, " ", activeDuration, "] ", message)
|
message = F.ToString("[", id, "] ", message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
@@ -100,16 +99,15 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
|
|||||||
message = tag + ": " + message
|
message = tag + ": " + message
|
||||||
}
|
}
|
||||||
messageSimple := message
|
messageSimple := message
|
||||||
var id ID
|
var id uint32
|
||||||
var hasId bool
|
var hasId bool
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
id, hasId = IDFromContext(ctx)
|
id, hasId = IDFromContext(ctx)
|
||||||
}
|
}
|
||||||
if hasId {
|
if hasId {
|
||||||
activeDuration := formatDuration(time.Since(id.CreatedAt))
|
|
||||||
if !f.DisableColors {
|
if !f.DisableColors {
|
||||||
var color aurora.Color
|
var color aurora.Color
|
||||||
color = aurora.Color(uint8(id.ID))
|
color = aurora.Color(uint8(id))
|
||||||
color %= 215
|
color %= 215
|
||||||
row := uint(color / 36)
|
row := uint(color / 36)
|
||||||
column := uint(color % 36)
|
column := uint(color % 36)
|
||||||
@@ -127,11 +125,11 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
|
|||||||
color += 16
|
color += 16
|
||||||
color = color << 16
|
color = color << 16
|
||||||
color |= 1 << 14
|
color |= 1 << 14
|
||||||
message = F.ToString("[", aurora.Colorize(id.ID, color).String(), " ", activeDuration, "] ", message)
|
message = F.ToString("[", aurora.Colorize(id, color).String(), "] ", message)
|
||||||
} else {
|
} else {
|
||||||
message = F.ToString("[", id.ID, " ", activeDuration, "] ", message)
|
message = F.ToString("[", id, "] ", message)
|
||||||
}
|
}
|
||||||
messageSimple = F.ToString("[", id.ID, " ", activeDuration, "] ", messageSimple)
|
messageSimple = F.ToString("[", id, "] ", messageSimple)
|
||||||
|
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
@@ -155,13 +153,3 @@ func xd(value int, x int) string {
|
|||||||
}
|
}
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatDuration(duration time.Duration) string {
|
|
||||||
if duration < time.Second {
|
|
||||||
return F.ToString(duration.Milliseconds(), "ms")
|
|
||||||
} else if duration < time.Minute {
|
|
||||||
return F.ToString(int64(duration.Seconds()), ".", int64(duration.Seconds()*100)%100, "s")
|
|
||||||
} else {
|
|
||||||
return F.ToString(int64(duration.Minutes()), "m", int64(duration.Seconds())%60, "s")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
15
log/id.go
15
log/id.go
@@ -3,7 +3,6 @@ package log
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/random"
|
"github.com/sagernet/sing/common/random"
|
||||||
)
|
)
|
||||||
@@ -14,19 +13,11 @@ func init() {
|
|||||||
|
|
||||||
type idKey struct{}
|
type idKey struct{}
|
||||||
|
|
||||||
type ID struct {
|
|
||||||
ID uint32
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContextWithNewID(ctx context.Context) context.Context {
|
func ContextWithNewID(ctx context.Context) context.Context {
|
||||||
return context.WithValue(ctx, (*idKey)(nil), ID{
|
return context.WithValue(ctx, (*idKey)(nil), rand.Uint32())
|
||||||
ID: rand.Uint32(),
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IDFromContext(ctx context.Context) (ID, bool) {
|
func IDFromContext(ctx context.Context) (uint32, bool) {
|
||||||
id, loaded := ctx.Value((*idKey)(nil)).(ID)
|
id, loaded := ctx.Value((*idKey)(nil)).(uint32)
|
||||||
return id, loaded
|
return id, loaded
|
||||||
}
|
}
|
||||||
|
|||||||
103
option/dns.go
103
option/dns.go
@@ -1,14 +1,5 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DNSOptions struct {
|
type DNSOptions struct {
|
||||||
Servers []DNSServerOptions `json:"servers,omitempty"`
|
Servers []DNSServerOptions `json:"servers,omitempty"`
|
||||||
Rules []DNSRule `json:"rules,omitempty"`
|
Rules []DNSRule `json:"rules,omitempty"`
|
||||||
@@ -31,97 +22,3 @@ type DNSServerOptions struct {
|
|||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSRule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultDNSRule `json:"-"`
|
|
||||||
LogicalOptions LogicalDNSRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule _DNSRule
|
|
||||||
|
|
||||||
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_DNSRule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "dns route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
|
||||||
Network string `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
Outbound Listable[string] `json:"outbound,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultDNSRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultDNSRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Server = r.Server
|
|
||||||
defaultValue.DisableCache = r.DisableCache
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalDNSRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
|
||||||
}
|
|
||||||
|
|||||||
101
option/route.go
101
option/route.go
@@ -1,17 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RouteOptions struct {
|
type RouteOptions struct {
|
||||||
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
||||||
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
||||||
|
IPRules []IPRule `json:"ip_rules,omitempty"`
|
||||||
Rules []Rule `json:"rules,omitempty"`
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
FindProcess bool `json:"find_process,omitempty"`
|
FindProcess bool `json:"find_process,omitempty"`
|
||||||
@@ -32,94 +24,3 @@ type GeositeOptions struct {
|
|||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _Rule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultRule `json:"-"`
|
|
||||||
LogicalOptions LogicalRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule _Rule
|
|
||||||
|
|
||||||
func (r Rule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_Rule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_Rule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
Network string `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
GeoIP Listable[string] `json:"geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Outbound = r.Outbound
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
|
||||||
}
|
|
||||||
|
|||||||
101
option/rule.go
Normal file
101
option/rule.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _Rule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultRule `json:"-"`
|
||||||
|
LogicalOptions LogicalRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule _Rule
|
||||||
|
|
||||||
|
func (r Rule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_Rule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_Rule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
GeoIP Listable[string] `json:"geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
||||||
|
}
|
||||||
104
option/rule_dns.go
Normal file
104
option/rule_dns.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _DNSRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultDNSRule `json:"-"`
|
||||||
|
LogicalOptions LogicalDNSRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule _DNSRule
|
||||||
|
|
||||||
|
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_DNSRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "dns route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultDNSRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
Outbound Listable[string] `json:"outbound,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultDNSRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultDNSRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Server = r.Server
|
||||||
|
defaultValue.DisableCache = r.DisableCache
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalDNSRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalDNSRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
||||||
|
}
|
||||||
125
option/rule_ip.go
Normal file
125
option/rule_ip.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _IPRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultIPRule `json:"-"`
|
||||||
|
LogicalOptions LogicalIPRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPRule _IPRule
|
||||||
|
|
||||||
|
func (r IPRule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_IPRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IPRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_IPRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_IPRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "ip route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultIPRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteAction tun.ActionType
|
||||||
|
|
||||||
|
func (a RouteAction) MarshalJSON() ([]byte, error) {
|
||||||
|
switch tun.ActionType(a) {
|
||||||
|
case tun.ActionTypeReject, tun.ActionTypeDirect:
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown action: ", a)
|
||||||
|
}
|
||||||
|
return json.Marshal(tun.ActionTypeName(tun.ActionType(a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RouteAction) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var value string
|
||||||
|
err := json.Unmarshal(bytes, &value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
actionType, err := tun.ParseActionType(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch actionType {
|
||||||
|
case tun.ActionTypeReject, tun.ActionTypeDirect:
|
||||||
|
default:
|
||||||
|
return E.New("unknown action: ", a)
|
||||||
|
}
|
||||||
|
*a = RouteAction(actionType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultIPRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultIPRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Action = r.Action
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalIPRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultIPRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalIPRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultIPRule.IsValid)
|
||||||
|
}
|
||||||
@@ -13,4 +13,5 @@ type WireGuardOutboundOptions struct {
|
|||||||
Workers int `json:"workers,omitempty"`
|
Workers int `json:"workers,omitempty"`
|
||||||
MTU uint32 `json:"mtu,omitempty"`
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
IPRewrite bool `json:"ip_rewrite,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Outbound) (adapter.Outbound, error) {
|
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Outbound) (adapter.Outbound, error) {
|
||||||
var metadata *adapter.InboundContext
|
|
||||||
if options.Tag != "" {
|
|
||||||
ctx, metadata = adapter.AppendContext(ctx)
|
|
||||||
metadata.Outbound = options.Tag
|
|
||||||
}
|
|
||||||
if options.Type == "" {
|
if options.Type == "" {
|
||||||
return nil, E.New("missing outbound type")
|
return nil, E.New("missing outbound type")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/canceler"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/canceler"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/canceler"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/canceler"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/task"
|
"github.com/sagernet/sing/common/task"
|
||||||
|
|||||||
@@ -136,9 +136,6 @@ var _ N.Dialer = (*shadowsocksDialer)(nil)
|
|||||||
type shadowsocksDialer Shadowsocks
|
type shadowsocksDialer Shadowsocks
|
||||||
|
|
||||||
func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
|
||||||
metadata.Outbound = h.tag
|
|
||||||
metadata.Destination = destination
|
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
@@ -164,9 +161,6 @@ func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, des
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
|
||||||
metadata.Outbound = h.tag
|
|
||||||
metadata.Destination = destination
|
|
||||||
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
|
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -99,9 +99,6 @@ func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.Cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowsocksR) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *ShadowsocksR) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
|
||||||
metadata.Outbound = h.tag
|
|
||||||
metadata.Destination = destination
|
|
||||||
switch network {
|
switch network {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
@@ -134,9 +131,6 @@ func (h *ShadowsocksR) DialContext(ctx context.Context, network string, destinat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowsocksR) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (h *ShadowsocksR) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
|
||||||
metadata.Outbound = h.tag
|
|
||||||
metadata.Destination = destination
|
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
|
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -86,26 +86,23 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (s *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
|
||||||
metadata.Outbound = h.tag
|
|
||||||
metadata.Destination = destination
|
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
return h.client.DialContext(ctx)
|
return s.client.DialContext(ctx)
|
||||||
default:
|
default:
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (s *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (s *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return NewConnection(ctx, h, conn, metadata)
|
return NewConnection(ctx, s, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowTLS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (s *ShadowTLS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
@@ -26,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Outbound = (*WireGuard)(nil)
|
_ adapter.IPOutbound = (*WireGuard)(nil)
|
||||||
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ type WireGuard struct {
|
|||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
bind *wireguard.ClientBind
|
bind *wireguard.ClientBind
|
||||||
device *device.Device
|
device *device.Device
|
||||||
|
natDevice wireguard.NatDevice
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,17 +109,25 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
if mtu == 0 {
|
if mtu == 0 {
|
||||||
mtu = 1408
|
mtu = 1408
|
||||||
}
|
}
|
||||||
var wireTunDevice wireguard.Device
|
var tunDevice wireguard.Device
|
||||||
var err error
|
var err error
|
||||||
if !options.SystemInterface && tun.WithGVisor {
|
if !options.SystemInterface && tun.WithGVisor {
|
||||||
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
|
tunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu, options.IPRewrite)
|
||||||
} else {
|
} else {
|
||||||
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
tunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create WireGuard device")
|
return nil, E.Cause(err, "create WireGuard device")
|
||||||
}
|
}
|
||||||
wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
|
natDevice, isNatDevice := tunDevice.(wireguard.NatDevice)
|
||||||
|
if !isNatDevice && router.NatRequired(tag) {
|
||||||
|
natDevice = wireguard.NewNATDevice(tunDevice, options.IPRewrite)
|
||||||
|
}
|
||||||
|
deviceInput := tunDevice
|
||||||
|
if natDevice != nil {
|
||||||
|
deviceInput = natDevice
|
||||||
|
}
|
||||||
|
wgDevice := device.NewDevice(deviceInput, outbound.bind, &device.Logger{
|
||||||
Verbosef: func(format string, args ...interface{}) {
|
Verbosef: func(format string, args ...interface{}) {
|
||||||
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
||||||
},
|
},
|
||||||
@@ -132,7 +143,8 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
return nil, E.Cause(err, "setup wireguard")
|
return nil, E.Cause(err, "setup wireguard")
|
||||||
}
|
}
|
||||||
outbound.device = wgDevice
|
outbound.device = wgDevice
|
||||||
outbound.tunDevice = wireTunDevice
|
outbound.natDevice = natDevice
|
||||||
|
outbound.tunDevice = tunDevice
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +183,27 @@ func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn,
|
|||||||
return NewPacketConnection(ctx, w, conn, metadata)
|
return NewPacketConnection(ctx, w, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WireGuard) NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) (tun.DirectDestination, error) {
|
||||||
|
if w.natDevice == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
session := tun.RouteSession{
|
||||||
|
IPVersion: metadata.IPVersion,
|
||||||
|
Network: tun.NetworkFromName(metadata.Network),
|
||||||
|
Source: metadata.Source.AddrPort(),
|
||||||
|
Destination: metadata.Destination.AddrPort(),
|
||||||
|
}
|
||||||
|
switch session.Network {
|
||||||
|
case syscall.IPPROTO_TCP:
|
||||||
|
w.logger.InfoContext(ctx, "linked connection to ", metadata.Destination)
|
||||||
|
case syscall.IPPROTO_UDP:
|
||||||
|
w.logger.InfoContext(ctx, "linked packet connection to ", metadata.Destination)
|
||||||
|
default:
|
||||||
|
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
|
||||||
|
}
|
||||||
|
return w.natDevice.CreateDestination(session, conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WireGuard) Start() error {
|
func (w *WireGuard) Start() error {
|
||||||
return w.tunDevice.Start()
|
return w.tunDevice.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
3
release/config/postremove.sh
Executable file
3
release/config/postremove.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
rm -rf /var/lib/sing-box
|
||||||
@@ -4,9 +4,10 @@ Documentation=https://sing-box.sagernet.org
|
|||||||
After=network.target nss-lookup.target
|
After=network.target nss-lookup.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
WorkingDirectory=/var/lib/sing-box
|
||||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run
|
ExecStart=/usr/bin/sing-box run -c /etc/sing-box/config.json
|
||||||
ExecReload=/bin/kill -HUP $MAINPID
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10s
|
RestartSec=10s
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ Documentation=https://sing-box.sagernet.org
|
|||||||
After=network.target nss-lookup.target
|
After=network.target nss-lookup.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
WorkingDirectory=/var/lib/sing-box-%i
|
||||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box-%i -c /etc/sing-box/%i.json run
|
ExecStart=/usr/bin/sing-box run -c /etc/sing-box/%i.json
|
||||||
ExecReload=/bin/kill -HUP $MAINPID
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10s
|
RestartSec=10s
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
|
curl -Lo go.tar.gz https://go.dev/dl/go1.20.1.linux-amd64.tar.gz
|
||||||
go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
|
||||||
curl -Lo go.tar.gz "https://go.dev/dl/go$go_version.linux-amd64.tar.gz"
|
|
||||||
sudo rm -rf /usr/local/go
|
sudo rm -rf /usr/local/go
|
||||||
sudo tar -C /usr/local -xzf go.tar.gz
|
sudo tar -C /usr/local -xzf go.tar.gz
|
||||||
rm go.tar.gz
|
rm go.tar.gz
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ Documentation=https://sing-box.sagernet.org
|
|||||||
After=network.target nss-lookup.target
|
After=network.target nss-lookup.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
WorkingDirectory=/var/lib/sing-box
|
||||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
ExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /usr/local/etc/sing-box run
|
ExecStart=/usr/local/bin/sing-box run -c /usr/local/etc/sing-box/config.json
|
||||||
ExecReload=/bin/kill -HUP $MAINPID
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10s
|
RestartSec=10s
|
||||||
|
|||||||
272
route/router.go
272
route/router.go
@@ -2,14 +2,11 @@ package route
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -37,7 +34,6 @@ import (
|
|||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,6 +68,7 @@ type Router struct {
|
|||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
rules []adapter.Rule
|
rules []adapter.Rule
|
||||||
|
ipRules []adapter.IPRule
|
||||||
defaultDetour string
|
defaultDetour string
|
||||||
defaultOutboundForConnection adapter.Outbound
|
defaultOutboundForConnection adapter.Outbound
|
||||||
defaultOutboundForPacketConnection adapter.Outbound
|
defaultOutboundForPacketConnection adapter.Outbound
|
||||||
@@ -128,6 +125,7 @@ func NewRouter(
|
|||||||
dnsLogger: logFactory.NewLogger("dns"),
|
dnsLogger: logFactory.NewLogger("dns"),
|
||||||
outboundByTag: make(map[string]adapter.Outbound),
|
outboundByTag: make(map[string]adapter.Outbound),
|
||||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||||
|
ipRules: make([]adapter.IPRule, 0, len(options.IPRules)),
|
||||||
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
||||||
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
||||||
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
||||||
@@ -149,6 +147,13 @@ func NewRouter(
|
|||||||
}
|
}
|
||||||
router.rules = append(router.rules, routeRule)
|
router.rules = append(router.rules, routeRule)
|
||||||
}
|
}
|
||||||
|
for i, ipRuleOptions := range options.IPRules {
|
||||||
|
ipRule, err := NewIPRule(router, router.logger, ipRuleOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse ip rule[", i, "]")
|
||||||
|
}
|
||||||
|
router.ipRules = append(router.ipRules, ipRule)
|
||||||
|
}
|
||||||
for i, dnsRuleOptions := range dnsOptions.Rules {
|
for i, dnsRuleOptions := range dnsOptions.Rules {
|
||||||
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -156,6 +161,7 @@ func NewRouter(
|
|||||||
}
|
}
|
||||||
router.dnsRules = append(router.dnsRules, dnsRule)
|
router.dnsRules = append(router.dnsRules, dnsRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
||||||
dummyTransportMap := make(map[string]dns.Transport)
|
dummyTransportMap := make(map[string]dns.Transport)
|
||||||
transportMap := make(map[string]dns.Transport)
|
transportMap := make(map[string]dns.Transport)
|
||||||
@@ -516,27 +522,6 @@ func (r *Router) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) GeoIPReader() *geoip.Reader {
|
|
||||||
return r.geoIPReader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
|
||||||
rule, cached := r.geositeCache[code]
|
|
||||||
if cached {
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
items, err := r.geositeReader.Read(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.geositeCache[code] = rule
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
outbound, loaded := r.outboundByTag[tag]
|
outbound, loaded := r.outboundByTag[tag]
|
||||||
return outbound, loaded
|
return outbound, loaded
|
||||||
@@ -811,6 +796,10 @@ func (r *Router) Rules() []adapter.Rule {
|
|||||||
return r.rules
|
return r.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) IPRules() []adapter.IPRule {
|
||||||
|
return r.ipRules
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
||||||
return r.networkMonitor
|
return r.networkMonitor
|
||||||
}
|
}
|
||||||
@@ -846,239 +835,6 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
|
|||||||
r.v2rayServer = server
|
r.v2rayServer = server
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func notPrivateNode(code string) bool {
|
|
||||||
return code != "private"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeoIPDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geoIPOptions.Path != "" {
|
|
||||||
geoPath = r.geoIPOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geoip.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geoip database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeoIPDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geoip database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geoip.Open(geoPath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open geoip database")
|
|
||||||
}
|
|
||||||
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
|
||||||
r.geoIPReader = geoReader
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeositeDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geositeOptions.Path != "" {
|
|
||||||
geoPath = r.geositeOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geosite.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geosite database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeositeDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geosite database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geosite.Open(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
|
||||||
r.geositeReader = geoReader
|
|
||||||
} else {
|
|
||||||
return E.Cause(err, "open geosite database")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geoIPOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geoIPOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geoip database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geoIPOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geositeOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geositeOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geosite database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geositeOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
||||||
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
||||||
}
|
}
|
||||||
|
|||||||
283
route/router_geo_resources.go
Normal file
283
route/router_geo_resources.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Router) GeoIPReader() *geoip.Reader {
|
||||||
|
return r.geoIPReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
||||||
|
rule, cached := r.geositeCache[code]
|
||||||
|
if cached {
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
items, err := r.geositeReader.Read(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.geositeCache[code] = rule
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeoIPDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geoIPOptions.Path != "" {
|
||||||
|
geoPath = r.geoIPOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geoip.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if rw.FileExists(geoPath) {
|
||||||
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
||||||
|
r.geoIPReader = geoReader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geoip database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeoIPDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geoip database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open geoip database")
|
||||||
|
}
|
||||||
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
||||||
|
r.geoIPReader = geoReader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeositeDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geositeOptions.Path != "" {
|
||||||
|
geoPath = r.geositeOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geosite.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geosite database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeositeDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geosite database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geosite.Open(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
||||||
|
r.geositeReader = geoReader
|
||||||
|
} else {
|
||||||
|
return E.Cause(err, "open geosite database")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geoIPOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geoIPOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geoip database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geoIPOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geositeOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geositeOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geosite database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geositeOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func notPrivateNode(code string) bool {
|
||||||
|
return code != "private"
|
||||||
|
}
|
||||||
47
route/router_ip.go
Normal file
47
route/router_ip.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Router) RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) tun.RouteAction {
|
||||||
|
for i, rule := range r.ipRules {
|
||||||
|
if rule.Match(&metadata) {
|
||||||
|
if rule.Action() == tun.ActionTypeReject {
|
||||||
|
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => reject")
|
||||||
|
return (*tun.ActionReject)(nil)
|
||||||
|
}
|
||||||
|
detour := rule.Outbound()
|
||||||
|
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||||
|
outbound, loaded := r.Outbound(detour)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "outbound not found: ", detour)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ipOutbound, loaded := outbound.(adapter.IPOutbound)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "outbound have no ip connection support: ", detour)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
destination, err := ipOutbound.NewIPConnection(ctx, conn, metadata)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return &tun.ActionDirect{DirectDestination: destination}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*tun.ActionReturn)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) NatRequired(outbound string) bool {
|
||||||
|
for _, ipRule := range r.ipRules {
|
||||||
|
if ipRule.Outbound() == outbound {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
199
route/rule_abstract.go
Normal file
199
route/rule_abstract.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type abstractDefaultRule struct {
|
||||||
|
items []RuleItem
|
||||||
|
sourceAddressItems []RuleItem
|
||||||
|
sourcePortItems []RuleItem
|
||||||
|
destinationAddressItems []RuleItem
|
||||||
|
destinationPortItems []RuleItem
|
||||||
|
allItems []RuleItem
|
||||||
|
invert bool
|
||||||
|
outbound string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Type() string {
|
||||||
|
return C.RuleTypeDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Start() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
err := common.Start(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Close() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
err := common.Close(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) UpdateGeosite() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
||||||
|
err := geositeItem.Update()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
for _, item := range r.items {
|
||||||
|
if !item.Match(metadata) {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.sourceAddressItems) > 0 {
|
||||||
|
var sourceAddressMatch bool
|
||||||
|
for _, item := range r.sourceAddressItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
sourceAddressMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sourceAddressMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.sourcePortItems) > 0 {
|
||||||
|
var sourcePortMatch bool
|
||||||
|
for _, item := range r.sourcePortItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
sourcePortMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sourcePortMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.destinationAddressItems) > 0 {
|
||||||
|
var destinationAddressMatch bool
|
||||||
|
for _, item := range r.destinationAddressItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
destinationAddressMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !destinationAddressMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.destinationPortItems) > 0 {
|
||||||
|
var destinationPortMatch bool
|
||||||
|
for _, item := range r.destinationPortItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
destinationPortMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !destinationPortMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !r.invert
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Outbound() string {
|
||||||
|
return r.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) String() string {
|
||||||
|
return strings.Join(F.MapToString(r.allItems), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type abstractLogicalRule struct {
|
||||||
|
rules []adapter.Rule
|
||||||
|
mode string
|
||||||
|
invert bool
|
||||||
|
outbound string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Type() string {
|
||||||
|
return C.RuleTypeLogical
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) UpdateGeosite() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.UpdateGeosite()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Start() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Close() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
if r.mode == C.LogicalTypeAnd {
|
||||||
|
return common.All(r.rules, func(it adapter.Rule) bool {
|
||||||
|
return it.Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
} else {
|
||||||
|
return common.Any(r.rules, func(it adapter.Rule) bool {
|
||||||
|
return it.Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Outbound() string {
|
||||||
|
return r.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) String() string {
|
||||||
|
var op string
|
||||||
|
switch r.mode {
|
||||||
|
case C.LogicalTypeAnd:
|
||||||
|
op = "&&"
|
||||||
|
case C.LogicalTypeOr:
|
||||||
|
op = "||"
|
||||||
|
}
|
||||||
|
if !r.invert {
|
||||||
|
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
||||||
|
} else {
|
||||||
|
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) {
|
func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) {
|
||||||
@@ -39,14 +34,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul
|
|||||||
var _ adapter.Rule = (*DefaultRule)(nil)
|
var _ adapter.Rule = (*DefaultRule)(nil)
|
||||||
|
|
||||||
type DefaultRule struct {
|
type DefaultRule struct {
|
||||||
items []RuleItem
|
abstractDefaultRule
|
||||||
sourceAddressItems []RuleItem
|
|
||||||
sourcePortItems []RuleItem
|
|
||||||
destinationAddressItems []RuleItem
|
|
||||||
destinationPortItems []RuleItem
|
|
||||||
allItems []RuleItem
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleItem interface {
|
type RuleItem interface {
|
||||||
@@ -56,8 +44,10 @@ type RuleItem interface {
|
|||||||
|
|
||||||
func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
|
func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
|
||||||
rule := &DefaultRule{
|
rule := &DefaultRule{
|
||||||
invert: options.Invert,
|
abstractDefaultRule{
|
||||||
outbound: options.Outbound,
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if len(options.Inbound) > 0 {
|
if len(options.Inbound) > 0 {
|
||||||
item := NewInboundRule(options.Inbound)
|
item := NewInboundRule(options.Inbound)
|
||||||
@@ -74,15 +64,10 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
return nil, E.New("invalid ip version: ", options.IPVersion)
|
return nil, E.New("invalid ip version: ", options.IPVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Network != "" {
|
if len(options.Network) > 0 {
|
||||||
switch options.Network {
|
item := NewNetworkItem(options.Network)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
rule.items = append(rule.items, item)
|
||||||
item := NewNetworkItem(options.Network)
|
rule.allItems = append(rule.allItems, item)
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
default:
|
|
||||||
return nil, E.New("invalid network: ", options.Network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(options.AuthUser) > 0 {
|
if len(options.AuthUser) > 0 {
|
||||||
item := NewAuthUserItem(options.AuthUser)
|
item := NewAuthUserItem(options.AuthUser)
|
||||||
@@ -202,130 +187,19 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultRule) Type() string {
|
|
||||||
return C.RuleTypeDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Start() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Start(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Close() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Close(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) UpdateGeosite() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
|
||||||
err := geositeItem.Update()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
for _, item := range r.items {
|
|
||||||
if !item.Match(metadata) {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourceAddressItems) > 0 {
|
|
||||||
var sourceAddressMatch bool
|
|
||||||
for _, item := range r.sourceAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourceAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourceAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourcePortItems) > 0 {
|
|
||||||
var sourcePortMatch bool
|
|
||||||
for _, item := range r.sourcePortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourcePortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourcePortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 {
|
|
||||||
var destinationAddressMatch bool
|
|
||||||
for _, item := range r.destinationAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 {
|
|
||||||
var destinationPortMatch bool
|
|
||||||
for _, item := range r.destinationPortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationPortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationPortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !r.invert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) String() string {
|
|
||||||
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)
|
||||||
|
|
||||||
type LogicalRule struct {
|
type LogicalRule struct {
|
||||||
mode string
|
abstractLogicalRule
|
||||||
rules []*DefaultRule
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
||||||
r := &LogicalRule{
|
r := &LogicalRule{
|
||||||
rules: make([]*DefaultRule, len(options.Rules)),
|
abstractLogicalRule{
|
||||||
invert: options.Invert,
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
outbound: options.Outbound,
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
switch options.Mode {
|
switch options.Mode {
|
||||||
case C.LogicalTypeAnd:
|
case C.LogicalTypeAnd:
|
||||||
@@ -344,68 +218,3 @@ func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalRule) Type() string {
|
|
||||||
return C.RuleTypeLogical
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) UpdateGeosite() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.UpdateGeosite()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Start() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Close() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
if r.mode == C.LogicalTypeAnd {
|
|
||||||
return common.All(r.rules, func(it *DefaultRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
} else {
|
|
||||||
return common.Any(r.rules, func(it *DefaultRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) String() string {
|
|
||||||
var op string
|
|
||||||
switch r.mode {
|
|
||||||
case C.LogicalTypeAnd:
|
|
||||||
op = "&&"
|
|
||||||
case C.LogicalTypeOr:
|
|
||||||
op = "||"
|
|
||||||
}
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) {
|
func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) {
|
||||||
@@ -39,21 +34,16 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.
|
|||||||
var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
|
var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
type DefaultDNSRule struct {
|
||||||
items []RuleItem
|
abstractDefaultRule
|
||||||
sourceAddressItems []RuleItem
|
disableCache bool
|
||||||
sourcePortItems []RuleItem
|
|
||||||
destinationAddressItems []RuleItem
|
|
||||||
destinationPortItems []RuleItem
|
|
||||||
allItems []RuleItem
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
disableCache bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
||||||
rule := &DefaultDNSRule{
|
rule := &DefaultDNSRule{
|
||||||
invert: options.Invert,
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
outbound: options.Server,
|
invert: options.Invert,
|
||||||
|
outbound: options.Server,
|
||||||
|
},
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
}
|
}
|
||||||
if len(options.Inbound) > 0 {
|
if len(options.Inbound) > 0 {
|
||||||
@@ -76,15 +66,10 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if options.Network != "" {
|
if len(options.Network) > 0 {
|
||||||
switch options.Network {
|
item := NewNetworkItem(options.Network)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
rule.items = append(rule.items, item)
|
||||||
item := NewNetworkItem(options.Network)
|
rule.allItems = append(rule.allItems, item)
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
default:
|
|
||||||
return nil, E.New("invalid network: ", options.Network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(options.AuthUser) > 0 {
|
if len(options.AuthUser) > 0 {
|
||||||
item := NewAuthUserItem(options.AuthUser)
|
item := NewAuthUserItem(options.AuthUser)
|
||||||
@@ -196,131 +181,24 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
|||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Type() string {
|
|
||||||
return C.RuleTypeDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Start() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Start(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Close() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Close(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) UpdateGeosite() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
|
||||||
err := geositeItem.Update()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
for _, item := range r.items {
|
|
||||||
if !item.Match(metadata) {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourceAddressItems) > 0 {
|
|
||||||
var sourceAddressMatch bool
|
|
||||||
for _, item := range r.sourceAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourceAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourceAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourcePortItems) > 0 {
|
|
||||||
var sourcePortMatch bool
|
|
||||||
for _, item := range r.sourcePortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourcePortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourcePortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 {
|
|
||||||
var destinationAddressMatch bool
|
|
||||||
for _, item := range r.destinationAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 {
|
|
||||||
var destinationPortMatch bool
|
|
||||||
for _, item := range r.destinationPortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationPortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationPortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !r.invert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) DisableCache() bool {
|
func (r *DefaultDNSRule) DisableCache() bool {
|
||||||
return r.disableCache
|
return r.disableCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultDNSRule) String() string {
|
|
||||||
return strings.Join(F.MapToString(r.allItems), " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
type LogicalDNSRule struct {
|
||||||
mode string
|
abstractLogicalRule
|
||||||
rules []*DefaultDNSRule
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
disableCache bool
|
disableCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
||||||
r := &LogicalDNSRule{
|
r := &LogicalDNSRule{
|
||||||
rules: make([]*DefaultDNSRule, len(options.Rules)),
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
invert: options.Invert,
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
outbound: options.Server,
|
invert: options.Invert,
|
||||||
|
outbound: options.Server,
|
||||||
|
},
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
}
|
}
|
||||||
switch options.Mode {
|
switch options.Mode {
|
||||||
@@ -341,71 +219,6 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Type() string {
|
|
||||||
return C.RuleTypeLogical
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) UpdateGeosite() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.UpdateGeosite()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Start() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Close() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
if r.mode == C.LogicalTypeAnd {
|
|
||||||
return common.All(r.rules, func(it *DefaultDNSRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
} else {
|
|
||||||
return common.Any(r.rules, func(it *DefaultDNSRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) DisableCache() bool {
|
func (r *LogicalDNSRule) DisableCache() bool {
|
||||||
return r.disableCache
|
return r.disableCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalDNSRule) String() string {
|
|
||||||
var op string
|
|
||||||
switch r.mode {
|
|
||||||
case C.LogicalTypeAnd:
|
|
||||||
op = "&&"
|
|
||||||
case C.LogicalTypeOr:
|
|
||||||
op = "||"
|
|
||||||
}
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
176
route/rule_ip.go
Normal file
176
route/rule_ip.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIPRule(router adapter.Router, logger log.ContextLogger, options option.IPRule) (adapter.IPRule, error) {
|
||||||
|
switch options.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
if !options.DefaultOptions.IsValid() {
|
||||||
|
return nil, E.New("missing conditions")
|
||||||
|
}
|
||||||
|
if common.IsEmpty(options.DefaultOptions.Action) {
|
||||||
|
return nil, E.New("missing action")
|
||||||
|
}
|
||||||
|
return NewDefaultIPRule(router, logger, options.DefaultOptions)
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
if !options.LogicalOptions.IsValid() {
|
||||||
|
return nil, E.New("missing conditions")
|
||||||
|
}
|
||||||
|
if common.IsEmpty(options.DefaultOptions.Action) {
|
||||||
|
return nil, E.New("missing action")
|
||||||
|
}
|
||||||
|
return NewLogicalIPRule(router, logger, options.LogicalOptions)
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: ", options.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.IPRule = (*DefaultIPRule)(nil)
|
||||||
|
|
||||||
|
type DefaultIPRule struct {
|
||||||
|
abstractDefaultRule
|
||||||
|
action tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultIPRule(router adapter.Router, logger log.ContextLogger, options option.DefaultIPRule) (*DefaultIPRule, error) {
|
||||||
|
rule := &DefaultIPRule{
|
||||||
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
|
action: tun.ActionType(options.Action),
|
||||||
|
}
|
||||||
|
if len(options.Inbound) > 0 {
|
||||||
|
item := NewInboundRule(options.Inbound)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.IPVersion > 0 {
|
||||||
|
switch options.IPVersion {
|
||||||
|
case 4, 6:
|
||||||
|
item := NewIPVersionItem(options.IPVersion == 6)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
default:
|
||||||
|
return nil, E.New("invalid ip version: ", options.IPVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(options.Network) > 0 {
|
||||||
|
item := NewNetworkItem(options.Network)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
|
||||||
|
item := NewDomainItem(options.Domain, options.DomainSuffix)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DomainKeyword) > 0 {
|
||||||
|
item := NewDomainKeywordItem(options.DomainKeyword)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DomainRegex) > 0 {
|
||||||
|
item, err := NewDomainRegexItem(options.DomainRegex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "domain_regex")
|
||||||
|
}
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Geosite) > 0 {
|
||||||
|
item := NewGeositeItem(router, logger, options.Geosite)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourceGeoIP) > 0 {
|
||||||
|
item := NewGeoIPItem(router, logger, true, options.SourceGeoIP)
|
||||||
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourceIPCIDR) > 0 {
|
||||||
|
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "source_ipcidr")
|
||||||
|
}
|
||||||
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourcePort) > 0 {
|
||||||
|
item := NewPortItem(true, options.SourcePort)
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourcePortRange) > 0 {
|
||||||
|
item, err := NewPortRangeItem(true, options.SourcePortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "source_port_range")
|
||||||
|
}
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Port) > 0 {
|
||||||
|
item := NewPortItem(false, options.Port)
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.PortRange) > 0 {
|
||||||
|
item, err := NewPortRangeItem(false, options.PortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "port_range")
|
||||||
|
}
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultIPRule) Action() tun.ActionType {
|
||||||
|
return r.action
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.IPRule = (*LogicalIPRule)(nil)
|
||||||
|
|
||||||
|
type LogicalIPRule struct {
|
||||||
|
abstractLogicalRule
|
||||||
|
action tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogicalIPRule(router adapter.Router, logger log.ContextLogger, options option.LogicalIPRule) (*LogicalIPRule, error) {
|
||||||
|
r := &LogicalIPRule{
|
||||||
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
|
action: tun.ActionType(options.Action),
|
||||||
|
}
|
||||||
|
switch options.Mode {
|
||||||
|
case C.LogicalTypeAnd:
|
||||||
|
r.mode = C.LogicalTypeAnd
|
||||||
|
case C.LogicalTypeOr:
|
||||||
|
r.mode = C.LogicalTypeOr
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||||
|
}
|
||||||
|
for i, subRule := range options.Rules {
|
||||||
|
rule, err := NewDefaultIPRule(router, logger, subRule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||||
|
}
|
||||||
|
r.rules[i] = rule
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LogicalIPRule) Action() tun.ActionType {
|
||||||
|
return r.action
|
||||||
|
}
|
||||||
42
route/rule_item_network.go
Normal file
42
route/rule_item_network.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*NetworkItem)(nil)
|
||||||
|
|
||||||
|
type NetworkItem struct {
|
||||||
|
networks []string
|
||||||
|
networkMap map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetworkItem(networks []string) *NetworkItem {
|
||||||
|
networkMap := make(map[string]bool)
|
||||||
|
for _, network := range networks {
|
||||||
|
networkMap[network] = true
|
||||||
|
}
|
||||||
|
return &NetworkItem{
|
||||||
|
networks: networks,
|
||||||
|
networkMap: networkMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return r.networkMap[metadata.Network]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkItem) String() string {
|
||||||
|
description := "network="
|
||||||
|
|
||||||
|
pLen := len(r.networks)
|
||||||
|
if pLen == 1 {
|
||||||
|
description += F.ToString(r.networks[0])
|
||||||
|
} else {
|
||||||
|
description += "[" + strings.Join(F.MapToString(r.networks), " ") + "]"
|
||||||
|
}
|
||||||
|
return description
|
||||||
|
}
|
||||||
@@ -12,25 +12,17 @@ var _ RuleItem = (*OutboundItem)(nil)
|
|||||||
type OutboundItem struct {
|
type OutboundItem struct {
|
||||||
outbounds []string
|
outbounds []string
|
||||||
outboundMap map[string]bool
|
outboundMap map[string]bool
|
||||||
matchAny bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOutboundRule(outbounds []string) *OutboundItem {
|
func NewOutboundRule(outbounds []string) *OutboundItem {
|
||||||
rule := &OutboundItem{outbounds: outbounds, outboundMap: make(map[string]bool)}
|
rule := &OutboundItem{outbounds, make(map[string]bool)}
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
if outbound == "any" {
|
rule.outboundMap[outbound] = true
|
||||||
rule.matchAny = true
|
|
||||||
} else {
|
|
||||||
rule.outboundMap[outbound] = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return rule
|
return rule
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool {
|
func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
if r.matchAny && metadata.Outbound != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return r.outboundMap[metadata.Outbound]
|
return r.outboundMap[metadata.Outbound]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ RuleItem = (*NetworkItem)(nil)
|
|
||||||
|
|
||||||
type NetworkItem struct {
|
|
||||||
network string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNetworkItem(network string) *NetworkItem {
|
|
||||||
return &NetworkItem{network}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
return r.network == metadata.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *NetworkItem) String() string {
|
|
||||||
return "network=" + r.network
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@@ -94,8 +93,3 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||||||
}()
|
}()
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
v2rayhttp.CloseIdleConnections(c.transport)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ func (c *Client) dialHTTP(ctx context.Context) (net.Conn, error) {
|
|||||||
request = request.WithContext(ctx)
|
request = request.WithContext(ctx)
|
||||||
switch hostLen := len(c.host); hostLen {
|
switch hostLen := len(c.host); hostLen {
|
||||||
case 0:
|
case 0:
|
||||||
request.Host = c.serverAddr.AddrString()
|
|
||||||
case 1:
|
case 1:
|
||||||
request.Host = c.host[0]
|
request.Host = c.host[0]
|
||||||
default:
|
default:
|
||||||
@@ -145,8 +144,6 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
|
|||||||
request = request.WithContext(ctx)
|
request = request.WithContext(ctx)
|
||||||
switch hostLen := len(c.host); hostLen {
|
switch hostLen := len(c.host); hostLen {
|
||||||
case 0:
|
case 0:
|
||||||
// https://github.com/v2fly/v2ray-core/blob/master/transport/internet/http/config.go#L13
|
|
||||||
request.Host = "www.example.com"
|
|
||||||
case 1:
|
case 1:
|
||||||
request.Host = c.host[0]
|
request.Host = c.host[0]
|
||||||
default:
|
default:
|
||||||
@@ -167,8 +164,3 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
|
|||||||
}()
|
}()
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
CloseIdleConnections(c.transport)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package v2rayhttp
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type ConnectionPool interface {
|
|
||||||
CloseIdleConnections()
|
|
||||||
}
|
|
||||||
|
|
||||||
func CloseIdleConnections(transport http.RoundTripper) {
|
|
||||||
if connectionPool, ok := transport.(ConnectionPool); ok {
|
|
||||||
connectionPool.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
package wireguard
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/wireguard-go/tun"
|
wgTun "github.com/sagernet/wireguard-go/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Device interface {
|
type Device interface {
|
||||||
tun.Device
|
wgTun.Device
|
||||||
N.Dialer
|
N.Dialer
|
||||||
Start() error
|
Start() error
|
||||||
|
Inet4Address() netip.Addr
|
||||||
|
Inet6Address() netip.Addr
|
||||||
// NewEndpoint() (stack.LinkEndpoint, error)
|
// NewEndpoint() (stack.LinkEndpoint, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NatDevice interface {
|
||||||
|
Device
|
||||||
|
CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination
|
||||||
|
}
|
||||||
|
|||||||
75
transport/wireguard/device_nat.go
Normal file
75
transport/wireguard/device_nat.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Device = (*natDeviceWrapper)(nil)
|
||||||
|
|
||||||
|
type natDeviceWrapper struct {
|
||||||
|
Device
|
||||||
|
outbound chan *buf.Buffer
|
||||||
|
mapping *tun.NatMapping
|
||||||
|
writer *tun.NatWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNATDevice(upstream Device, ipRewrite bool) NatDevice {
|
||||||
|
wrapper := &natDeviceWrapper{
|
||||||
|
Device: upstream,
|
||||||
|
outbound: make(chan *buf.Buffer, 256),
|
||||||
|
mapping: tun.NewNatMapping(ipRewrite),
|
||||||
|
}
|
||||||
|
if ipRewrite {
|
||||||
|
wrapper.writer = tun.NewNatWriter(upstream.Inet4Address(), upstream.Inet6Address())
|
||||||
|
}
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) Read(p []byte, offset int) (int, error) {
|
||||||
|
select {
|
||||||
|
case packet := <-d.outbound:
|
||||||
|
defer packet.Release()
|
||||||
|
return copy(p[offset:], packet.Bytes()), nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return d.Device.Read(p, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) Write(p []byte, offset int) (int, error) {
|
||||||
|
packet := p[offset:]
|
||||||
|
handled, err := d.mapping.WritePacket(packet)
|
||||||
|
if handled {
|
||||||
|
return len(packet), err
|
||||||
|
}
|
||||||
|
return d.Device.Write(p, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
|
||||||
|
d.mapping.CreateSession(session, conn)
|
||||||
|
return &natDestinationWrapper{d, session}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tun.DirectDestination = (*natDestinationWrapper)(nil)
|
||||||
|
|
||||||
|
type natDestinationWrapper struct {
|
||||||
|
device *natDeviceWrapper
|
||||||
|
session tun.RouteSession
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) WritePacket(buffer *buf.Buffer) error {
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacket(buffer.Bytes())
|
||||||
|
}
|
||||||
|
d.device.outbound <- buffer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) Close() error {
|
||||||
|
d.device.mapping.DeleteSession(d.session)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) Timeout() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
27
transport/wireguard/device_nat_gvisor.go
Normal file
27
transport/wireguard/device_nat_gvisor.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build with_gvisor
|
||||||
|
|
||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) WritePacketBuffer(buffer *stack.PacketBuffer) error {
|
||||||
|
defer buffer.DecRef()
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacketBuffer(buffer)
|
||||||
|
}
|
||||||
|
var packetLen int
|
||||||
|
for _, slice := range buffer.AsSlices() {
|
||||||
|
packetLen += len(slice)
|
||||||
|
}
|
||||||
|
packet := buf.NewSize(packetLen)
|
||||||
|
for _, slice := range buffer.AsSlices() {
|
||||||
|
common.Must1(packet.Write(slice))
|
||||||
|
}
|
||||||
|
d.device.outbound <- packet
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -8,10 +8,12 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/wireguard-go/tun"
|
wgTun "github.com/sagernet/wireguard-go/tun"
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/bufferv2"
|
"gvisor.dev/gvisor/pkg/bufferv2"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
@@ -25,33 +27,38 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Device = (*StackDevice)(nil)
|
var _ NatDevice = (*StackDevice)(nil)
|
||||||
|
|
||||||
const defaultNIC tcpip.NICID = 1
|
const defaultNIC tcpip.NICID = 1
|
||||||
|
|
||||||
type StackDevice struct {
|
type StackDevice struct {
|
||||||
stack *stack.Stack
|
stack *stack.Stack
|
||||||
mtu uint32
|
mtu uint32
|
||||||
events chan tun.Event
|
events chan wgTun.Event
|
||||||
outbound chan *stack.PacketBuffer
|
outbound chan *stack.PacketBuffer
|
||||||
done chan struct{}
|
packetOutbound chan *buf.Buffer
|
||||||
dispatcher stack.NetworkDispatcher
|
done chan struct{}
|
||||||
addr4 tcpip.Address
|
dispatcher stack.NetworkDispatcher
|
||||||
addr6 tcpip.Address
|
addr4 tcpip.Address
|
||||||
|
addr6 tcpip.Address
|
||||||
|
mapping *tun.NatMapping
|
||||||
|
writer *tun.NatWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, error) {
|
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (*StackDevice, error) {
|
||||||
ipStack := stack.New(stack.Options{
|
ipStack := stack.New(stack.Options{
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
|
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
|
||||||
HandleLocal: true,
|
HandleLocal: true,
|
||||||
})
|
})
|
||||||
tunDevice := &StackDevice{
|
tunDevice := &StackDevice{
|
||||||
stack: ipStack,
|
stack: ipStack,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
events: make(chan tun.Event, 1),
|
events: make(chan wgTun.Event, 1),
|
||||||
outbound: make(chan *stack.PacketBuffer, 256),
|
outbound: make(chan *stack.PacketBuffer, 256),
|
||||||
done: make(chan struct{}),
|
packetOutbound: make(chan *buf.Buffer, 256),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
mapping: tun.NewNatMapping(ipRewrite),
|
||||||
}
|
}
|
||||||
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
|
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,6 +84,9 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, er
|
|||||||
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
|
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ipRewrite {
|
||||||
|
tunDevice.writer = tun.NewNatWriter(tunDevice.Inet4Address(), tunDevice.Inet6Address())
|
||||||
|
}
|
||||||
sOpt := tcpip.TCPSACKEnabled(true)
|
sOpt := tcpip.TCPSACKEnabled(true)
|
||||||
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
|
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
|
||||||
cOpt := tcpip.CongestionControlOption("cubic")
|
cOpt := tcpip.CongestionControlOption("cubic")
|
||||||
@@ -144,8 +154,16 @@ func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
return udpConn, nil
|
return udpConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *StackDevice) Inet4Address() netip.Addr {
|
||||||
|
return M.AddrFromIP(net.IP(w.addr4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *StackDevice) Inet6Address() netip.Addr {
|
||||||
|
return M.AddrFromIP(net.IP(w.addr6))
|
||||||
|
}
|
||||||
|
|
||||||
func (w *StackDevice) Start() error {
|
func (w *StackDevice) Start() error {
|
||||||
w.events <- tun.EventUp
|
w.events <- wgTun.EventUp
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +183,10 @@ func (w *StackDevice) Read(p []byte, offset int) (n int, err error) {
|
|||||||
n += copy(p[n:], slice)
|
n += copy(p[n:], slice)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
case packet := <-w.packetOutbound:
|
||||||
|
defer packet.Release()
|
||||||
|
n = copy(p[offset:], packet.Bytes())
|
||||||
|
return
|
||||||
case <-w.done:
|
case <-w.done:
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
@@ -175,6 +197,10 @@ func (w *StackDevice) Write(p []byte, offset int) (n int, err error) {
|
|||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
handled, err := w.mapping.WritePacket(p)
|
||||||
|
if handled {
|
||||||
|
return len(p), err
|
||||||
|
}
|
||||||
var networkProtocol tcpip.NetworkProtocolNumber
|
var networkProtocol tcpip.NetworkProtocolNumber
|
||||||
switch header.IPVersion(p) {
|
switch header.IPVersion(p) {
|
||||||
case header.IPv4Version:
|
case header.IPv4Version:
|
||||||
@@ -203,7 +229,7 @@ func (w *StackDevice) Name() (string, error) {
|
|||||||
return "sing-box", nil
|
return "sing-box", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *StackDevice) Events() chan tun.Event {
|
func (w *StackDevice) Events() chan wgTun.Event {
|
||||||
return w.events
|
return w.events
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +248,44 @@ func (w *StackDevice) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *StackDevice) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
|
||||||
|
w.mapping.CreateSession(session, conn)
|
||||||
|
return &stackNatDestination{
|
||||||
|
device: w,
|
||||||
|
session: session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stackNatDestination struct {
|
||||||
|
device *StackDevice
|
||||||
|
session tun.RouteSession
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) WritePacket(buffer *buf.Buffer) error {
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacket(buffer.Bytes())
|
||||||
|
}
|
||||||
|
d.device.packetOutbound <- buffer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) WritePacketBuffer(buffer *stack.PacketBuffer) error {
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacketBuffer(buffer)
|
||||||
|
}
|
||||||
|
d.device.outbound <- buffer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) Close() error {
|
||||||
|
d.device.mapping.DeleteSession(d.session)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) Timeout() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
||||||
|
|
||||||
type wireEndpoint StackDevice
|
type wireEndpoint StackDevice
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ import (
|
|||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) {
|
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (Device, error) {
|
||||||
return nil, tun.ErrGVisorNotIncluded
|
return nil, tun.ErrGVisorNotIncluded
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ type SystemDevice struct {
|
|||||||
name string
|
name string
|
||||||
mtu int
|
mtu int
|
||||||
events chan wgTun.Event
|
events chan wgTun.Event
|
||||||
|
addr4 netip.Addr
|
||||||
|
addr6 netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func (w *SystemDevice) NewEndpoint() (stack.LinkEndpoint, error) {
|
/*func (w *SystemDevice) NewEndpoint() (stack.LinkEndpoint, error) {
|
||||||
@@ -55,11 +57,24 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var inet4Address netip.Addr
|
||||||
|
var inet6Address netip.Addr
|
||||||
|
if len(inet4Addresses) > 0 {
|
||||||
|
inet4Address = inet4Addresses[0].Addr()
|
||||||
|
}
|
||||||
|
if len(inet6Addresses) > 0 {
|
||||||
|
inet6Address = inet6Addresses[0].Addr()
|
||||||
|
}
|
||||||
return &SystemDevice{
|
return &SystemDevice{
|
||||||
dialer.NewDefault(router, option.DialerOptions{
|
dialer: dialer.NewDefault(router, option.DialerOptions{
|
||||||
BindInterface: interfaceName,
|
BindInterface: interfaceName,
|
||||||
}),
|
}),
|
||||||
tunInterface, interfaceName, int(mtu), make(chan wgTun.Event),
|
device: tunInterface,
|
||||||
|
name: interfaceName,
|
||||||
|
mtu: int(mtu),
|
||||||
|
events: make(chan wgTun.Event),
|
||||||
|
addr4: inet4Address,
|
||||||
|
addr6: inet6Address,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +86,14 @@ func (w *SystemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr
|
|||||||
return w.dialer.ListenPacket(ctx, destination)
|
return w.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *SystemDevice) Inet4Address() netip.Addr {
|
||||||
|
return w.addr4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SystemDevice) Inet6Address() netip.Addr {
|
||||||
|
return w.addr6
|
||||||
|
}
|
||||||
|
|
||||||
func (w *SystemDevice) Start() error {
|
func (w *SystemDevice) Start() error {
|
||||||
w.events <- wgTun.EventUp
|
w.events <- wgTun.EventUp
|
||||||
return nil
|
return nil
|
||||||
@@ -80,12 +103,12 @@ func (w *SystemDevice) File() *os.File {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SystemDevice) Read(bytes []byte, index int) (int, error) {
|
func (w *SystemDevice) Read(p []byte, offset int) (int, error) {
|
||||||
return w.device.Read(bytes[index-tun.PacketOffset:])
|
return w.device.Read(p[offset-tun.PacketOffset:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SystemDevice) Write(bytes []byte, index int) (int, error) {
|
func (w *SystemDevice) Write(p []byte, offset int) (int, error) {
|
||||||
return w.device.Write(bytes[index:])
|
return w.device.Write(p[offset:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SystemDevice) Flush() error {
|
func (w *SystemDevice) Flush() error {
|
||||||
|
|||||||
Reference in New Issue
Block a user