Projects STRLCPY sing-box Commits 41e1e48a
🤬
  • ■ ■ ■ ■
    adapter/inbound.go
    skipped 26 lines
    27 27  type InboundContext struct {
    28 28   Inbound string
    29 29   InboundType string
    30  - IPVersion int
     30 + IPVersion uint8
    31 31   Network string
    32 32   Source M.Socksaddr
    33 33   Destination M.Socksaddr
    skipped 44 lines
  • ■ ■ ■ ■ ■ ■
    adapter/outbound.go
    skipped 3 lines
    4 4   "context"
    5 5   "net"
    6 6   
     7 + "github.com/sagernet/sing-tun"
    7 8   N "github.com/sagernet/sing/common/network"
    8 9  )
    9 10   
    skipped 8 lines
    18 19   NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
    19 20  }
    20 21   
     22 +type IPOutbound interface {
     23 + Outbound
     24 + NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
     25 +}
     26 + 
  • ■ ■ ■ ■ ■ ■
    adapter/router.go
    skipped 22 lines
    23 23   
    24 24   RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
    25 25   RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
     26 + RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
     27 + 
     28 + NatRequired(outbound string) bool
    26 29   
    27 30   GeoIPReader() *geoip.Reader
    28 31   LoadGeosite(code string) (Rule, error)
    skipped 10 lines
    39 42   NetworkMonitor() tun.NetworkUpdateMonitor
    40 43   InterfaceMonitor() tun.DefaultInterfaceMonitor
    41 44   PackageManager() tun.PackageManager
     45 + 
    42 46   Rules() []Rule
     47 + IPRules() []IPRule
    43 48   
    44 49   TimeService
    45 50   
    skipped 30 lines
    76 81  type DNSRule interface {
    77 82   Rule
    78 83   DisableCache() bool
     84 + RewriteTTL() *uint32
     85 +}
     86 + 
     87 +type IPRule interface {
     88 + Rule
     89 + Action() tun.ActionType
    79 90  }
    80 91   
    81 92  type InterfaceUpdateListener interface {
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    common/badjsonmerge/merge_test.go
    skipped 20 lines
    21 21   {
    22 22   Type: C.RuleTypeDefault,
    23 23   DefaultOptions: option.DefaultRule{
    24  - Network: N.NetworkTCP,
     24 + Network: []string{N.NetworkTCP},
    25 25   Outbound: "direct",
    26 26   },
    27 27   },
    skipped 14 lines
    42 42   {
    43 43   Type: C.RuleTypeDefault,
    44 44   DefaultOptions: option.DefaultRule{
    45  - Network: N.NetworkUDP,
     45 + Network: []string{N.NetworkUDP},
    46 46   Outbound: "direct",
    47 47   },
    48 48   },
    skipped 12 lines
  • ■ ■ ■ ■ ■
    common/dialer/tfo.go
    skipped 26 lines
    27 27   
    28 28  func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
    29 29   if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
    30  - return dialer.DialContext(ctx, network, destination.String(), nil)
     30 + switch N.NetworkName(network) {
     31 + case N.NetworkTCP, N.NetworkUDP:
     32 + return dialer.Dialer.DialContext(ctx, network, destination.String())
     33 + default:
     34 + return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
     35 + }
    31 36   }
    32 37   return &slowOpenConn{
    33 38   dialer: dialer,
    skipped 113 lines
  • ■ ■ ■ ■ ■ ■
    docs/configuration/outbound/index.md
    skipped 37 lines
    38 38  #### tag
    39 39   
    40 40  The tag of the outbound.
     41 + 
     42 +### Features
     43 + 
     44 +#### Outbounds that support IP connection
     45 + 
     46 +* `WireGuard`
     47 + 
  • ■ ■ ■ ■ ■ ■
    docs/configuration/outbound/index.zh.md
    skipped 36 lines
    37 37  #### tag
    38 38   
    39 39  出站的标签。
     40 + 
     41 +### 特性
     42 + 
     43 +#### 支持 IP 连接的出站
     44 + 
     45 +* `WireGuard`
     46 + 
  • ■ ■ ■ ■ ■ ■
    docs/configuration/route/index.md
    skipped 6 lines
    7 7   "route": {
    8 8   "geoip": {},
    9 9   "geosite": {},
     10 + "ip_rules": [],
    10 11   "rules": [],
    11 12   "final": "",
    12 13   "auto_detect_interface": false,
    skipped 6 lines
    19 20   
    20 21  ### Fields
    21 22   
    22  -| Key | Format |
    23  -|-----------|------------------------------|
    24  -| `geoip` | [GeoIP](./geoip) |
    25  -| `geosite` | [Geosite](./geosite) |
    26  -| `rules` | List of [Route Rule](./rule) |
     23 +| Key | Format |
     24 +|------------|------------------------------------|
     25 +| `geoip` | [GeoIP](./geoip) |
     26 +| `geosite` | [Geosite](./geosite) |
     27 +| `ip_rules` | List of [IP Route Rule](./ip-rule) |
     28 +| `rules` | List of [Route Rule](./rule) |
    27 29   
    28 30  #### final
    29 31   
    skipped 39 lines
  • ■ ■ ■ ■ ■ ■
    docs/configuration/route/index.zh.md
    skipped 6 lines
    7 7   "route": {
    8 8   "geoip": {},
    9 9   "geosite": {},
     10 + "ip_rules": [],
    10 11   "rules": [],
    11 12   "final": "",
    12 13   "auto_detect_interface": false,
    skipped 6 lines
    19 20   
    20 21  ### 字段
    21 22   
    22  -| 键 | 格式 |
    23  -|-----------|----------------------|
    24  -| `geoip` | [GeoIP](./geoip) |
    25  -| `geosite` | [GeoSite](./geosite) |
    26  -| `rules` | 一组 [路由规则](./rule) |
     23 +| 键 | 格式 |
     24 +|------------|-------------------------|
     25 +| `geoip` | [GeoIP](./geoip) |
     26 +| `geosite` | [GeoSite](./geosite) |
     27 +| `ip_rules` | 一组 [IP 路由规则](./ip-rule) |
     28 +| `rules` | 一组 [路由规则](./rule) |
    27 29   
    28 30  #### final
    29 31   
    skipped 36 lines
    66 68  默认为出站连接设置路由标记。
    67 69   
    68 70  如果设置了 `outbound.routing_mark` 设置,则不生效。
    69  - 
  • ■ ■ ■ ■ ■ ■
    docs/configuration/route/ip-rule.md
     1 +### Structure
     2 + 
     3 +```json
     4 +{
     5 + "route": {
     6 + "rules": [
     7 + {
     8 + "inbound": [
     9 + "mixed-in"
     10 + ],
     11 + "ip_version": 6,
     12 + "network": [
     13 + "tcp"
     14 + ],
     15 + "domain": [
     16 + "test.com"
     17 + ],
     18 + "domain_suffix": [
     19 + ".cn"
     20 + ],
     21 + "domain_keyword": [
     22 + "test"
     23 + ],
     24 + "domain_regex": [
     25 + "^stun\\..+"
     26 + ],
     27 + "geosite": [
     28 + "cn"
     29 + ],
     30 + "source_geoip": [
     31 + "private"
     32 + ],
     33 + "geoip": [
     34 + "cn"
     35 + ],
     36 + "source_ip_cidr": [
     37 + "10.0.0.0/24",
     38 + "192.168.0.1"
     39 + ],
     40 + "ip_cidr": [
     41 + "10.0.0.0/24",
     42 + "192.168.0.1"
     43 + ],
     44 + "source_port": [
     45 + 12345
     46 + ],
     47 + "source_port_range": [
     48 + "1000:2000",
     49 + ":3000",
     50 + "4000:"
     51 + ],
     52 + "port": [
     53 + 80,
     54 + 443
     55 + ],
     56 + "port_range": [
     57 + "1000:2000",
     58 + ":3000",
     59 + "4000:"
     60 + ],
     61 + "invert": false,
     62 + "action": "direct",
     63 + "outbound": "wireguard"
     64 + },
     65 + {
     66 + "type": "logical",
     67 + "mode": "and",
     68 + "rules": [],
     69 + "invert": false,
     70 + "action": "direct",
     71 + "outbound": "wireguard"
     72 + }
     73 + ]
     74 + }
     75 +}
     76 + 
     77 +```
     78 + 
     79 +!!! note ""
     80 + 
     81 + You can ignore the JSON Array [] tag when the content is only one item
     82 + 
     83 +### Default Fields
     84 + 
     85 +!!! note ""
     86 + 
     87 + The default rule uses the following matching logic:
     88 + (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
     89 + (`port` || `port_range`) &&
     90 + (`source_geoip` || `source_ip_cidr`) &&
     91 + (`source_port` || `source_port_range`) &&
     92 + `other fields`
     93 + 
     94 +#### inbound
     95 + 
     96 +Tags of [Inbound](/configuration/inbound).
     97 + 
     98 +#### ip_version
     99 + 
     100 +4 or 6.
     101 + 
     102 +Not limited if empty.
     103 + 
     104 +#### network
     105 + 
     106 +Match network protocol.
     107 + 
     108 +Available values:
     109 + 
     110 +* `tcp`
     111 +* `udp`
     112 +* `icmpv4`
     113 +* `icmpv6`
     114 + 
     115 +#### domain
     116 + 
     117 +Match full domain.
     118 + 
     119 +#### domain_suffix
     120 + 
     121 +Match domain suffix.
     122 + 
     123 +#### domain_keyword
     124 + 
     125 +Match domain using keyword.
     126 + 
     127 +#### domain_regex
     128 + 
     129 +Match domain using regular expression.
     130 + 
     131 +#### geosite
     132 + 
     133 +Match geosite.
     134 + 
     135 +#### source_geoip
     136 + 
     137 +Match source geoip.
     138 + 
     139 +#### geoip
     140 + 
     141 +Match geoip.
     142 + 
     143 +#### source_ip_cidr
     144 + 
     145 +Match source ip cidr.
     146 + 
     147 +#### ip_cidr
     148 + 
     149 +Match ip cidr.
     150 + 
     151 +#### source_port
     152 + 
     153 +Match source port.
     154 + 
     155 +#### source_port_range
     156 + 
     157 +Match source port range.
     158 + 
     159 +#### port
     160 + 
     161 +Match port.
     162 + 
     163 +#### port_range
     164 + 
     165 +Match port range.
     166 + 
     167 +#### invert
     168 + 
     169 +Invert match result.
     170 + 
     171 +#### action
     172 + 
     173 +==Required==
     174 + 
     175 +| Action | Description |
     176 +|--------|--------------------------------------------------------------------|
     177 +| return | Stop IP routing and assemble the connection to the transport layer |
     178 +| block | Block the connection |
     179 +| direct | Directly forward the connection |
     180 + 
     181 +#### outbound
     182 + 
     183 +==Required if action is direct==
     184 + 
     185 +Tag of the target outbound.
     186 + 
     187 +Only outbound which supports IP connection can be used, see [Outbounds that support IP connection](/configuration/outbound/#outbounds-that-support-ip-connection).
     188 + 
     189 +### Logical Fields
     190 + 
     191 +#### type
     192 + 
     193 +`logical`
     194 + 
     195 +#### mode
     196 + 
     197 +==Required==
     198 + 
     199 +`and` or `or`
     200 + 
     201 +#### rules
     202 + 
     203 +==Required==
     204 + 
     205 +Included default rules.
  • ■ ■ ■ ■ ■ ■
    docs/configuration/route/ip-rule.zh.md
     1 +### 结构
     2 + 
     3 +```json
     4 +{
     5 + "route": {
     6 + "rules": [
     7 + {
     8 + "inbound": [
     9 + "mixed-in"
     10 + ],
     11 + "ip_version": 6,
     12 + "network": [
     13 + "tcp"
     14 + ],
     15 + "domain": [
     16 + "test.com"
     17 + ],
     18 + "domain_suffix": [
     19 + ".cn"
     20 + ],
     21 + "domain_keyword": [
     22 + "test"
     23 + ],
     24 + "domain_regex": [
     25 + "^stun\\..+"
     26 + ],
     27 + "geosite": [
     28 + "cn"
     29 + ],
     30 + "source_geoip": [
     31 + "private"
     32 + ],
     33 + "geoip": [
     34 + "cn"
     35 + ],
     36 + "source_ip_cidr": [
     37 + "10.0.0.0/24",
     38 + "192.168.0.1"
     39 + ],
     40 + "ip_cidr": [
     41 + "10.0.0.0/24",
     42 + "192.168.0.1"
     43 + ],
     44 + "source_port": [
     45 + 12345
     46 + ],
     47 + "source_port_range": [
     48 + "1000:2000",
     49 + ":3000",
     50 + "4000:"
     51 + ],
     52 + "port": [
     53 + 80,
     54 + 443
     55 + ],
     56 + "port_range": [
     57 + "1000:2000",
     58 + ":3000",
     59 + "4000:"
     60 + ],
     61 + "invert": false,
     62 + "action": "direct",
     63 + "outbound": "wireguard"
     64 + },
     65 + {
     66 + "type": "logical",
     67 + "mode": "and",
     68 + "rules": [],
     69 + "invert": false,
     70 + "action": "direct",
     71 + "outbound": "wireguard"
     72 + }
     73 + ]
     74 + }
     75 +}
     76 + 
     77 +```
     78 + 
     79 +!!! note ""
     80 + 
     81 + 当内容只有一项时,可以忽略 JSON 数组 [] 标签。
     82 + 
     83 +### Default Fields
     84 + 
     85 +!!! note ""
     86 + 
     87 + 默认规则使用以下匹配逻辑:
     88 + (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
     89 + (`port` || `port_range`) &&
     90 + (`source_geoip` || `source_ip_cidr`) &&
     91 + (`source_port` || `source_port_range`) &&
     92 + `other fields`
     93 + 
     94 +#### inbound
     95 + 
     96 +[入站](/zh/configuration/inbound) 标签。
     97 + 
     98 +#### ip_version
     99 + 
     100 +4 或 6。
     101 + 
     102 +默认不限制。
     103 + 
     104 +#### network
     105 + 
     106 +匹配网络协议。
     107 + 
     108 +可用值:
     109 + 
     110 +* `tcp`
     111 +* `udp`
     112 +* `icmpv4`
     113 +* `icmpv6`
     114 + 
     115 +#### domain
     116 + 
     117 +匹配完整域名。
     118 + 
     119 +#### domain_suffix
     120 + 
     121 +匹配域名后缀。
     122 + 
     123 +#### domain_keyword
     124 + 
     125 +匹配域名关键字。
     126 + 
     127 +#### domain_regex
     128 + 
     129 +匹配域名正则表达式。
     130 + 
     131 +#### geosite
     132 + 
     133 +匹配 GeoSite。
     134 + 
     135 +#### source_geoip
     136 + 
     137 +匹配源 GeoIP。
     138 + 
     139 +#### geoip
     140 + 
     141 +匹配 GeoIP。
     142 + 
     143 +#### source_ip_cidr
     144 + 
     145 +匹配源 IP CIDR。
     146 + 
     147 +#### ip_cidr
     148 + 
     149 +匹配 IP CIDR。
     150 + 
     151 +#### source_port
     152 + 
     153 +匹配源端口。
     154 + 
     155 +#### source_port_range
     156 + 
     157 +匹配源端口范围。
     158 + 
     159 +#### port
     160 + 
     161 +匹配端口。
     162 + 
     163 +#### port_range
     164 + 
     165 +匹配端口范围。
     166 + 
     167 +#### invert
     168 + 
     169 +反选匹配结果。
     170 + 
     171 +#### action
     172 + 
     173 +==必填==
     174 + 
     175 +| Action | 描述 |
     176 +|--------|---------------------|
     177 +| return | 停止 IP 路由并将该连接组装到传输层 |
     178 +| block | 屏蔽该连接 |
     179 +| direct | 直接转发该连接 |
     180 + 
     181 + 
     182 +#### outbound
     183 + 
     184 +==action 为 direct 则必填==
     185 + 
     186 +目标出站的标签。
     187 + 
     188 +### 逻辑字段
     189 + 
     190 +#### type
     191 + 
     192 +`logical`
     193 + 
     194 +#### mode
     195 + 
     196 +==必填==
     197 + 
     198 +`and` 或 `or`
     199 + 
     200 +#### rules
     201 + 
     202 +==必填==
     203 + 
     204 +包括的默认规则。
  • ■ ■ ■ ■ ■ ■
    docs/configuration/route/rule.md
    skipped 8 lines
    9 9   "mixed-in"
    10 10   ],
    11 11   "ip_version": 6,
    12  - "network": "tcp",
     12 + "network": [
     13 + "tcp"
     14 + ],
    13 15   "auth_user": [
    14 16   "usera",
    15 17   "userb"
    skipped 228 lines
    244 246   
    245 247  #### mode
    246 248   
     249 +==Required==
     250 + 
    247 251  `and` or `or`
    248 252   
    249 253  #### rules
    250 254   
    251  -Included default rules.
    252  - 
    253  -#### invert
    254  - 
    255  -Invert match result.
    256  - 
    257  -#### outbound
    258  - 
    259 255  ==Required==
    260 256   
    261  -Tag of the target outbound.
     257 +Included default rules.
    262 258   
  • ■ ■ ■ ■ ■ ■
    docs/configuration/route/rule.zh.md
    skipped 8 lines
    9 9   "mixed-in"
    10 10   ],
    11 11   "ip_version": 6,
    12  - "network": "tcp",
     12 + "network": [
     13 + "tcp"
     14 + ],
    13 15   "auth_user": [
    14 16   "usera",
    15 17   "userb"
    skipped 226 lines
    242 244   
    243 245  #### mode
    244 246   
     247 +==必填==
     248 + 
    245 249  `and` 或 `or`
    246 250   
    247 251  #### rules
    248 252   
    249  -包括的默认规则。
    250  - 
    251  -#### invert
    252  - 
    253  -反选匹配结果。
    254  - 
    255  -#### outbound
    256  - 
    257 253  ==必填==
    258 254   
    259  -目标出站的标签。
    260  - 
     255 +包括的默认规则。
  • ■ ■ ■ ■ ■
    docs/examples/index.md
    skipped 7 lines
    8 8  * [Shadowsocks](./shadowsocks)
    9 9  * [ShadowTLS](./shadowtls)
    10 10  * [Clash API](./clash-api)
     11 +* [WireGuard Direct](./wireguard-direct)
    11 12   
  • ■ ■ ■ ■ ■
    docs/examples/index.zh.md
    skipped 7 lines
    8 8  * [Shadowsocks](./shadowsocks)
    9 9  * [ShadowTLS](./shadowtls)
    10 10  * [Clash API](./clash-api)
     11 +* [WireGuard Direct](./wireguard-direct)
    11 12   
  • ■ ■ ■ ■ ■ ■
    docs/examples/wireguard-direct.md
     1 +# WireGuard Direct
     2 + 
     3 +```json
     4 +{
     5 + "dns": {
     6 + "servers": [
     7 + {
     8 + "tag": "google",
     9 + "address": "tls://8.8.8.8"
     10 + },
     11 + {
     12 + "tag": "local",
     13 + "address": "223.5.5.5",
     14 + "detour": "direct"
     15 + }
     16 + ],
     17 + "rules": [
     18 + {
     19 + "geoip": "cn",
     20 + "server": "direct"
     21 + }
     22 + ],
     23 + "reverse_mapping": true
     24 + },
     25 + "inbounds": [
     26 + {
     27 + "type": "tun",
     28 + "tag": "tun",
     29 + "inet4_address": "172.19.0.1/30",
     30 + "auto_route": true,
     31 + "sniff": true,
     32 + "stack": "system"
     33 + }
     34 + ],
     35 + "outbounds": [
     36 + {
     37 + "type": "wireguard",
     38 + "tag": "wg",
     39 + "server": "127.0.0.1",
     40 + "server_port": 2345,
     41 + "local_address": [
     42 + "172.19.0.1/128"
     43 + ],
     44 + "private_key": "KLTnpPY03pig/WC3zR8U7VWmpANHPFh2/4pwICGJ5Fk=",
     45 + "peer_public_key": "uvNabcamf6Rs0vzmcw99jsjTJbxo6eWGOykSY66zsUk="
     46 + },
     47 + {
     48 + "type": "dns",
     49 + "tag": "dns"
     50 + },
     51 + {
     52 + "type": "direct",
     53 + "tag": "direct"
     54 + },
     55 + {
     56 + "type": "block",
     57 + "tag": "block"
     58 + }
     59 + ],
     60 + "route": {
     61 + "ip_rules": [
     62 + {
     63 + "port": 53,
     64 + "action": "return"
     65 + },
     66 + {
     67 + "geoip": "cn",
     68 + "geosite": "cn",
     69 + "action": "return"
     70 + },
     71 + {
     72 + "action": "direct",
     73 + "outbound": "wg"
     74 + }
     75 + ],
     76 + "rules": [
     77 + {
     78 + "protocol": "dns",
     79 + "outbound": "dns"
     80 + },
     81 + {
     82 + "geoip": "cn",
     83 + "geosite": "cn",
     84 + "outbound": "direct"
     85 + }
     86 + ],
     87 + "auto_detect_interface": true
     88 + }
     89 +}
     90 +```
  • ■ ■ ■ ■ ■ ■
    go.mod
    skipped 24 lines
    25 25   github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
    26 26   github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
    27 27   github.com/sagernet/reality v0.0.0-20230323230523-5fa25e693e7f
    28  - github.com/sagernet/sing v0.2.1-0.20230323071235-f8038854d286
    29  - github.com/sagernet/sing-dns v0.1.4
     28 + github.com/sagernet/sing v0.2.1
     29 + github.com/sagernet/sing-dns v0.1.5-0.20230324014656-cc070d645ee7
    30 30   github.com/sagernet/sing-shadowsocks v0.2.0
    31 31   github.com/sagernet/sing-shadowtls v0.1.0
    32  - github.com/sagernet/sing-tun v0.1.3-0.20230323073325-35d565af6515
     32 + github.com/sagernet/sing-tun v0.1.4-0.20230324082220-ca53ccf346b5
    33 33   github.com/sagernet/sing-vmess v0.1.3
    34 34   github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
    35 35   github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
    skipped 60 lines
  • ■ ■ ■ ■ ■ ■
    go.sum
    skipped 110 lines
    111 111  github.com/sagernet/reality v0.0.0-20230323230523-5fa25e693e7f/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
    112 112  github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
    113 113  github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
    114  -github.com/sagernet/sing v0.2.1-0.20230323071235-f8038854d286 h1:0Td2b5l1KgrdlOnbRWgFFWsyb0TLoq/tP6j9Lut4JN0=
    115  -github.com/sagernet/sing v0.2.1-0.20230323071235-f8038854d286/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
    116  -github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
    117  -github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
     114 +github.com/sagernet/sing v0.2.1 h1:r0STYeyfKBBtoAHsBtW1dQonxG+3Qidde7/1VAMhdn8=
     115 +github.com/sagernet/sing v0.2.1/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
     116 +github.com/sagernet/sing-dns v0.1.5-0.20230324014656-cc070d645ee7 h1:+Ym0IAbyJKZ6fV+PBEXXjC+RyZF5/bI69qe2b0C5i0g=
     117 +github.com/sagernet/sing-dns v0.1.5-0.20230324014656-cc070d645ee7/go.mod h1:Ym+EQTT0AWzMkbXQSO+R4Qxvo4d5AZPv02R+qYlu/Fg=
    118 118  github.com/sagernet/sing-shadowsocks v0.2.0 h1:ILDWL7pwWfkPLEbviE/MyCgfjaBmJY/JVVY+5jhSb58=
    119 119  github.com/sagernet/sing-shadowsocks v0.2.0/go.mod h1:ysYzszRLpNzJSorvlWRMuzU6Vchsp7sd52q+JNY4axw=
    120 120  github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
    121 121  github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
    122  -github.com/sagernet/sing-tun v0.1.3-0.20230323073325-35d565af6515 h1:r25BJqn3o34g+bDdhoRU65zimKPfGCTv7nHuysyJojo=
    123  -github.com/sagernet/sing-tun v0.1.3-0.20230323073325-35d565af6515/go.mod h1:1xzFt4zJ7CzXdbgPcy7+Lsg4ypZo0ivDNZ0oQdL3zEY=
     122 +github.com/sagernet/sing-tun v0.1.4-0.20230324082220-ca53ccf346b5 h1:gPzTJj6eJqEn8q03eBh2PNvueRqWq1UAgtKwtWkwmIU=
     123 +github.com/sagernet/sing-tun v0.1.4-0.20230324082220-ca53ccf346b5/go.mod h1:4YxIDEkkCjGXDOTMPw1SXpLmCQUFAWuaQN250oo+928=
    124 124  github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
    125 125  github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
    126 126  github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
    skipped 117 lines
  • ■ ■ ■ ■ ■
    inbound/tun.go
    skipped 18 lines
    19 19   "github.com/sagernet/sing/common/ranges"
    20 20  )
    21 21   
    22  -var _ adapter.Inbound = (*Tun)(nil)
     22 +var (
     23 + _ adapter.Inbound = (*Tun)(nil)
     24 + _ tun.Router = (*Tun)(nil)
     25 +)
    23 26   
    24 27  type Tun struct {
    25 28   tag string
    skipped 12 lines
    38 41  }
    39 42   
    40 43  func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
    41  - tunName := options.InterfaceName
    42  - if tunName == "" {
    43  - tunName = tun.CalculateInterfaceName("")
    44  - }
    45 44   tunMTU := options.MTU
    46 45   if tunMTU == 0 {
    47 46   tunMTU = 9000
    skipped 27 lines
    75 74   logger: logger,
    76 75   inboundOptions: options.InboundOptions,
    77 76   tunOptions: tun.Options{
    78  - Name: tunName,
     77 + Name: options.InterfaceName,
    79 78   MTU: tunMTU,
    80 79   Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
    81 80   Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
    skipped 59 lines
    141 140   
    142 141  func (t *Tun) Start() error {
    143 142   if C.IsAndroid && t.platformInterface == nil {
     143 + t.logger.Trace("building android rules")
    144 144   t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
     145 + }
     146 + if t.tunOptions.Name == "" {
     147 + t.tunOptions.Name = tun.CalculateInterfaceName("")
    145 148   }
    146 149   var (
    147 150   tunInterface tun.Tun
    148 151   err error
    149 152   )
     153 + t.logger.Trace("opening interface")
    150 154   if t.platformInterface != nil {
    151 155   tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
    152 156   } else {
    skipped 2 lines
    155 159   if err != nil {
    156 160   return E.Cause(err, "configure tun interface")
    157 161   }
     162 + t.logger.Trace("creating stack")
    158 163   t.tunIf = tunInterface
     164 + var tunRouter tun.Router
     165 + if len(t.router.IPRules()) > 0 {
     166 + tunRouter = t
     167 + }
    159 168   t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
    160 169   Context: t.ctx,
    161 170   Tun: tunInterface,
    skipped 3 lines
    165 174   Inet6Address: t.tunOptions.Inet6Address,
    166 175   EndpointIndependentNat: t.endpointIndependentNat,
    167 176   UDPTimeout: t.udpTimeout,
     177 + Router: tunRouter,
    168 178   Handler: t,
    169 179   Logger: t.logger,
    170 180   UnderPlatform: t.platformInterface != nil,
    skipped 1 lines
    172 182   if err != nil {
    173 183   return err
    174 184   }
     185 + t.logger.Trace("starting stack")
    175 186   err = t.tunStack.Start()
    176 187   if err != nil {
    177 188   return err
    skipped 7 lines
    185 196   t.tunStack,
    186 197   t.tunIf,
    187 198   )
     199 +}
     200 + 
     201 +func (t *Tun) RouteConnection(session tun.RouteSession, conn tun.RouteContext) tun.RouteAction {
     202 + ctx := log.ContextWithNewID(t.ctx)
     203 + var metadata adapter.InboundContext
     204 + metadata.Inbound = t.tag
     205 + metadata.InboundType = C.TypeTun
     206 + metadata.IPVersion = session.IPVersion
     207 + metadata.Network = tun.NetworkName(session.Network)
     208 + metadata.Source = M.SocksaddrFromNetIP(session.Source)
     209 + metadata.Destination = M.SocksaddrFromNetIP(session.Destination)
     210 + metadata.InboundOptions = t.inboundOptions
     211 + t.logger.DebugContext(ctx, "incoming connection from ", metadata.Source)
     212 + t.logger.DebugContext(ctx, "incoming connection to ", metadata.Destination)
     213 + return t.router.RouteIPConnection(ctx, conn, metadata)
    188 214  }
    189 215   
    190 216  func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
    skipped 37 lines
  • ■ ■ ■ ■ ■
    mkdocs.yml
    skipped 38 lines
    39 39   - From source: installation/from-source.md
    40 40   - Clients:
    41 41   - SFI:
    42  - - installation/clients/sfi/index.md
     42 + - installation/clients/sfi/index.md
    43 43   - Configuration:
    44 44   - configuration/index.md
    45 45   - Log:
    skipped 8 lines
    54 54   - configuration/route/index.md
    55 55   - GeoIP: configuration/route/geoip.md
    56 56   - Geosite: configuration/route/geosite.md
     57 + - IP Route Rule: configuration/route/ip-rule.md
    57 58   - Route Rule: configuration/route/rule.md
    58 59   - Protocol Sniff: configuration/route/sniff.md
    59 60   - Experimental:
    skipped 109 lines
    169 170   DNS Rule: DNS 规则
    170 171   
    171 172   Route: 路由
     173 + IP Route Rule: IP 路由规则
    172 174   Route Rule: 路由规则
    173 175   Protocol Sniff: 协议探测
    174 176   
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    option/dns.go
    1 1  package option
    2 2   
    3  -import (
    4  - "reflect"
    5  - 
    6  - "github.com/sagernet/sing-box/common/json"
    7  - C "github.com/sagernet/sing-box/constant"
    8  - "github.com/sagernet/sing/common"
    9  - E "github.com/sagernet/sing/common/exceptions"
    10  -)
    11  - 
    12 3  type DNSOptions struct {
    13 4   Servers []DNSServerOptions `json:"servers,omitempty"`
    14 5   Rules []DNSRule `json:"rules,omitempty"`
    skipped 18 lines
    33 24   Detour string `json:"detour,omitempty"`
    34 25  }
    35 26   
    36  -type _DNSRule struct {
    37  - Type string `json:"type,omitempty"`
    38  - DefaultOptions DefaultDNSRule `json:"-"`
    39  - LogicalOptions LogicalDNSRule `json:"-"`
    40  -}
    41  - 
    42  -type DNSRule _DNSRule
    43  - 
    44  -func (r DNSRule) MarshalJSON() ([]byte, error) {
    45  - var v any
    46  - switch r.Type {
    47  - case C.RuleTypeDefault:
    48  - r.Type = ""
    49  - v = r.DefaultOptions
    50  - case C.RuleTypeLogical:
    51  - v = r.LogicalOptions
    52  - default:
    53  - return nil, E.New("unknown rule type: " + r.Type)
    54  - }
    55  - return MarshallObjects((_DNSRule)(r), v)
    56  -}
    57  - 
    58  -func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
    59  - err := json.Unmarshal(bytes, (*_DNSRule)(r))
    60  - if err != nil {
    61  - return err
    62  - }
    63  - var v any
    64  - switch r.Type {
    65  - case "", C.RuleTypeDefault:
    66  - r.Type = C.RuleTypeDefault
    67  - v = &r.DefaultOptions
    68  - case C.RuleTypeLogical:
    69  - v = &r.LogicalOptions
    70  - default:
    71  - return E.New("unknown rule type: " + r.Type)
    72  - }
    73  - err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
    74  - if err != nil {
    75  - return E.Cause(err, "dns route rule")
    76  - }
    77  - return nil
    78  -}
    79  - 
    80  -type DefaultDNSRule struct {
    81  - Inbound Listable[string] `json:"inbound,omitempty"`
    82  - IPVersion int `json:"ip_version,omitempty"`
    83  - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
    84  - Network string `json:"network,omitempty"`
    85  - AuthUser Listable[string] `json:"auth_user,omitempty"`
    86  - Protocol Listable[string] `json:"protocol,omitempty"`
    87  - Domain Listable[string] `json:"domain,omitempty"`
    88  - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
    89  - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
    90  - DomainRegex Listable[string] `json:"domain_regex,omitempty"`
    91  - Geosite Listable[string] `json:"geosite,omitempty"`
    92  - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
    93  - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
    94  - SourcePort Listable[uint16] `json:"source_port,omitempty"`
    95  - SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
    96  - Port Listable[uint16] `json:"port,omitempty"`
    97  - PortRange Listable[string] `json:"port_range,omitempty"`
    98  - ProcessName Listable[string] `json:"process_name,omitempty"`
    99  - ProcessPath Listable[string] `json:"process_path,omitempty"`
    100  - PackageName Listable[string] `json:"package_name,omitempty"`
    101  - User Listable[string] `json:"user,omitempty"`
    102  - UserID Listable[int32] `json:"user_id,omitempty"`
    103  - Outbound Listable[string] `json:"outbound,omitempty"`
    104  - ClashMode string `json:"clash_mode,omitempty"`
    105  - Invert bool `json:"invert,omitempty"`
    106  - Server string `json:"server,omitempty"`
    107  - DisableCache bool `json:"disable_cache,omitempty"`
    108  -}
    109  - 
    110  -func (r DefaultDNSRule) IsValid() bool {
    111  - var defaultValue DefaultDNSRule
    112  - defaultValue.Invert = r.Invert
    113  - defaultValue.Server = r.Server
    114  - defaultValue.DisableCache = r.DisableCache
    115  - return !reflect.DeepEqual(r, defaultValue)
    116  -}
    117  - 
    118  -type LogicalDNSRule struct {
    119  - Mode string `json:"mode"`
    120  - Rules []DefaultDNSRule `json:"rules,omitempty"`
    121  - Invert bool `json:"invert,omitempty"`
    122  - Server string `json:"server,omitempty"`
    123  - DisableCache bool `json:"disable_cache,omitempty"`
    124  -}
    125  - 
    126  -func (r LogicalDNSRule) IsValid() bool {
    127  - return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
    128  -}
    129  - 
  • ■ ■ ■ ■ ■
    option/route.go
    1 1  package option
    2 2   
    3  -import (
    4  - "reflect"
    5  - 
    6  - "github.com/sagernet/sing-box/common/json"
    7  - C "github.com/sagernet/sing-box/constant"
    8  - "github.com/sagernet/sing/common"
    9  - E "github.com/sagernet/sing/common/exceptions"
    10  -)
    11  - 
    12 3  type RouteOptions struct {
    13 4   GeoIP *GeoIPOptions `json:"geoip,omitempty"`
    14 5   Geosite *GeositeOptions `json:"geosite,omitempty"`
     6 + IPRules []IPRule `json:"ip_rules,omitempty"`
    15 7   Rules []Rule `json:"rules,omitempty"`
    16 8   Final string `json:"final,omitempty"`
    17 9   FindProcess bool `json:"find_process,omitempty"`
    skipped 15 lines
    33 25   DownloadDetour string `json:"download_detour,omitempty"`
    34 26  }
    35 27   
    36  -type _Rule struct {
    37  - Type string `json:"type,omitempty"`
    38  - DefaultOptions DefaultRule `json:"-"`
    39  - LogicalOptions LogicalRule `json:"-"`
    40  -}
    41  - 
    42  -type Rule _Rule
    43  - 
    44  -func (r Rule) MarshalJSON() ([]byte, error) {
    45  - var v any
    46  - switch r.Type {
    47  - case C.RuleTypeDefault:
    48  - r.Type = ""
    49  - v = r.DefaultOptions
    50  - case C.RuleTypeLogical:
    51  - v = r.LogicalOptions
    52  - default:
    53  - return nil, E.New("unknown rule type: " + r.Type)
    54  - }
    55  - return MarshallObjects((_Rule)(r), v)
    56  -}
    57  - 
    58  -func (r *Rule) UnmarshalJSON(bytes []byte) error {
    59  - err := json.Unmarshal(bytes, (*_Rule)(r))
    60  - if err != nil {
    61  - return err
    62  - }
    63  - var v any
    64  - switch r.Type {
    65  - case "", C.RuleTypeDefault:
    66  - r.Type = C.RuleTypeDefault
    67  - v = &r.DefaultOptions
    68  - case C.RuleTypeLogical:
    69  - v = &r.LogicalOptions
    70  - default:
    71  - return E.New("unknown rule type: " + r.Type)
    72  - }
    73  - err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
    74  - if err != nil {
    75  - return E.Cause(err, "route rule")
    76  - }
    77  - return nil
    78  -}
    79  - 
    80  -type DefaultRule struct {
    81  - Inbound Listable[string] `json:"inbound,omitempty"`
    82  - IPVersion int `json:"ip_version,omitempty"`
    83  - Network string `json:"network,omitempty"`
    84  - AuthUser Listable[string] `json:"auth_user,omitempty"`
    85  - Protocol Listable[string] `json:"protocol,omitempty"`
    86  - Domain Listable[string] `json:"domain,omitempty"`
    87  - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
    88  - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
    89  - DomainRegex Listable[string] `json:"domain_regex,omitempty"`
    90  - Geosite Listable[string] `json:"geosite,omitempty"`
    91  - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
    92  - GeoIP Listable[string] `json:"geoip,omitempty"`
    93  - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
    94  - IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
    95  - SourcePort Listable[uint16] `json:"source_port,omitempty"`
    96  - SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
    97  - Port Listable[uint16] `json:"port,omitempty"`
    98  - PortRange Listable[string] `json:"port_range,omitempty"`
    99  - ProcessName Listable[string] `json:"process_name,omitempty"`
    100  - ProcessPath Listable[string] `json:"process_path,omitempty"`
    101  - PackageName Listable[string] `json:"package_name,omitempty"`
    102  - User Listable[string] `json:"user,omitempty"`
    103  - UserID Listable[int32] `json:"user_id,omitempty"`
    104  - ClashMode string `json:"clash_mode,omitempty"`
    105  - Invert bool `json:"invert,omitempty"`
    106  - Outbound string `json:"outbound,omitempty"`
    107  -}
    108  - 
    109  -func (r DefaultRule) IsValid() bool {
    110  - var defaultValue DefaultRule
    111  - defaultValue.Invert = r.Invert
    112  - defaultValue.Outbound = r.Outbound
    113  - return !reflect.DeepEqual(r, defaultValue)
    114  -}
    115  - 
    116  -type LogicalRule struct {
    117  - Mode string `json:"mode"`
    118  - Rules []DefaultRule `json:"rules,omitempty"`
    119  - Invert bool `json:"invert,omitempty"`
    120  - Outbound string `json:"outbound,omitempty"`
    121  -}
    122  - 
    123  -func (r LogicalRule) IsValid() bool {
    124  - return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
    125  -}
    126  - 
  • ■ ■ ■ ■ ■ ■
    option/rule.go
     1 +package option
     2 + 
     3 +import (
     4 + "reflect"
     5 + 
     6 + "github.com/sagernet/sing-box/common/json"
     7 + C "github.com/sagernet/sing-box/constant"
     8 + "github.com/sagernet/sing/common"
     9 + E "github.com/sagernet/sing/common/exceptions"
     10 +)
     11 + 
     12 +type _Rule struct {
     13 + Type string `json:"type,omitempty"`
     14 + DefaultOptions DefaultRule `json:"-"`
     15 + LogicalOptions LogicalRule `json:"-"`
     16 +}
     17 + 
     18 +type Rule _Rule
     19 + 
     20 +func (r Rule) MarshalJSON() ([]byte, error) {
     21 + var v any
     22 + switch r.Type {
     23 + case C.RuleTypeDefault:
     24 + r.Type = ""
     25 + v = r.DefaultOptions
     26 + case C.RuleTypeLogical:
     27 + v = r.LogicalOptions
     28 + default:
     29 + return nil, E.New("unknown rule type: " + r.Type)
     30 + }
     31 + return MarshallObjects((_Rule)(r), v)
     32 +}
     33 + 
     34 +func (r *Rule) UnmarshalJSON(bytes []byte) error {
     35 + err := json.Unmarshal(bytes, (*_Rule)(r))
     36 + if err != nil {
     37 + return err
     38 + }
     39 + var v any
     40 + switch r.Type {
     41 + case "", C.RuleTypeDefault:
     42 + r.Type = C.RuleTypeDefault
     43 + v = &r.DefaultOptions
     44 + case C.RuleTypeLogical:
     45 + v = &r.LogicalOptions
     46 + default:
     47 + return E.New("unknown rule type: " + r.Type)
     48 + }
     49 + err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
     50 + if err != nil {
     51 + return E.Cause(err, "route rule")
     52 + }
     53 + return nil
     54 +}
     55 + 
     56 +type DefaultRule struct {
     57 + Inbound Listable[string] `json:"inbound,omitempty"`
     58 + IPVersion int `json:"ip_version,omitempty"`
     59 + Network Listable[string] `json:"network,omitempty"`
     60 + AuthUser Listable[string] `json:"auth_user,omitempty"`
     61 + Protocol Listable[string] `json:"protocol,omitempty"`
     62 + Domain Listable[string] `json:"domain,omitempty"`
     63 + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
     64 + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
     65 + DomainRegex Listable[string] `json:"domain_regex,omitempty"`
     66 + Geosite Listable[string] `json:"geosite,omitempty"`
     67 + SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
     68 + GeoIP Listable[string] `json:"geoip,omitempty"`
     69 + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
     70 + IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
     71 + SourcePort Listable[uint16] `json:"source_port,omitempty"`
     72 + SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
     73 + Port Listable[uint16] `json:"port,omitempty"`
     74 + PortRange Listable[string] `json:"port_range,omitempty"`
     75 + ProcessName Listable[string] `json:"process_name,omitempty"`
     76 + ProcessPath Listable[string] `json:"process_path,omitempty"`
     77 + PackageName Listable[string] `json:"package_name,omitempty"`
     78 + User Listable[string] `json:"user,omitempty"`
     79 + UserID Listable[int32] `json:"user_id,omitempty"`
     80 + ClashMode string `json:"clash_mode,omitempty"`
     81 + Invert bool `json:"invert,omitempty"`
     82 + Outbound string `json:"outbound,omitempty"`
     83 +}
     84 + 
     85 +func (r DefaultRule) IsValid() bool {
     86 + var defaultValue DefaultRule
     87 + defaultValue.Invert = r.Invert
     88 + defaultValue.Outbound = r.Outbound
     89 + return !reflect.DeepEqual(r, defaultValue)
     90 +}
     91 + 
     92 +type LogicalRule struct {
     93 + Mode string `json:"mode"`
     94 + Rules []DefaultRule `json:"rules,omitempty"`
     95 + Invert bool `json:"invert,omitempty"`
     96 + Outbound string `json:"outbound,omitempty"`
     97 +}
     98 + 
     99 +func (r LogicalRule) IsValid() bool {
     100 + return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
     101 +}
     102 + 
  • ■ ■ ■ ■ ■ ■
    option/rule_dns.go
     1 +package option
     2 + 
     3 +import (
     4 + "reflect"
     5 + 
     6 + "github.com/sagernet/sing-box/common/json"
     7 + C "github.com/sagernet/sing-box/constant"
     8 + "github.com/sagernet/sing/common"
     9 + E "github.com/sagernet/sing/common/exceptions"
     10 +)
     11 + 
     12 +type _DNSRule struct {
     13 + Type string `json:"type,omitempty"`
     14 + DefaultOptions DefaultDNSRule `json:"-"`
     15 + LogicalOptions LogicalDNSRule `json:"-"`
     16 +}
     17 + 
     18 +type DNSRule _DNSRule
     19 + 
     20 +func (r DNSRule) MarshalJSON() ([]byte, error) {
     21 + var v any
     22 + switch r.Type {
     23 + case C.RuleTypeDefault:
     24 + r.Type = ""
     25 + v = r.DefaultOptions
     26 + case C.RuleTypeLogical:
     27 + v = r.LogicalOptions
     28 + default:
     29 + return nil, E.New("unknown rule type: " + r.Type)
     30 + }
     31 + return MarshallObjects((_DNSRule)(r), v)
     32 +}
     33 + 
     34 +func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
     35 + err := json.Unmarshal(bytes, (*_DNSRule)(r))
     36 + if err != nil {
     37 + return err
     38 + }
     39 + var v any
     40 + switch r.Type {
     41 + case "", C.RuleTypeDefault:
     42 + r.Type = C.RuleTypeDefault
     43 + v = &r.DefaultOptions
     44 + case C.RuleTypeLogical:
     45 + v = &r.LogicalOptions
     46 + default:
     47 + return E.New("unknown rule type: " + r.Type)
     48 + }
     49 + err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
     50 + if err != nil {
     51 + return E.Cause(err, "dns route rule")
     52 + }
     53 + return nil
     54 +}
     55 + 
     56 +type DefaultDNSRule struct {
     57 + Inbound Listable[string] `json:"inbound,omitempty"`
     58 + IPVersion int `json:"ip_version,omitempty"`
     59 + QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
     60 + Network Listable[string] `json:"network,omitempty"`
     61 + AuthUser Listable[string] `json:"auth_user,omitempty"`
     62 + Protocol Listable[string] `json:"protocol,omitempty"`
     63 + Domain Listable[string] `json:"domain,omitempty"`
     64 + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
     65 + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
     66 + DomainRegex Listable[string] `json:"domain_regex,omitempty"`
     67 + Geosite Listable[string] `json:"geosite,omitempty"`
     68 + SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
     69 + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
     70 + SourcePort Listable[uint16] `json:"source_port,omitempty"`
     71 + SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
     72 + Port Listable[uint16] `json:"port,omitempty"`
     73 + PortRange Listable[string] `json:"port_range,omitempty"`
     74 + ProcessName Listable[string] `json:"process_name,omitempty"`
     75 + ProcessPath Listable[string] `json:"process_path,omitempty"`
     76 + PackageName Listable[string] `json:"package_name,omitempty"`
     77 + User Listable[string] `json:"user,omitempty"`
     78 + UserID Listable[int32] `json:"user_id,omitempty"`
     79 + Outbound Listable[string] `json:"outbound,omitempty"`
     80 + ClashMode string `json:"clash_mode,omitempty"`
     81 + Invert bool `json:"invert,omitempty"`
     82 + Server string `json:"server,omitempty"`
     83 + DisableCache bool `json:"disable_cache,omitempty"`
     84 + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
     85 +}
     86 + 
     87 +func (r DefaultDNSRule) IsValid() bool {
     88 + var defaultValue DefaultDNSRule
     89 + defaultValue.Invert = r.Invert
     90 + defaultValue.Server = r.Server
     91 + defaultValue.DisableCache = r.DisableCache
     92 + defaultValue.RewriteTTL = r.RewriteTTL
     93 + return !reflect.DeepEqual(r, defaultValue)
     94 +}
     95 + 
     96 +type LogicalDNSRule struct {
     97 + Mode string `json:"mode"`
     98 + Rules []DefaultDNSRule `json:"rules,omitempty"`
     99 + Invert bool `json:"invert,omitempty"`
     100 + Server string `json:"server,omitempty"`
     101 + DisableCache bool `json:"disable_cache,omitempty"`
     102 + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
     103 +}
     104 + 
     105 +func (r LogicalDNSRule) IsValid() bool {
     106 + return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
     107 +}
     108 + 
  • ■ ■ ■ ■ ■ ■
    option/rule_ip.go
     1 +package option
     2 + 
     3 +import (
     4 + "reflect"
     5 + 
     6 + "github.com/sagernet/sing-box/common/json"
     7 + C "github.com/sagernet/sing-box/constant"
     8 + "github.com/sagernet/sing-tun"
     9 + "github.com/sagernet/sing/common"
     10 + E "github.com/sagernet/sing/common/exceptions"
     11 +)
     12 + 
     13 +type _IPRule struct {
     14 + Type string `json:"type,omitempty"`
     15 + DefaultOptions DefaultIPRule `json:"-"`
     16 + LogicalOptions LogicalIPRule `json:"-"`
     17 +}
     18 + 
     19 +type IPRule _IPRule
     20 + 
     21 +func (r IPRule) MarshalJSON() ([]byte, error) {
     22 + var v any
     23 + switch r.Type {
     24 + case C.RuleTypeDefault:
     25 + r.Type = ""
     26 + v = r.DefaultOptions
     27 + case C.RuleTypeLogical:
     28 + v = r.LogicalOptions
     29 + default:
     30 + return nil, E.New("unknown rule type: " + r.Type)
     31 + }
     32 + return MarshallObjects((_IPRule)(r), v)
     33 +}
     34 + 
     35 +func (r *IPRule) UnmarshalJSON(bytes []byte) error {
     36 + err := json.Unmarshal(bytes, (*_IPRule)(r))
     37 + if err != nil {
     38 + return err
     39 + }
     40 + var v any
     41 + switch r.Type {
     42 + case "", C.RuleTypeDefault:
     43 + r.Type = C.RuleTypeDefault
     44 + v = &r.DefaultOptions
     45 + case C.RuleTypeLogical:
     46 + v = &r.LogicalOptions
     47 + default:
     48 + return E.New("unknown rule type: " + r.Type)
     49 + }
     50 + err = UnmarshallExcluded(bytes, (*_IPRule)(r), v)
     51 + if err != nil {
     52 + return E.Cause(err, "ip route rule")
     53 + }
     54 + return nil
     55 +}
     56 + 
     57 +type DefaultIPRule struct {
     58 + Inbound Listable[string] `json:"inbound,omitempty"`
     59 + IPVersion int `json:"ip_version,omitempty"`
     60 + Network Listable[string] `json:"network,omitempty"`
     61 + Domain Listable[string] `json:"domain,omitempty"`
     62 + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
     63 + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
     64 + DomainRegex Listable[string] `json:"domain_regex,omitempty"`
     65 + Geosite Listable[string] `json:"geosite,omitempty"`
     66 + SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
     67 + GeoIP Listable[string] `json:"geoip,omitempty"`
     68 + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
     69 + IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
     70 + SourcePort Listable[uint16] `json:"source_port,omitempty"`
     71 + SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
     72 + Port Listable[uint16] `json:"port,omitempty"`
     73 + PortRange Listable[string] `json:"port_range,omitempty"`
     74 + Invert bool `json:"invert,omitempty"`
     75 + Action RouteAction `json:"action,omitempty"`
     76 + Outbound string `json:"outbound,omitempty"`
     77 +}
     78 + 
     79 +type RouteAction tun.ActionType
     80 + 
     81 +func (a RouteAction) MarshalJSON() ([]byte, error) {
     82 + typeName, err := tun.ActionTypeName(tun.ActionType(a))
     83 + if err != nil {
     84 + return nil, err
     85 + }
     86 + return json.Marshal(typeName)
     87 +}
     88 + 
     89 +func (a *RouteAction) UnmarshalJSON(bytes []byte) error {
     90 + var value string
     91 + err := json.Unmarshal(bytes, &value)
     92 + if err != nil {
     93 + return err
     94 + }
     95 + actionType, err := tun.ParseActionType(value)
     96 + if err != nil {
     97 + return err
     98 + }
     99 + *a = RouteAction(actionType)
     100 + return nil
     101 +}
     102 + 
     103 +func (r DefaultIPRule) IsValid() bool {
     104 + var defaultValue DefaultIPRule
     105 + defaultValue.Invert = r.Invert
     106 + defaultValue.Outbound = r.Outbound
     107 + return !reflect.DeepEqual(r, defaultValue)
     108 +}
     109 + 
     110 +type LogicalIPRule struct {
     111 + Mode string `json:"mode"`
     112 + Rules []DefaultIPRule `json:"rules,omitempty"`
     113 + Invert bool `json:"invert,omitempty"`
     114 + Action RouteAction `json:"action,omitempty"`
     115 + Outbound string `json:"outbound,omitempty"`
     116 +}
     117 + 
     118 +func (r LogicalIPRule) IsValid() bool {
     119 + return len(r.Rules) > 0 && common.All(r.Rules, DefaultIPRule.IsValid)
     120 +}
     121 + 
  • ■ ■ ■ ■ ■
    option/wireguard.go
    skipped 12 lines
    13 13   Workers int `json:"workers,omitempty"`
    14 14   MTU uint32 `json:"mtu,omitempty"`
    15 15   Network NetworkList `json:"network,omitempty"`
     16 + IPRewrite bool `json:"ip_rewrite,omitempty"`
    16 17  }
    17 18   
  • ■ ■ ■ ■ ■
    outbound/wireguard.go
    skipped 7 lines
    8 8   "encoding/hex"
    9 9   "fmt"
    10 10   "net"
     11 + "os"
    11 12   "strings"
     13 + "syscall"
    12 14   
    13 15   "github.com/sagernet/sing-box/adapter"
    14 16   "github.com/sagernet/sing-box/common/dialer"
    skipped 11 lines
    26 28  )
    27 29   
    28 30  var (
    29  - _ adapter.Outbound = (*WireGuard)(nil)
     31 + _ adapter.IPOutbound = (*WireGuard)(nil)
    30 32   _ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
    31 33  )
    32 34   
    skipped 1 lines
    34 36   myOutboundAdapter
    35 37   bind *wireguard.ClientBind
    36 38   device *device.Device
     39 + natDevice wireguard.NatDevice
    37 40   tunDevice wireguard.Device
    38 41  }
    39 42   
    skipped 66 lines
    106 109   if mtu == 0 {
    107 110   mtu = 1408
    108 111   }
    109  - var wireTunDevice wireguard.Device
     112 + var tunDevice wireguard.Device
    110 113   var err error
    111 114   if !options.SystemInterface && tun.WithGVisor {
    112  - wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
     115 + tunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu, options.IPRewrite)
    113 116   } else {
    114  - wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
     117 + tunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
    115 118   }
    116 119   if err != nil {
    117 120   return nil, E.Cause(err, "create WireGuard device")
    118 121   }
    119  - wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
     122 + natDevice, isNatDevice := tunDevice.(wireguard.NatDevice)
     123 + if !isNatDevice && router.NatRequired(tag) {
     124 + natDevice = wireguard.NewNATDevice(tunDevice, options.IPRewrite)
     125 + }
     126 + deviceInput := tunDevice
     127 + if natDevice != nil {
     128 + deviceInput = natDevice
     129 + }
     130 + wgDevice := device.NewDevice(deviceInput, outbound.bind, &device.Logger{
    120 131   Verbosef: func(format string, args ...interface{}) {
    121 132   logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
    122 133   },
    skipped 9 lines
    132 143   return nil, E.Cause(err, "setup wireguard")
    133 144   }
    134 145   outbound.device = wgDevice
    135  - outbound.tunDevice = wireTunDevice
     146 + outbound.natDevice = natDevice
     147 + outbound.tunDevice = tunDevice
    136 148   return outbound, nil
    137 149  }
    138 150   
    skipped 30 lines
    169 181   
    170 182  func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
    171 183   return NewPacketConnection(ctx, w, conn, metadata)
     184 +}
     185 + 
     186 +func (w *WireGuard) NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) (tun.DirectDestination, error) {
     187 + if w.natDevice == nil {
     188 + return nil, os.ErrInvalid
     189 + }
     190 + session := tun.RouteSession{
     191 + IPVersion: metadata.IPVersion,
     192 + Network: tun.NetworkFromName(metadata.Network),
     193 + Source: metadata.Source.AddrPort(),
     194 + Destination: metadata.Destination.AddrPort(),
     195 + }
     196 + switch session.Network {
     197 + case syscall.IPPROTO_TCP:
     198 + w.logger.InfoContext(ctx, "linked connection to ", metadata.Destination)
     199 + case syscall.IPPROTO_UDP:
     200 + w.logger.InfoContext(ctx, "linked packet connection to ", metadata.Destination)
     201 + default:
     202 + w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
     203 + }
     204 + return w.natDevice.CreateDestination(session, conn), nil
    172 205  }
    173 206   
    174 207  func (w *WireGuard) Start() error {
    skipped 10 lines
  • ■ ■ ■ ■ ■
    route/router.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "context"
    5  - "io"
    6 5   "net"
    7  - "net/http"
    8 6   "net/netip"
    9 7   "net/url"
    10 8   "os"
    11 9   "os/user"
    12  - "path/filepath"
    13 10   "strings"
    14 11   "time"
    15 12   
    skipped 21 lines
    37 34   F "github.com/sagernet/sing/common/format"
    38 35   M "github.com/sagernet/sing/common/metadata"
    39 36   N "github.com/sagernet/sing/common/network"
    40  - "github.com/sagernet/sing/common/rw"
    41 37   "github.com/sagernet/sing/common/uot"
    42 38  )
    43 39   
    skipped 28 lines
    72 68   outbounds []adapter.Outbound
    73 69   outboundByTag map[string]adapter.Outbound
    74 70   rules []adapter.Rule
     71 + ipRules []adapter.IPRule
    75 72   defaultDetour string
    76 73   defaultOutboundForConnection adapter.Outbound
    77 74   defaultOutboundForPacketConnection adapter.Outbound
    skipped 51 lines
    129 126   dnsLogger: logFactory.NewLogger("dns"),
    130 127   outboundByTag: make(map[string]adapter.Outbound),
    131 128   rules: make([]adapter.Rule, 0, len(options.Rules)),
     129 + ipRules: make([]adapter.IPRule, 0, len(options.IPRules)),
    132 130   dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
    133 131   needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
    134 132   needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
    skipped 15 lines
    150 148   }
    151 149   router.rules = append(router.rules, routeRule)
    152 150   }
     151 + for i, ipRuleOptions := range options.IPRules {
     152 + ipRule, err := NewIPRule(router, router.logger, ipRuleOptions)
     153 + if err != nil {
     154 + return nil, E.Cause(err, "parse ip rule[", i, "]")
     155 + }
     156 + router.ipRules = append(router.ipRules, ipRule)
     157 + }
    153 158   for i, dnsRuleOptions := range dnsOptions.Rules {
    154 159   dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
    155 160   if err != nil {
    skipped 1 lines
    157 162   }
    158 163   router.dnsRules = append(router.dnsRules, dnsRule)
    159 164   }
     165 + 
    160 166   transports := make([]dns.Transport, len(dnsOptions.Servers))
    161 167   dummyTransportMap := make(map[string]dns.Transport)
    162 168   transportMap := make(map[string]dns.Transport)
    skipped 356 lines
    519 525   r.packageManager,
    520 526   r.timeService,
    521 527   )
    522  -}
    523  - 
    524  -func (r *Router) GeoIPReader() *geoip.Reader {
    525  - return r.geoIPReader
    526  -}
    527  - 
    528  -func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
    529  - rule, cached := r.geositeCache[code]
    530  - if cached {
    531  - return rule, nil
    532  - }
    533  - items, err := r.geositeReader.Read(code)
    534  - if err != nil {
    535  - return nil, err
    536  - }
    537  - rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
    538  - if err != nil {
    539  - return nil, err
    540  - }
    541  - r.geositeCache[code] = rule
    542  - return rule, nil
    543 528  }
    544 529   
    545 530  func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
    skipped 173 lines
    719 704   }
    720 705   conn = bufio.NewCachedPacketConn(conn, buffer, destination)
    721 706   }
     707 + if r.dnsReverseMapping != nil && metadata.Domain == "" {
     708 + domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
     709 + if loaded {
     710 + metadata.Domain = domain
     711 + r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
     712 + }
     713 + }
    722 714   if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
    723 715   addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
    724 716   if err != nil {
    skipped 100 lines
    825 817   return r.rules
    826 818  }
    827 819   
     820 +func (r *Router) IPRules() []adapter.IPRule {
     821 + return r.ipRules
     822 +}
     823 + 
    828 824  func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
    829 825   return r.networkMonitor
    830 826  }
    skipped 27 lines
    858 854   
    859 855  func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
    860 856   r.v2rayServer = server
    861  -}
    862  - 
    863  -func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
    864  - for _, rule := range rules {
    865  - switch rule.Type {
    866  - case C.RuleTypeDefault:
    867  - if cond(rule.DefaultOptions) {
    868  - return true
    869  - }
    870  - case C.RuleTypeLogical:
    871  - for _, subRule := range rule.LogicalOptions.Rules {
    872  - if cond(subRule) {
    873  - return true
    874  - }
    875  - }
    876  - }
    877  - }
    878  - return false
    879  -}
    880  - 
    881  -func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
    882  - for _, rule := range rules {
    883  - switch rule.Type {
    884  - case C.RuleTypeDefault:
    885  - if cond(rule.DefaultOptions) {
    886  - return true
    887  - }
    888  - case C.RuleTypeLogical:
    889  - for _, subRule := range rule.LogicalOptions.Rules {
    890  - if cond(subRule) {
    891  - return true
    892  - }
    893  - }
    894  - }
    895  - }
    896  - return false
    897  -}
    898  - 
    899  -func isGeoIPRule(rule option.DefaultRule) bool {
    900  - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
    901  -}
    902  - 
    903  -func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
    904  - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
    905  -}
    906  - 
    907  -func isGeositeRule(rule option.DefaultRule) bool {
    908  - return len(rule.Geosite) > 0
    909  -}
    910  - 
    911  -func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
    912  - return len(rule.Geosite) > 0
    913  -}
    914  - 
    915  -func isProcessRule(rule option.DefaultRule) bool {
    916  - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
    917  -}
    918  - 
    919  -func isProcessDNSRule(rule option.DefaultDNSRule) bool {
    920  - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
    921  -}
    922  - 
    923  -func notPrivateNode(code string) bool {
    924  - return code != "private"
    925  -}
    926  - 
    927  -func (r *Router) prepareGeoIPDatabase() error {
    928  - var geoPath string
    929  - if r.geoIPOptions.Path != "" {
    930  - geoPath = r.geoIPOptions.Path
    931  - } else {
    932  - geoPath = "geoip.db"
    933  - if foundPath, loaded := C.FindPath(geoPath); loaded {
    934  - geoPath = foundPath
    935  - }
    936  - }
    937  - geoPath = C.BasePath(geoPath)
    938  - if !rw.FileExists(geoPath) {
    939  - r.logger.Warn("geoip database not exists: ", geoPath)
    940  - var err error
    941  - for attempts := 0; attempts < 3; attempts++ {
    942  - err = r.downloadGeoIPDatabase(geoPath)
    943  - if err == nil {
    944  - break
    945  - }
    946  - r.logger.Error("download geoip database: ", err)
    947  - os.Remove(geoPath)
    948  - // time.Sleep(10 * time.Second)
    949  - }
    950  - if err != nil {
    951  - return err
    952  - }
    953  - }
    954  - geoReader, codes, err := geoip.Open(geoPath)
    955  - if err != nil {
    956  - return E.Cause(err, "open geoip database")
    957  - }
    958  - r.logger.Info("loaded geoip database: ", len(codes), " codes")
    959  - r.geoIPReader = geoReader
    960  - return nil
    961  -}
    962  - 
    963  -func (r *Router) prepareGeositeDatabase() error {
    964  - var geoPath string
    965  - if r.geositeOptions.Path != "" {
    966  - geoPath = r.geositeOptions.Path
    967  - } else {
    968  - geoPath = "geosite.db"
    969  - if foundPath, loaded := C.FindPath(geoPath); loaded {
    970  - geoPath = foundPath
    971  - }
    972  - }
    973  - geoPath = C.BasePath(geoPath)
    974  - if !rw.FileExists(geoPath) {
    975  - r.logger.Warn("geosite database not exists: ", geoPath)
    976  - var err error
    977  - for attempts := 0; attempts < 3; attempts++ {
    978  - err = r.downloadGeositeDatabase(geoPath)
    979  - if err == nil {
    980  - break
    981  - }
    982  - r.logger.Error("download geosite database: ", err)
    983  - os.Remove(geoPath)
    984  - // time.Sleep(10 * time.Second)
    985  - }
    986  - if err != nil {
    987  - return err
    988  - }
    989  - }
    990  - geoReader, codes, err := geosite.Open(geoPath)
    991  - if err == nil {
    992  - r.logger.Info("loaded geosite database: ", len(codes), " codes")
    993  - r.geositeReader = geoReader
    994  - } else {
    995  - return E.Cause(err, "open geosite database")
    996  - }
    997  - return nil
    998  -}
    999  - 
    1000  -func (r *Router) downloadGeoIPDatabase(savePath string) error {
    1001  - var downloadURL string
    1002  - if r.geoIPOptions.DownloadURL != "" {
    1003  - downloadURL = r.geoIPOptions.DownloadURL
    1004  - } else {
    1005  - downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
    1006  - }
    1007  - r.logger.Info("downloading geoip database")
    1008  - var detour adapter.Outbound
    1009  - if r.geoIPOptions.DownloadDetour != "" {
    1010  - outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
    1011  - if !loaded {
    1012  - return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
    1013  - }
    1014  - detour = outbound
    1015  - } else {
    1016  - detour = r.defaultOutboundForConnection
    1017  - }
    1018  - 
    1019  - if parentDir := filepath.Dir(savePath); parentDir != "" {
    1020  - os.MkdirAll(parentDir, 0o755)
    1021  - }
    1022  - 
    1023  - saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
    1024  - if err != nil {
    1025  - return E.Cause(err, "open output file: ", downloadURL)
    1026  - }
    1027  - defer saveFile.Close()
    1028  - 
    1029  - httpClient := &http.Client{
    1030  - Transport: &http.Transport{
    1031  - ForceAttemptHTTP2: true,
    1032  - TLSHandshakeTimeout: 5 * time.Second,
    1033  - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
    1034  - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
    1035  - },
    1036  - },
    1037  - }
    1038  - defer httpClient.CloseIdleConnections()
    1039  - response, err := httpClient.Get(downloadURL)
    1040  - if err != nil {
    1041  - return err
    1042  - }
    1043  - defer response.Body.Close()
    1044  - _, err = io.Copy(saveFile, response.Body)
    1045  - return err
    1046  -}
    1047  - 
    1048  -func (r *Router) downloadGeositeDatabase(savePath string) error {
    1049  - var downloadURL string
    1050  - if r.geositeOptions.DownloadURL != "" {
    1051  - downloadURL = r.geositeOptions.DownloadURL
    1052  - } else {
    1053  - downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
    1054  - }
    1055  - r.logger.Info("downloading geosite database")
    1056  - var detour adapter.Outbound
    1057  - if r.geositeOptions.DownloadDetour != "" {
    1058  - outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
    1059  - if !loaded {
    1060  - return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
    1061  - }
    1062  - detour = outbound
    1063  - } else {
    1064  - detour = r.defaultOutboundForConnection
    1065  - }
    1066  - 
    1067  - if parentDir := filepath.Dir(savePath); parentDir != "" {
    1068  - os.MkdirAll(parentDir, 0o755)
    1069  - }
    1070  - 
    1071  - saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
    1072  - if err != nil {
    1073  - return E.Cause(err, "open output file: ", downloadURL)
    1074  - }
    1075  - defer saveFile.Close()
    1076  - 
    1077  - httpClient := &http.Client{
    1078  - Transport: &http.Transport{
    1079  - ForceAttemptHTTP2: true,
    1080  - TLSHandshakeTimeout: 5 * time.Second,
    1081  - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
    1082  - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
    1083  - },
    1084  - },
    1085  - }
    1086  - defer httpClient.CloseIdleConnections()
    1087  - response, err := httpClient.Get(downloadURL)
    1088  - if err != nil {
    1089  - return err
    1090  - }
    1091  - defer response.Body.Close()
    1092  - _, err = io.Copy(saveFile, response.Body)
    1093  - return err
    1094 857  }
    1095 858   
    1096 859  func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
    skipped 37 lines
  • ■ ■ ■ ■ ■ ■
    route/router_dns.go
    skipped 46 lines
    47 47   if rule.DisableCache() {
    48 48   ctx = dns.ContextWithDisableCache(ctx, true)
    49 49   }
     50 + if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
     51 + ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
     52 + }
    50 53   detour := rule.Outbound()
    51 54   r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
    52 55   if transport, loaded := r.transportMap[detour]; loaded {
    skipped 104 lines
  • ■ ■ ■ ■ ■ ■
    route/router_geo_resources.go
     1 +package route
     2 + 
     3 +import (
     4 + "context"
     5 + "io"
     6 + "net"
     7 + "net/http"
     8 + "os"
     9 + "path/filepath"
     10 + "time"
     11 + 
     12 + "github.com/sagernet/sing-box/adapter"
     13 + "github.com/sagernet/sing-box/common/geoip"
     14 + "github.com/sagernet/sing-box/common/geosite"
     15 + C "github.com/sagernet/sing-box/constant"
     16 + "github.com/sagernet/sing-box/option"
     17 + "github.com/sagernet/sing/common"
     18 + E "github.com/sagernet/sing/common/exceptions"
     19 + M "github.com/sagernet/sing/common/metadata"
     20 + "github.com/sagernet/sing/common/rw"
     21 +)
     22 + 
     23 +func (r *Router) GeoIPReader() *geoip.Reader {
     24 + return r.geoIPReader
     25 +}
     26 + 
     27 +func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
     28 + rule, cached := r.geositeCache[code]
     29 + if cached {
     30 + return rule, nil
     31 + }
     32 + items, err := r.geositeReader.Read(code)
     33 + if err != nil {
     34 + return nil, err
     35 + }
     36 + rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
     37 + if err != nil {
     38 + return nil, err
     39 + }
     40 + r.geositeCache[code] = rule
     41 + return rule, nil
     42 +}
     43 + 
     44 +func (r *Router) prepareGeoIPDatabase() error {
     45 + var geoPath string
     46 + if r.geoIPOptions.Path != "" {
     47 + geoPath = r.geoIPOptions.Path
     48 + } else {
     49 + geoPath = "geoip.db"
     50 + if foundPath, loaded := C.FindPath(geoPath); loaded {
     51 + geoPath = foundPath
     52 + }
     53 + }
     54 + geoPath = C.BasePath(geoPath)
     55 + if rw.FileExists(geoPath) {
     56 + geoReader, codes, err := geoip.Open(geoPath)
     57 + if err == nil {
     58 + r.logger.Info("loaded geoip database: ", len(codes), " codes")
     59 + r.geoIPReader = geoReader
     60 + return nil
     61 + }
     62 + }
     63 + if !rw.FileExists(geoPath) {
     64 + r.logger.Warn("geoip database not exists: ", geoPath)
     65 + var err error
     66 + for attempts := 0; attempts < 3; attempts++ {
     67 + err = r.downloadGeoIPDatabase(geoPath)
     68 + if err == nil {
     69 + break
     70 + }
     71 + r.logger.Error("download geoip database: ", err)
     72 + os.Remove(geoPath)
     73 + // time.Sleep(10 * time.Second)
     74 + }
     75 + if err != nil {
     76 + return err
     77 + }
     78 + }
     79 + geoReader, codes, err := geoip.Open(geoPath)
     80 + if err != nil {
     81 + return E.Cause(err, "open geoip database")
     82 + }
     83 + r.logger.Info("loaded geoip database: ", len(codes), " codes")
     84 + r.geoIPReader = geoReader
     85 + return nil
     86 +}
     87 + 
     88 +func (r *Router) prepareGeositeDatabase() error {
     89 + var geoPath string
     90 + if r.geositeOptions.Path != "" {
     91 + geoPath = r.geositeOptions.Path
     92 + } else {
     93 + geoPath = "geosite.db"
     94 + if foundPath, loaded := C.FindPath(geoPath); loaded {
     95 + geoPath = foundPath
     96 + }
     97 + }
     98 + geoPath = C.BasePath(geoPath)
     99 + if !rw.FileExists(geoPath) {
     100 + r.logger.Warn("geosite database not exists: ", geoPath)
     101 + var err error
     102 + for attempts := 0; attempts < 3; attempts++ {
     103 + err = r.downloadGeositeDatabase(geoPath)
     104 + if err == nil {
     105 + break
     106 + }
     107 + r.logger.Error("download geosite database: ", err)
     108 + os.Remove(geoPath)
     109 + // time.Sleep(10 * time.Second)
     110 + }
     111 + if err != nil {
     112 + return err
     113 + }
     114 + }
     115 + geoReader, codes, err := geosite.Open(geoPath)
     116 + if err == nil {
     117 + r.logger.Info("loaded geosite database: ", len(codes), " codes")
     118 + r.geositeReader = geoReader
     119 + } else {
     120 + return E.Cause(err, "open geosite database")
     121 + }
     122 + return nil
     123 +}
     124 + 
     125 +func (r *Router) downloadGeoIPDatabase(savePath string) error {
     126 + var downloadURL string
     127 + if r.geoIPOptions.DownloadURL != "" {
     128 + downloadURL = r.geoIPOptions.DownloadURL
     129 + } else {
     130 + downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
     131 + }
     132 + r.logger.Info("downloading geoip database")
     133 + var detour adapter.Outbound
     134 + if r.geoIPOptions.DownloadDetour != "" {
     135 + outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
     136 + if !loaded {
     137 + return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
     138 + }
     139 + detour = outbound
     140 + } else {
     141 + detour = r.defaultOutboundForConnection
     142 + }
     143 + 
     144 + if parentDir := filepath.Dir(savePath); parentDir != "" {
     145 + os.MkdirAll(parentDir, 0o755)
     146 + }
     147 + 
     148 + saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
     149 + if err != nil {
     150 + return E.Cause(err, "open output file: ", downloadURL)
     151 + }
     152 + defer saveFile.Close()
     153 + 
     154 + httpClient := &http.Client{
     155 + Transport: &http.Transport{
     156 + ForceAttemptHTTP2: true,
     157 + TLSHandshakeTimeout: 5 * time.Second,
     158 + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
     159 + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
     160 + },
     161 + },
     162 + }
     163 + defer httpClient.CloseIdleConnections()
     164 + response, err := httpClient.Get(downloadURL)
     165 + if err != nil {
     166 + return err
     167 + }
     168 + defer response.Body.Close()
     169 + _, err = io.Copy(saveFile, response.Body)
     170 + return err
     171 +}
     172 + 
     173 +func (r *Router) downloadGeositeDatabase(savePath string) error {
     174 + var downloadURL string
     175 + if r.geositeOptions.DownloadURL != "" {
     176 + downloadURL = r.geositeOptions.DownloadURL
     177 + } else {
     178 + downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
     179 + }
     180 + r.logger.Info("downloading geosite database")
     181 + var detour adapter.Outbound
     182 + if r.geositeOptions.DownloadDetour != "" {
     183 + outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
     184 + if !loaded {
     185 + return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
     186 + }
     187 + detour = outbound
     188 + } else {
     189 + detour = r.defaultOutboundForConnection
     190 + }
     191 + 
     192 + if parentDir := filepath.Dir(savePath); parentDir != "" {
     193 + os.MkdirAll(parentDir, 0o755)
     194 + }
     195 + 
     196 + saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
     197 + if err != nil {
     198 + return E.Cause(err, "open output file: ", downloadURL)
     199 + }
     200 + defer saveFile.Close()
     201 + 
     202 + httpClient := &http.Client{
     203 + Transport: &http.Transport{
     204 + ForceAttemptHTTP2: true,
     205 + TLSHandshakeTimeout: 5 * time.Second,
     206 + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
     207 + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
     208 + },
     209 + },
     210 + }
     211 + defer httpClient.CloseIdleConnections()
     212 + response, err := httpClient.Get(downloadURL)
     213 + if err != nil {
     214 + return err
     215 + }
     216 + defer response.Body.Close()
     217 + _, err = io.Copy(saveFile, response.Body)
     218 + return err
     219 +}
     220 + 
     221 +func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
     222 + for _, rule := range rules {
     223 + switch rule.Type {
     224 + case C.RuleTypeDefault:
     225 + if cond(rule.DefaultOptions) {
     226 + return true
     227 + }
     228 + case C.RuleTypeLogical:
     229 + for _, subRule := range rule.LogicalOptions.Rules {
     230 + if cond(subRule) {
     231 + return true
     232 + }
     233 + }
     234 + }
     235 + }
     236 + return false
     237 +}
     238 + 
     239 +func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
     240 + for _, rule := range rules {
     241 + switch rule.Type {
     242 + case C.RuleTypeDefault:
     243 + if cond(rule.DefaultOptions) {
     244 + return true
     245 + }
     246 + case C.RuleTypeLogical:
     247 + for _, subRule := range rule.LogicalOptions.Rules {
     248 + if cond(subRule) {
     249 + return true
     250 + }
     251 + }
     252 + }
     253 + }
     254 + return false
     255 +}
     256 + 
     257 +func isGeoIPRule(rule option.DefaultRule) bool {
     258 + return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
     259 +}
     260 + 
     261 +func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
     262 + return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
     263 +}
     264 + 
     265 +func isGeositeRule(rule option.DefaultRule) bool {
     266 + return len(rule.Geosite) > 0
     267 +}
     268 + 
     269 +func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
     270 + return len(rule.Geosite) > 0
     271 +}
     272 + 
     273 +func isProcessRule(rule option.DefaultRule) bool {
     274 + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
     275 +}
     276 + 
     277 +func isProcessDNSRule(rule option.DefaultDNSRule) bool {
     278 + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
     279 +}
     280 + 
     281 +func notPrivateNode(code string) bool {
     282 + return code != "private"
     283 +}
     284 + 
  • ■ ■ ■ ■ ■ ■
    route/router_ip.go
     1 +package route
     2 + 
     3 +import (
     4 + "context"
     5 + "strings"
     6 + 
     7 + "github.com/sagernet/sing-box/adapter"
     8 + "github.com/sagernet/sing-dns"
     9 + "github.com/sagernet/sing-tun"
     10 + F "github.com/sagernet/sing/common/format"
     11 +)
     12 + 
     13 +func (r *Router) RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) tun.RouteAction {
     14 + if r.dnsReverseMapping != nil && metadata.Domain == "" {
     15 + domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
     16 + if loaded {
     17 + metadata.Domain = domain
     18 + r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
     19 + }
     20 + }
     21 + if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
     22 + addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
     23 + if err != nil {
     24 + r.logger.ErrorContext(ctx, err)
     25 + return (*tun.ActionReturn)(nil)
     26 + }
     27 + metadata.DestinationAddresses = addresses
     28 + r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
     29 + }
     30 + for i, rule := range r.ipRules {
     31 + if rule.Match(&metadata) {
     32 + if rule.Action() == tun.ActionTypeBlock {
     33 + r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => block")
     34 + return (*tun.ActionBlock)(nil)
     35 + }
     36 + detour := rule.Outbound()
     37 + r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
     38 + outbound, loaded := r.Outbound(detour)
     39 + if !loaded {
     40 + r.logger.ErrorContext(ctx, "outbound not found: ", detour)
     41 + break
     42 + }
     43 + ipOutbound, loaded := outbound.(adapter.IPOutbound)
     44 + if !loaded {
     45 + r.logger.ErrorContext(ctx, "outbound have no ip connection support: ", detour)
     46 + break
     47 + }
     48 + destination, err := ipOutbound.NewIPConnection(ctx, conn, metadata)
     49 + if err != nil {
     50 + r.logger.ErrorContext(ctx, err)
     51 + break
     52 + }
     53 + return &tun.ActionDirect{DirectDestination: destination}
     54 + }
     55 + }
     56 + return (*tun.ActionReturn)(nil)
     57 +}
     58 + 
     59 +func (r *Router) NatRequired(outbound string) bool {
     60 + for _, ipRule := range r.ipRules {
     61 + if ipRule.Outbound() == outbound {
     62 + return true
     63 + }
     64 + }
     65 + return false
     66 +}
     67 + 
  • ■ ■ ■ ■ ■ ■
    route/rule_abstract.go
     1 +package route
     2 + 
     3 +import (
     4 + "strings"
     5 + 
     6 + "github.com/sagernet/sing-box/adapter"
     7 + C "github.com/sagernet/sing-box/constant"
     8 + "github.com/sagernet/sing/common"
     9 + F "github.com/sagernet/sing/common/format"
     10 +)
     11 + 
     12 +type abstractDefaultRule struct {
     13 + items []RuleItem
     14 + sourceAddressItems []RuleItem
     15 + sourcePortItems []RuleItem
     16 + destinationAddressItems []RuleItem
     17 + destinationPortItems []RuleItem
     18 + allItems []RuleItem
     19 + invert bool
     20 + outbound string
     21 +}
     22 + 
     23 +func (r *abstractDefaultRule) Type() string {
     24 + return C.RuleTypeDefault
     25 +}
     26 + 
     27 +func (r *abstractDefaultRule) Start() error {
     28 + for _, item := range r.allItems {
     29 + err := common.Start(item)
     30 + if err != nil {
     31 + return err
     32 + }
     33 + }
     34 + return nil
     35 +}
     36 + 
     37 +func (r *abstractDefaultRule) Close() error {
     38 + for _, item := range r.allItems {
     39 + err := common.Close(item)
     40 + if err != nil {
     41 + return err
     42 + }
     43 + }
     44 + return nil
     45 +}
     46 + 
     47 +func (r *abstractDefaultRule) UpdateGeosite() error {
     48 + for _, item := range r.allItems {
     49 + if geositeItem, isSite := item.(*GeositeItem); isSite {
     50 + err := geositeItem.Update()
     51 + if err != nil {
     52 + return err
     53 + }
     54 + }
     55 + }
     56 + return nil
     57 +}
     58 + 
     59 +func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
     60 + for _, item := range r.items {
     61 + if !item.Match(metadata) {
     62 + return r.invert
     63 + }
     64 + }
     65 + 
     66 + if len(r.sourceAddressItems) > 0 {
     67 + var sourceAddressMatch bool
     68 + for _, item := range r.sourceAddressItems {
     69 + if item.Match(metadata) {
     70 + sourceAddressMatch = true
     71 + break
     72 + }
     73 + }
     74 + if !sourceAddressMatch {
     75 + return r.invert
     76 + }
     77 + }
     78 + 
     79 + if len(r.sourcePortItems) > 0 {
     80 + var sourcePortMatch bool
     81 + for _, item := range r.sourcePortItems {
     82 + if item.Match(metadata) {
     83 + sourcePortMatch = true
     84 + break
     85 + }
     86 + }
     87 + if !sourcePortMatch {
     88 + return r.invert
     89 + }
     90 + }
     91 + 
     92 + if len(r.destinationAddressItems) > 0 {
     93 + var destinationAddressMatch bool
     94 + for _, item := range r.destinationAddressItems {
     95 + if item.Match(metadata) {
     96 + destinationAddressMatch = true
     97 + break
     98 + }
     99 + }
     100 + if !destinationAddressMatch {
     101 + return r.invert
     102 + }
     103 + }
     104 + 
     105 + if len(r.destinationPortItems) > 0 {
     106 + var destinationPortMatch bool
     107 + for _, item := range r.destinationPortItems {
     108 + if item.Match(metadata) {
     109 + destinationPortMatch = true
     110 + break
     111 + }
     112 + }
     113 + if !destinationPortMatch {
     114 + return r.invert
     115 + }
     116 + }
     117 + 
     118 + return !r.invert
     119 +}
     120 + 
     121 +func (r *abstractDefaultRule) Outbound() string {
     122 + return r.outbound
     123 +}
     124 + 
     125 +func (r *abstractDefaultRule) String() string {
     126 + return strings.Join(F.MapToString(r.allItems), " ")
     127 +}
     128 + 
     129 +type abstractLogicalRule struct {
     130 + rules []adapter.Rule
     131 + mode string
     132 + invert bool
     133 + outbound string
     134 +}
     135 + 
     136 +func (r *abstractLogicalRule) Type() string {
     137 + return C.RuleTypeLogical
     138 +}
     139 + 
     140 +func (r *abstractLogicalRule) UpdateGeosite() error {
     141 + for _, rule := range r.rules {
     142 + err := rule.UpdateGeosite()
     143 + if err != nil {
     144 + return err
     145 + }
     146 + }
     147 + return nil
     148 +}
     149 + 
     150 +func (r *abstractLogicalRule) Start() error {
     151 + for _, rule := range r.rules {
     152 + err := rule.Start()
     153 + if err != nil {
     154 + return err
     155 + }
     156 + }
     157 + return nil
     158 +}
     159 + 
     160 +func (r *abstractLogicalRule) Close() error {
     161 + for _, rule := range r.rules {
     162 + err := rule.Close()
     163 + if err != nil {
     164 + return err
     165 + }
     166 + }
     167 + return nil
     168 +}
     169 + 
     170 +func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
     171 + if r.mode == C.LogicalTypeAnd {
     172 + return common.All(r.rules, func(it adapter.Rule) bool {
     173 + return it.Match(metadata)
     174 + }) != r.invert
     175 + } else {
     176 + return common.Any(r.rules, func(it adapter.Rule) bool {
     177 + return it.Match(metadata)
     178 + }) != r.invert
     179 + }
     180 +}
     181 + 
     182 +func (r *abstractLogicalRule) Outbound() string {
     183 + return r.outbound
     184 +}
     185 + 
     186 +func (r *abstractLogicalRule) String() string {
     187 + var op string
     188 + switch r.mode {
     189 + case C.LogicalTypeAnd:
     190 + op = "&&"
     191 + case C.LogicalTypeOr:
     192 + op = "||"
     193 + }
     194 + if !r.invert {
     195 + return strings.Join(F.MapToString(r.rules), " "+op+" ")
     196 + } else {
     197 + return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
     198 + }
     199 +}
     200 + 
  • ■ ■ ■ ■ ■
    route/rule.go route/rule_default.go
    1 1  package route
    2 2   
    3 3  import (
    4  - "strings"
    5  - 
    6 4   "github.com/sagernet/sing-box/adapter"
    7 5   C "github.com/sagernet/sing-box/constant"
    8 6   "github.com/sagernet/sing-box/log"
    9 7   "github.com/sagernet/sing-box/option"
    10  - "github.com/sagernet/sing/common"
    11 8   E "github.com/sagernet/sing/common/exceptions"
    12  - F "github.com/sagernet/sing/common/format"
    13  - N "github.com/sagernet/sing/common/network"
    14 9  )
    15 10   
    16 11  func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) {
    skipped 22 lines
    39 34  var _ adapter.Rule = (*DefaultRule)(nil)
    40 35   
    41 36  type DefaultRule struct {
    42  - items []RuleItem
    43  - sourceAddressItems []RuleItem
    44  - sourcePortItems []RuleItem
    45  - destinationAddressItems []RuleItem
    46  - destinationPortItems []RuleItem
    47  - allItems []RuleItem
    48  - invert bool
    49  - outbound string
     37 + abstractDefaultRule
    50 38  }
    51 39   
    52 40  type RuleItem interface {
    skipped 3 lines
    56 44   
    57 45  func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
    58 46   rule := &DefaultRule{
    59  - invert: options.Invert,
    60  - outbound: options.Outbound,
     47 + abstractDefaultRule{
     48 + invert: options.Invert,
     49 + outbound: options.Outbound,
     50 + },
    61 51   }
    62 52   if len(options.Inbound) > 0 {
    63 53   item := NewInboundRule(options.Inbound)
    skipped 10 lines
    74 64   return nil, E.New("invalid ip version: ", options.IPVersion)
    75 65   }
    76 66   }
    77  - if options.Network != "" {
    78  - switch options.Network {
    79  - case N.NetworkTCP, N.NetworkUDP:
    80  - item := NewNetworkItem(options.Network)
    81  - rule.items = append(rule.items, item)
    82  - rule.allItems = append(rule.allItems, item)
    83  - default:
    84  - return nil, E.New("invalid network: ", options.Network)
    85  - }
     67 + if len(options.Network) > 0 {
     68 + item := NewNetworkItem(options.Network)
     69 + rule.items = append(rule.items, item)
     70 + rule.allItems = append(rule.allItems, item)
    86 71   }
    87 72   if len(options.AuthUser) > 0 {
    88 73   item := NewAuthUserItem(options.AuthUser)
    skipped 113 lines
    202 187   return rule, nil
    203 188  }
    204 189   
    205  -func (r *DefaultRule) Type() string {
    206  - return C.RuleTypeDefault
    207  -}
    208  - 
    209  -func (r *DefaultRule) Start() error {
    210  - for _, item := range r.allItems {
    211  - err := common.Start(item)
    212  - if err != nil {
    213  - return err
    214  - }
    215  - }
    216  - return nil
    217  -}
    218  - 
    219  -func (r *DefaultRule) Close() error {
    220  - for _, item := range r.allItems {
    221  - err := common.Close(item)
    222  - if err != nil {
    223  - return err
    224  - }
    225  - }
    226  - return nil
    227  -}
    228  - 
    229  -func (r *DefaultRule) UpdateGeosite() error {
    230  - for _, item := range r.allItems {
    231  - if geositeItem, isSite := item.(*GeositeItem); isSite {
    232  - err := geositeItem.Update()
    233  - if err != nil {
    234  - return err
    235  - }
    236  - }
    237  - }
    238  - return nil
    239  -}
    240  - 
    241  -func (r *DefaultRule) Match(metadata *adapter.InboundContext) bool {
    242  - for _, item := range r.items {
    243  - if !item.Match(metadata) {
    244  - return r.invert
    245  - }
    246  - }
    247  - 
    248  - if len(r.sourceAddressItems) > 0 {
    249  - var sourceAddressMatch bool
    250  - for _, item := range r.sourceAddressItems {
    251  - if item.Match(metadata) {
    252  - sourceAddressMatch = true
    253  - break
    254  - }
    255  - }
    256  - if !sourceAddressMatch {
    257  - return r.invert
    258  - }
    259  - }
    260  - 
    261  - if len(r.sourcePortItems) > 0 {
    262  - var sourcePortMatch bool
    263  - for _, item := range r.sourcePortItems {
    264  - if item.Match(metadata) {
    265  - sourcePortMatch = true
    266  - break
    267  - }
    268  - }
    269  - if !sourcePortMatch {
    270  - return r.invert
    271  - }
    272  - }
    273  - 
    274  - if len(r.destinationAddressItems) > 0 {
    275  - var destinationAddressMatch bool
    276  - for _, item := range r.destinationAddressItems {
    277  - if item.Match(metadata) {
    278  - destinationAddressMatch = true
    279  - break
    280  - }
    281  - }
    282  - if !destinationAddressMatch {
    283  - return r.invert
    284  - }
    285  - }
    286  - 
    287  - if len(r.destinationPortItems) > 0 {
    288  - var destinationPortMatch bool
    289  - for _, item := range r.destinationPortItems {
    290  - if item.Match(metadata) {
    291  - destinationPortMatch = true
    292  - break
    293  - }
    294  - }
    295  - if !destinationPortMatch {
    296  - return r.invert
    297  - }
    298  - }
    299  - 
    300  - return !r.invert
    301  -}
    302  - 
    303  -func (r *DefaultRule) Outbound() string {
    304  - return r.outbound
    305  -}
    306  - 
    307  -func (r *DefaultRule) String() string {
    308  - if !r.invert {
    309  - return strings.Join(F.MapToString(r.allItems), " ")
    310  - } else {
    311  - return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")"
    312  - }
    313  -}
    314  - 
    315 190  var _ adapter.Rule = (*LogicalRule)(nil)
    316 191   
    317 192  type LogicalRule struct {
    318  - mode string
    319  - rules []*DefaultRule
    320  - invert bool
    321  - outbound string
     193 + abstractLogicalRule
    322 194  }
    323 195   
    324 196  func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
    325 197   r := &LogicalRule{
    326  - rules: make([]*DefaultRule, len(options.Rules)),
    327  - invert: options.Invert,
    328  - outbound: options.Outbound,
     198 + abstractLogicalRule{
     199 + rules: make([]adapter.Rule, len(options.Rules)),
     200 + invert: options.Invert,
     201 + outbound: options.Outbound,
     202 + },
    329 203   }
    330 204   switch options.Mode {
    331 205   case C.LogicalTypeAnd:
    skipped 13 lines
    345 219   return r, nil
    346 220  }
    347 221   
    348  -func (r *LogicalRule) Type() string {
    349  - return C.RuleTypeLogical
    350  -}
    351  - 
    352  -func (r *LogicalRule) UpdateGeosite() error {
    353  - for _, rule := range r.rules {
    354  - err := rule.UpdateGeosite()
    355  - if err != nil {
    356  - return err
    357  - }
    358  - }
    359  - return nil
    360  -}
    361  - 
    362  -func (r *LogicalRule) Start() error {
    363  - for _, rule := range r.rules {
    364  - err := rule.Start()
    365  - if err != nil {
    366  - return err
    367  - }
    368  - }
    369  - return nil
    370  -}
    371  - 
    372  -func (r *LogicalRule) Close() error {
    373  - for _, rule := range r.rules {
    374  - err := rule.Close()
    375  - if err != nil {
    376  - return err
    377  - }
    378  - }
    379  - return nil
    380  -}
    381  - 
    382  -func (r *LogicalRule) Match(metadata *adapter.InboundContext) bool {
    383  - if r.mode == C.LogicalTypeAnd {
    384  - return common.All(r.rules, func(it *DefaultRule) bool {
    385  - return it.Match(metadata)
    386  - }) != r.invert
    387  - } else {
    388  - return common.Any(r.rules, func(it *DefaultRule) bool {
    389  - return it.Match(metadata)
    390  - }) != r.invert
    391  - }
    392  -}
    393  - 
    394  -func (r *LogicalRule) Outbound() string {
    395  - return r.outbound
    396  -}
    397  - 
    398  -func (r *LogicalRule) String() string {
    399  - var op string
    400  - switch r.mode {
    401  - case C.LogicalTypeAnd:
    402  - op = "&&"
    403  - case C.LogicalTypeOr:
    404  - op = "||"
    405  - }
    406  - if !r.invert {
    407  - return strings.Join(F.MapToString(r.rules), " "+op+" ")
    408  - } else {
    409  - return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
    410  - }
    411  -}
    412  - 
  • ■ ■ ■ ■ ■
    route/rule_dns.go
    1 1  package route
    2 2   
    3 3  import (
    4  - "strings"
    5  - 
    6 4   "github.com/sagernet/sing-box/adapter"
    7 5   C "github.com/sagernet/sing-box/constant"
    8 6   "github.com/sagernet/sing-box/log"
    9 7   "github.com/sagernet/sing-box/option"
    10  - "github.com/sagernet/sing/common"
    11 8   E "github.com/sagernet/sing/common/exceptions"
    12  - F "github.com/sagernet/sing/common/format"
    13  - N "github.com/sagernet/sing/common/network"
    14 9  )
    15 10   
    16 11  func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) {
    skipped 22 lines
    39 34  var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
    40 35   
    41 36  type DefaultDNSRule struct {
    42  - items []RuleItem
    43  - sourceAddressItems []RuleItem
    44  - sourcePortItems []RuleItem
    45  - destinationAddressItems []RuleItem
    46  - destinationPortItems []RuleItem
    47  - allItems []RuleItem
    48  - invert bool
    49  - outbound string
    50  - disableCache bool
     37 + abstractDefaultRule
     38 + disableCache bool
     39 + rewriteTTL *uint32
    51 40  }
    52 41   
    53 42  func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
    54 43   rule := &DefaultDNSRule{
    55  - invert: options.Invert,
    56  - outbound: options.Server,
     44 + abstractDefaultRule: abstractDefaultRule{
     45 + invert: options.Invert,
     46 + outbound: options.Server,
     47 + },
    57 48   disableCache: options.DisableCache,
     49 + rewriteTTL: options.RewriteTTL,
    58 50   }
    59 51   if len(options.Inbound) > 0 {
    60 52   item := NewInboundRule(options.Inbound)
    skipped 15 lines
    76 68   rule.items = append(rule.items, item)
    77 69   rule.allItems = append(rule.allItems, item)
    78 70   }
    79  - if options.Network != "" {
    80  - switch options.Network {
    81  - case N.NetworkTCP, N.NetworkUDP:
    82  - item := NewNetworkItem(options.Network)
    83  - rule.items = append(rule.items, item)
    84  - rule.allItems = append(rule.allItems, item)
    85  - default:
    86  - return nil, E.New("invalid network: ", options.Network)
    87  - }
     71 + if len(options.Network) > 0 {
     72 + item := NewNetworkItem(options.Network)
     73 + rule.items = append(rule.items, item)
     74 + rule.allItems = append(rule.allItems, item)
    88 75   }
    89 76   if len(options.AuthUser) > 0 {
    90 77   item := NewAuthUserItem(options.AuthUser)
    skipped 105 lines
    196 183   return rule, nil
    197 184  }
    198 185   
    199  -func (r *DefaultDNSRule) Type() string {
    200  - return C.RuleTypeDefault
    201  -}
    202  - 
    203  -func (r *DefaultDNSRule) Start() error {
    204  - for _, item := range r.allItems {
    205  - err := common.Start(item)
    206  - if err != nil {
    207  - return err
    208  - }
    209  - }
    210  - return nil
    211  -}
    212  - 
    213  -func (r *DefaultDNSRule) Close() error {
    214  - for _, item := range r.allItems {
    215  - err := common.Close(item)
    216  - if err != nil {
    217  - return err
    218  - }
    219  - }
    220  - return nil
    221  -}
    222  - 
    223  -func (r *DefaultDNSRule) UpdateGeosite() error {
    224  - for _, item := range r.allItems {
    225  - if geositeItem, isSite := item.(*GeositeItem); isSite {
    226  - err := geositeItem.Update()
    227  - if err != nil {
    228  - return err
    229  - }
    230  - }
    231  - }
    232  - return nil
    233  -}
    234  - 
    235  -func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
    236  - for _, item := range r.items {
    237  - if !item.Match(metadata) {
    238  - return r.invert
    239  - }
    240  - }
    241  - 
    242  - if len(r.sourceAddressItems) > 0 {
    243  - var sourceAddressMatch bool
    244  - for _, item := range r.sourceAddressItems {
    245  - if item.Match(metadata) {
    246  - sourceAddressMatch = true
    247  - break
    248  - }
    249  - }
    250  - if !sourceAddressMatch {
    251  - return r.invert
    252  - }
    253  - }
    254  - 
    255  - if len(r.sourcePortItems) > 0 {
    256  - var sourcePortMatch bool
    257  - for _, item := range r.sourcePortItems {
    258  - if item.Match(metadata) {
    259  - sourcePortMatch = true
    260  - break
    261  - }
    262  - }
    263  - if !sourcePortMatch {
    264  - return r.invert
    265  - }
    266  - }
    267  - 
    268  - if len(r.destinationAddressItems) > 0 {
    269  - var destinationAddressMatch bool
    270  - for _, item := range r.destinationAddressItems {
    271  - if item.Match(metadata) {
    272  - destinationAddressMatch = true
    273  - break
    274  - }
    275  - }
    276  - if !destinationAddressMatch {
    277  - return r.invert
    278  - }
    279  - }
    280  - 
    281  - if len(r.destinationPortItems) > 0 {
    282  - var destinationPortMatch bool
    283  - for _, item := range r.destinationPortItems {
    284  - if item.Match(metadata) {
    285  - destinationPortMatch = true
    286  - break
    287  - }
    288  - }
    289  - if !destinationPortMatch {
    290  - return r.invert
    291  - }
    292  - }
    293  - 
    294  - return !r.invert
    295  -}
    296  - 
    297  -func (r *DefaultDNSRule) Outbound() string {
    298  - return r.outbound
    299  -}
    300  - 
    301 186  func (r *DefaultDNSRule) DisableCache() bool {
    302 187   return r.disableCache
    303 188  }
    304 189   
    305  -func (r *DefaultDNSRule) String() string {
    306  - return strings.Join(F.MapToString(r.allItems), " ")
     190 +func (r *DefaultDNSRule) RewriteTTL() *uint32 {
     191 + return r.rewriteTTL
    307 192  }
    308 193   
    309 194  var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
    310 195   
    311 196  type LogicalDNSRule struct {
    312  - mode string
    313  - rules []*DefaultDNSRule
    314  - invert bool
    315  - outbound string
     197 + abstractLogicalRule
    316 198   disableCache bool
     199 + rewriteTTL *uint32
    317 200  }
    318 201   
    319 202  func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
    320 203   r := &LogicalDNSRule{
    321  - rules: make([]*DefaultDNSRule, len(options.Rules)),
    322  - invert: options.Invert,
    323  - outbound: options.Server,
     204 + abstractLogicalRule: abstractLogicalRule{
     205 + rules: make([]adapter.Rule, len(options.Rules)),
     206 + invert: options.Invert,
     207 + outbound: options.Server,
     208 + },
    324 209   disableCache: options.DisableCache,
     210 + rewriteTTL: options.RewriteTTL,
    325 211   }
    326 212   switch options.Mode {
    327 213   case C.LogicalTypeAnd:
    skipped 13 lines
    341 227   return r, nil
    342 228  }
    343 229   
    344  -func (r *LogicalDNSRule) Type() string {
    345  - return C.RuleTypeLogical
    346  -}
    347  - 
    348  -func (r *LogicalDNSRule) UpdateGeosite() error {
    349  - for _, rule := range r.rules {
    350  - err := rule.UpdateGeosite()
    351  - if err != nil {
    352  - return err
    353  - }
    354  - }
    355  - return nil
    356  -}
    357  - 
    358  -func (r *LogicalDNSRule) Start() error {
    359  - for _, rule := range r.rules {
    360  - err := rule.Start()
    361  - if err != nil {
    362  - return err
    363  - }
    364  - }
    365  - return nil
    366  -}
    367  - 
    368  -func (r *LogicalDNSRule) Close() error {
    369  - for _, rule := range r.rules {
    370  - err := rule.Close()
    371  - if err != nil {
    372  - return err
    373  - }
    374  - }
    375  - return nil
    376  -}
    377  - 
    378  -func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
    379  - if r.mode == C.LogicalTypeAnd {
    380  - return common.All(r.rules, func(it *DefaultDNSRule) bool {
    381  - return it.Match(metadata)
    382  - }) != r.invert
    383  - } else {
    384  - return common.Any(r.rules, func(it *DefaultDNSRule) bool {
    385  - return it.Match(metadata)
    386  - }) != r.invert
    387  - }
    388  -}
    389  - 
    390  -func (r *LogicalDNSRule) Outbound() string {
    391  - return r.outbound
    392  -}
    393  - 
    394 230  func (r *LogicalDNSRule) DisableCache() bool {
    395 231   return r.disableCache
    396 232  }
    397 233   
    398  -func (r *LogicalDNSRule) String() string {
    399  - var op string
    400  - switch r.mode {
    401  - case C.LogicalTypeAnd:
    402  - op = "&&"
    403  - case C.LogicalTypeOr:
    404  - op = "||"
    405  - }
    406  - if !r.invert {
    407  - return strings.Join(F.MapToString(r.rules), " "+op+" ")
    408  - } else {
    409  - return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
    410  - }
     234 +func (r *LogicalDNSRule) RewriteTTL() *uint32 {
     235 + return r.rewriteTTL
    411 236  }
    412 237   
  • ■ ■ ■ ■ ■ ■
    route/rule_ip.go
     1 +package route
     2 + 
     3 +import (
     4 + "github.com/sagernet/sing-box/adapter"
     5 + C "github.com/sagernet/sing-box/constant"
     6 + "github.com/sagernet/sing-box/log"
     7 + "github.com/sagernet/sing-box/option"
     8 + "github.com/sagernet/sing-tun"
     9 + "github.com/sagernet/sing/common"
     10 + E "github.com/sagernet/sing/common/exceptions"
     11 +)
     12 + 
     13 +func NewIPRule(router adapter.Router, logger log.ContextLogger, options option.IPRule) (adapter.IPRule, error) {
     14 + switch options.Type {
     15 + case "", C.RuleTypeDefault:
     16 + if !options.DefaultOptions.IsValid() {
     17 + return nil, E.New("missing conditions")
     18 + }
     19 + if common.IsEmpty(options.DefaultOptions.Action) {
     20 + return nil, E.New("missing action")
     21 + }
     22 + return NewDefaultIPRule(router, logger, options.DefaultOptions)
     23 + case C.RuleTypeLogical:
     24 + if !options.LogicalOptions.IsValid() {
     25 + return nil, E.New("missing conditions")
     26 + }
     27 + if common.IsEmpty(options.DefaultOptions.Action) {
     28 + return nil, E.New("missing action")
     29 + }
     30 + return NewLogicalIPRule(router, logger, options.LogicalOptions)
     31 + default:
     32 + return nil, E.New("unknown rule type: ", options.Type)
     33 + }
     34 +}
     35 + 
     36 +var _ adapter.IPRule = (*DefaultIPRule)(nil)
     37 + 
     38 +type DefaultIPRule struct {
     39 + abstractDefaultRule
     40 + action tun.ActionType
     41 +}
     42 + 
     43 +func NewDefaultIPRule(router adapter.Router, logger log.ContextLogger, options option.DefaultIPRule) (*DefaultIPRule, error) {
     44 + rule := &DefaultIPRule{
     45 + abstractDefaultRule: abstractDefaultRule{
     46 + invert: options.Invert,
     47 + outbound: options.Outbound,
     48 + },
     49 + action: tun.ActionType(options.Action),
     50 + }
     51 + if len(options.Inbound) > 0 {
     52 + item := NewInboundRule(options.Inbound)
     53 + rule.items = append(rule.items, item)
     54 + rule.allItems = append(rule.allItems, item)
     55 + }
     56 + if options.IPVersion > 0 {
     57 + switch options.IPVersion {
     58 + case 4, 6:
     59 + item := NewIPVersionItem(options.IPVersion == 6)
     60 + rule.items = append(rule.items, item)
     61 + rule.allItems = append(rule.allItems, item)
     62 + default:
     63 + return nil, E.New("invalid ip version: ", options.IPVersion)
     64 + }
     65 + }
     66 + if len(options.Network) > 0 {
     67 + item := NewNetworkItem(options.Network)
     68 + rule.items = append(rule.items, item)
     69 + rule.allItems = append(rule.allItems, item)
     70 + }
     71 + if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
     72 + item := NewDomainItem(options.Domain, options.DomainSuffix)
     73 + rule.destinationAddressItems = append(rule.destinationAddressItems, item)
     74 + rule.allItems = append(rule.allItems, item)
     75 + }
     76 + if len(options.DomainKeyword) > 0 {
     77 + item := NewDomainKeywordItem(options.DomainKeyword)
     78 + rule.destinationAddressItems = append(rule.destinationAddressItems, item)
     79 + rule.allItems = append(rule.allItems, item)
     80 + }
     81 + if len(options.DomainRegex) > 0 {
     82 + item, err := NewDomainRegexItem(options.DomainRegex)
     83 + if err != nil {
     84 + return nil, E.Cause(err, "domain_regex")
     85 + }
     86 + rule.destinationAddressItems = append(rule.destinationAddressItems, item)
     87 + rule.allItems = append(rule.allItems, item)
     88 + }
     89 + if len(options.Geosite) > 0 {
     90 + item := NewGeositeItem(router, logger, options.Geosite)
     91 + rule.destinationAddressItems = append(rule.destinationAddressItems, item)
     92 + rule.allItems = append(rule.allItems, item)
     93 + }
     94 + if len(options.SourceGeoIP) > 0 {
     95 + item := NewGeoIPItem(router, logger, true, options.SourceGeoIP)
     96 + rule.sourceAddressItems = append(rule.sourceAddressItems, item)
     97 + rule.allItems = append(rule.allItems, item)
     98 + }
     99 + if len(options.GeoIP) > 0 {
     100 + item := NewGeoIPItem(router, logger, false, options.GeoIP)
     101 + rule.destinationAddressItems = append(rule.destinationAddressItems, item)
     102 + rule.allItems = append(rule.allItems, item)
     103 + }
     104 + if len(options.SourceIPCIDR) > 0 {
     105 + item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
     106 + if err != nil {
     107 + return nil, E.Cause(err, "source_ipcidr")
     108 + }
     109 + rule.sourceAddressItems = append(rule.sourceAddressItems, item)
     110 + rule.allItems = append(rule.allItems, item)
     111 + }
     112 + if len(options.IPCIDR) > 0 {
     113 + item, err := NewIPCIDRItem(false, options.IPCIDR)
     114 + if err != nil {
     115 + return nil, E.Cause(err, "ipcidr")
     116 + }
     117 + rule.destinationAddressItems = append(rule.destinationAddressItems, item)
     118 + rule.allItems = append(rule.allItems, item)
     119 + }
     120 + if len(options.SourcePort) > 0 {
     121 + item := NewPortItem(true, options.SourcePort)
     122 + rule.sourcePortItems = append(rule.sourcePortItems, item)
     123 + rule.allItems = append(rule.allItems, item)
     124 + }
     125 + if len(options.SourcePortRange) > 0 {
     126 + item, err := NewPortRangeItem(true, options.SourcePortRange)
     127 + if err != nil {
     128 + return nil, E.Cause(err, "source_port_range")
     129 + }
     130 + rule.sourcePortItems = append(rule.sourcePortItems, item)
     131 + rule.allItems = append(rule.allItems, item)
     132 + }
     133 + if len(options.Port) > 0 {
     134 + item := NewPortItem(false, options.Port)
     135 + rule.destinationPortItems = append(rule.destinationPortItems, item)
     136 + rule.allItems = append(rule.allItems, item)
     137 + }
     138 + if len(options.PortRange) > 0 {
     139 + item, err := NewPortRangeItem(false, options.PortRange)
     140 + if err != nil {
     141 + return nil, E.Cause(err, "port_range")
     142 + }
     143 + rule.destinationPortItems = append(rule.destinationPortItems, item)
     144 + rule.allItems = append(rule.allItems, item)
     145 + }
     146 + return rule, nil
     147 +}
     148 + 
     149 +func (r *DefaultIPRule) Action() tun.ActionType {
     150 + return r.action
     151 +}
     152 + 
     153 +var _ adapter.IPRule = (*LogicalIPRule)(nil)
     154 + 
     155 +type LogicalIPRule struct {
     156 + abstractLogicalRule
     157 + action tun.ActionType
     158 +}
     159 + 
     160 +func NewLogicalIPRule(router adapter.Router, logger log.ContextLogger, options option.LogicalIPRule) (*LogicalIPRule, error) {
     161 + r := &LogicalIPRule{
     162 + abstractLogicalRule: abstractLogicalRule{
     163 + rules: make([]adapter.Rule, len(options.Rules)),
     164 + invert: options.Invert,
     165 + outbound: options.Outbound,
     166 + },
     167 + action: tun.ActionType(options.Action),
     168 + }
     169 + switch options.Mode {
     170 + case C.LogicalTypeAnd:
     171 + r.mode = C.LogicalTypeAnd
     172 + case C.LogicalTypeOr:
     173 + r.mode = C.LogicalTypeOr
     174 + default:
     175 + return nil, E.New("unknown logical mode: ", options.Mode)
     176 + }
     177 + for i, subRule := range options.Rules {
     178 + rule, err := NewDefaultIPRule(router, logger, subRule)
     179 + if err != nil {
     180 + return nil, E.Cause(err, "sub rule[", i, "]")
     181 + }
     182 + r.rules[i] = rule
     183 + }
     184 + return r, nil
     185 +}
     186 + 
     187 +func (r *LogicalIPRule) Action() tun.ActionType {
     188 + return r.action
     189 +}
     190 + 
  • route/rule_auth_user.go route/rule_item_auth_user.go
    Content is identical
  • route/rule_cidr.go route/rule_item_cidr.go
    Content is identical
  • route/rule_clash_mode.go route/rule_item_clash_mode.go
    Content is identical
  • route/rule_domain.go route/rule_item_domain.go
    Content is identical
  • route/rule_domain_keyword.go route/rule_item_domain_keyword.go
    Content is identical
  • route/rule_domain_regex.go route/rule_item_domain_regex.go
    Content is identical
  • route/rule_geoip.go route/rule_item_geoip.go
    Content is identical
  • route/rule_geosite.go route/rule_item_geosite.go
    Content is identical
  • route/rule_inbound.go route/rule_item_inbound.go
    Content is identical
  • route/rule_ipversion.go route/rule_item_ipversion.go
    Content is identical
  • ■ ■ ■ ■ ■ ■
    route/rule_item_network.go
     1 +package route
     2 + 
     3 +import (
     4 + "strings"
     5 + 
     6 + "github.com/sagernet/sing-box/adapter"
     7 + F "github.com/sagernet/sing/common/format"
     8 +)
     9 + 
     10 +var _ RuleItem = (*NetworkItem)(nil)
     11 + 
     12 +type NetworkItem struct {
     13 + networks []string
     14 + networkMap map[string]bool
     15 +}
     16 + 
     17 +func NewNetworkItem(networks []string) *NetworkItem {
     18 + networkMap := make(map[string]bool)
     19 + for _, network := range networks {
     20 + networkMap[network] = true
     21 + }
     22 + return &NetworkItem{
     23 + networks: networks,
     24 + networkMap: networkMap,
     25 + }
     26 +}
     27 + 
     28 +func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
     29 + return r.networkMap[metadata.Network]
     30 +}
     31 + 
     32 +func (r *NetworkItem) String() string {
     33 + description := "network="
     34 + 
     35 + pLen := len(r.networks)
     36 + if pLen == 1 {
     37 + description += F.ToString(r.networks[0])
     38 + } else {
     39 + description += "[" + strings.Join(F.MapToString(r.networks), " ") + "]"
     40 + }
     41 + return description
     42 +}
     43 + 
  • route/rule_outbound.go route/rule_item_outbound.go
    Content is identical
  • route/rule_package_name.go route/rule_item_package_name.go
    Content is identical
  • route/rule_port.go route/rule_item_port.go
    Content is identical
  • route/rule_port_range.go route/rule_item_port_range.go
    Content is identical
  • route/rule_process_name.go route/rule_item_process_name.go
    Content is identical
  • route/rule_process_path.go route/rule_item_process_path.go
    Content is identical
  • route/rule_protocol.go route/rule_item_protocol.go
    Content is identical
  • route/rule_query_type.go route/rule_item_query_type.go
    Content is identical
  • route/rule_user.go route/rule_item_user.go
    Content is identical
  • route/rule_user_id.go route/rule_item_user_id.go
    Content is identical
  • ■ ■ ■ ■ ■ ■
    route/rule_network.go
    1  -package route
    2  - 
    3  -import (
    4  - "github.com/sagernet/sing-box/adapter"
    5  -)
    6  - 
    7  -var _ RuleItem = (*NetworkItem)(nil)
    8  - 
    9  -type NetworkItem struct {
    10  - network string
    11  -}
    12  - 
    13  -func NewNetworkItem(network string) *NetworkItem {
    14  - return &NetworkItem{network}
    15  -}
    16  - 
    17  -func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
    18  - return r.network == metadata.Network
    19  -}
    20  - 
    21  -func (r *NetworkItem) String() string {
    22  - return "network=" + r.network
    23  -}
    24  - 
  • ■ ■ ■ ■ ■
    transport/wireguard/device.go
    1 1  package wireguard
    2 2   
    3 3  import (
     4 + "net/netip"
     5 + 
     6 + "github.com/sagernet/sing-tun"
    4 7   N "github.com/sagernet/sing/common/network"
    5  - "github.com/sagernet/wireguard-go/tun"
     8 + wgTun "github.com/sagernet/wireguard-go/tun"
    6 9  )
    7 10   
    8 11  type Device interface {
    9  - tun.Device
     12 + wgTun.Device
    10 13   N.Dialer
    11 14   Start() error
     15 + Inet4Address() netip.Addr
     16 + Inet6Address() netip.Addr
    12 17   // NewEndpoint() (stack.LinkEndpoint, error)
    13 18  }
    14 19   
     20 +type NatDevice interface {
     21 + Device
     22 + CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination
     23 +}
     24 + 
  • ■ ■ ■ ■ ■ ■
    transport/wireguard/device_nat.go
     1 +package wireguard
     2 + 
     3 +import (
     4 + "github.com/sagernet/sing-tun"
     5 + "github.com/sagernet/sing/common/buf"
     6 +)
     7 + 
     8 +var _ Device = (*natDeviceWrapper)(nil)
     9 + 
     10 +type natDeviceWrapper struct {
     11 + Device
     12 + outbound chan *buf.Buffer
     13 + mapping *tun.NatMapping
     14 + writer *tun.NatWriter
     15 +}
     16 + 
     17 +func NewNATDevice(upstream Device, ipRewrite bool) NatDevice {
     18 + wrapper := &natDeviceWrapper{
     19 + Device: upstream,
     20 + outbound: make(chan *buf.Buffer, 256),
     21 + mapping: tun.NewNatMapping(ipRewrite),
     22 + }
     23 + if ipRewrite {
     24 + wrapper.writer = tun.NewNatWriter(upstream.Inet4Address(), upstream.Inet6Address())
     25 + }
     26 + return wrapper
     27 +}
     28 + 
     29 +func (d *natDeviceWrapper) Read(p []byte, offset int) (int, error) {
     30 + select {
     31 + case packet := <-d.outbound:
     32 + defer packet.Release()
     33 + return copy(p[offset:], packet.Bytes()), nil
     34 + default:
     35 + }
     36 + return d.Device.Read(p, offset)
     37 +}
     38 + 
     39 +func (d *natDeviceWrapper) Write(p []byte, offset int) (int, error) {
     40 + packet := p[offset:]
     41 + handled, err := d.mapping.WritePacket(packet)
     42 + if handled {
     43 + return len(packet), err
     44 + }
     45 + return d.Device.Write(p, offset)
     46 +}
     47 + 
     48 +func (d *natDeviceWrapper) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
     49 + d.mapping.CreateSession(session, conn)
     50 + return &natDestinationWrapper{d, session}
     51 +}
     52 + 
     53 +var _ tun.DirectDestination = (*natDestinationWrapper)(nil)
     54 + 
     55 +type natDestinationWrapper struct {
     56 + device *natDeviceWrapper
     57 + session tun.RouteSession
     58 +}
     59 + 
     60 +func (d *natDestinationWrapper) WritePacket(buffer *buf.Buffer) error {
     61 + if d.device.writer != nil {
     62 + d.device.writer.RewritePacket(buffer.Bytes())
     63 + }
     64 + d.device.outbound <- buffer
     65 + return nil
     66 +}
     67 + 
     68 +func (d *natDestinationWrapper) Close() error {
     69 + d.device.mapping.DeleteSession(d.session)
     70 + return nil
     71 +}
     72 + 
     73 +func (d *natDestinationWrapper) Timeout() bool {
     74 + return false
     75 +}
     76 + 
  • ■ ■ ■ ■ ■ ■
    transport/wireguard/device_nat_gvisor.go
     1 +//go:build with_gvisor
     2 + 
     3 +package wireguard
     4 + 
     5 +import (
     6 + "github.com/sagernet/sing/common"
     7 + "github.com/sagernet/sing/common/buf"
     8 + 
     9 + "gvisor.dev/gvisor/pkg/tcpip/stack"
     10 +)
     11 + 
     12 +func (d *natDestinationWrapper) WritePacketBuffer(buffer *stack.PacketBuffer) error {
     13 + defer buffer.DecRef()
     14 + if d.device.writer != nil {
     15 + d.device.writer.RewritePacketBuffer(buffer)
     16 + }
     17 + var packetLen int
     18 + for _, slice := range buffer.AsSlices() {
     19 + packetLen += len(slice)
     20 + }
     21 + packet := buf.NewSize(packetLen)
     22 + for _, slice := range buffer.AsSlices() {
     23 + common.Must1(packet.Write(slice))
     24 + }
     25 + d.device.outbound <- packet
     26 + return nil
     27 +}
     28 + 
  • ■ ■ ■ ■ ■
    transport/wireguard/device_stack.go
    skipped 7 lines
    8 8   "net/netip"
    9 9   "os"
    10 10   
     11 + "github.com/sagernet/sing-tun"
     12 + "github.com/sagernet/sing/common/buf"
    11 13   E "github.com/sagernet/sing/common/exceptions"
    12 14   M "github.com/sagernet/sing/common/metadata"
    13 15   N "github.com/sagernet/sing/common/network"
    14  - "github.com/sagernet/wireguard-go/tun"
     16 + wgTun "github.com/sagernet/wireguard-go/tun"
    15 17   
    16 18   "gvisor.dev/gvisor/pkg/bufferv2"
    17 19   "gvisor.dev/gvisor/pkg/tcpip"
    skipped 7 lines
    25 27   "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
    26 28  )
    27 29   
    28  -var _ Device = (*StackDevice)(nil)
     30 +var _ NatDevice = (*StackDevice)(nil)
    29 31   
    30 32  const defaultNIC tcpip.NICID = 1
    31 33   
    32 34  type StackDevice struct {
    33  - stack *stack.Stack
    34  - mtu uint32
    35  - events chan tun.Event
    36  - outbound chan *stack.PacketBuffer
    37  - done chan struct{}
    38  - dispatcher stack.NetworkDispatcher
    39  - addr4 tcpip.Address
    40  - addr6 tcpip.Address
     35 + stack *stack.Stack
     36 + mtu uint32
     37 + events chan wgTun.Event
     38 + outbound chan *stack.PacketBuffer
     39 + packetOutbound chan *buf.Buffer
     40 + done chan struct{}
     41 + dispatcher stack.NetworkDispatcher
     42 + addr4 tcpip.Address
     43 + addr6 tcpip.Address
     44 + mapping *tun.NatMapping
     45 + writer *tun.NatWriter
    41 46  }
    42 47   
    43  -func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, error) {
     48 +func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (*StackDevice, error) {
    44 49   ipStack := stack.New(stack.Options{
    45 50   NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
    46 51   TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
    47 52   HandleLocal: true,
    48 53   })
    49 54   tunDevice := &StackDevice{
    50  - stack: ipStack,
    51  - mtu: mtu,
    52  - events: make(chan tun.Event, 1),
    53  - outbound: make(chan *stack.PacketBuffer, 256),
    54  - done: make(chan struct{}),
     55 + stack: ipStack,
     56 + mtu: mtu,
     57 + events: make(chan wgTun.Event, 1),
     58 + outbound: make(chan *stack.PacketBuffer, 256),
     59 + packetOutbound: make(chan *buf.Buffer, 256),
     60 + done: make(chan struct{}),
     61 + mapping: tun.NewNatMapping(ipRewrite),
    55 62   }
    56 63   err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
    57 64   if err != nil {
    skipped 18 lines
    76 83   if err != nil {
    77 84   return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
    78 85   }
     86 + }
     87 + if ipRewrite {
     88 + tunDevice.writer = tun.NewNatWriter(tunDevice.Inet4Address(), tunDevice.Inet6Address())
    79 89   }
    80 90   sOpt := tcpip.TCPSACKEnabled(true)
    81 91   ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
    skipped 60 lines
    142 152   return nil, err
    143 153   }
    144 154   return udpConn, nil
     155 +}
     156 + 
     157 +func (w *StackDevice) Inet4Address() netip.Addr {
     158 + return M.AddrFromIP(net.IP(w.addr4))
     159 +}
     160 + 
     161 +func (w *StackDevice) Inet6Address() netip.Addr {
     162 + return M.AddrFromIP(net.IP(w.addr6))
    145 163  }
    146 164   
    147 165  func (w *StackDevice) Start() error {
    148  - w.events <- tun.EventUp
     166 + w.events <- wgTun.EventUp
    149 167   return nil
    150 168  }
    151 169   
    skipped 13 lines
    165 183   n += copy(p[n:], slice)
    166 184   }
    167 185   return
     186 + case packet := <-w.packetOutbound:
     187 + defer packet.Release()
     188 + n = copy(p[offset:], packet.Bytes())
     189 + return
    168 190   case <-w.done:
    169 191   return 0, os.ErrClosed
    170 192   }
    skipped 4 lines
    175 197   if len(p) == 0 {
    176 198   return
    177 199   }
     200 + handled, err := w.mapping.WritePacket(p)
     201 + if handled {
     202 + return len(p), err
     203 + }
    178 204   var networkProtocol tcpip.NetworkProtocolNumber
    179 205   switch header.IPVersion(p) {
    180 206   case header.IPv4Version:
    skipped 22 lines
    203 229   return "sing-box", nil
    204 230  }
    205 231   
    206  -func (w *StackDevice) Events() chan tun.Event {
     232 +func (w *StackDevice) Events() chan wgTun.Event {
    207 233   return w.events
    208 234  }
    209 235   
    skipped 10 lines
    220 246   w.stack.Wait()
    221 247   close(w.done)
    222 248   return nil
     249 +}
     250 + 
     251 +func (w *StackDevice) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
     252 + w.mapping.CreateSession(session, conn)
     253 + return &stackNatDestination{
     254 + device: w,
     255 + session: session,
     256 + }
     257 +}
     258 + 
     259 +type stackNatDestination struct {
     260 + device *StackDevice
     261 + session tun.RouteSession
     262 +}
     263 + 
     264 +func (d *stackNatDestination) WritePacket(buffer *buf.Buffer) error {
     265 + if d.device.writer != nil {
     266 + d.device.writer.RewritePacket(buffer.Bytes())
     267 + }
     268 + d.device.packetOutbound <- buffer
     269 + return nil
     270 +}
     271 + 
     272 +func (d *stackNatDestination) WritePacketBuffer(buffer *stack.PacketBuffer) error {
     273 + if d.device.writer != nil {
     274 + d.device.writer.RewritePacketBuffer(buffer)
     275 + }
     276 + d.device.outbound <- buffer
     277 + return nil
     278 +}
     279 + 
     280 +func (d *stackNatDestination) Close() error {
     281 + d.device.mapping.DeleteSession(d.session)
     282 + return nil
     283 +}
     284 + 
     285 +func (d *stackNatDestination) Timeout() bool {
     286 + return false
    223 287  }
    224 288   
    225 289  var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
    skipped 49 lines
  • ■ ■ ■ ■
    transport/wireguard/device_stack_stub.go
    skipped 7 lines
    8 8   "github.com/sagernet/sing-tun"
    9 9  )
    10 10   
    11  -func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) {
     11 +func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (Device, error) {
    12 12   return nil, tun.ErrGVisorNotIncluded
    13 13  }
    14 14   
  • ■ ■ ■ ■ ■
    transport/wireguard/device_system.go
    skipped 22 lines
    23 23   name string
    24 24   mtu int
    25 25   events chan wgTun.Event
     26 + addr4 netip.Addr
     27 + addr6 netip.Addr
    26 28  }
    27 29   
    28 30  /*func (w *SystemDevice) NewEndpoint() (stack.LinkEndpoint, error) {
    skipped 26 lines
    55 57   if err != nil {
    56 58   return nil, err
    57 59   }
     60 + var inet4Address netip.Addr
     61 + var inet6Address netip.Addr
     62 + if len(inet4Addresses) > 0 {
     63 + inet4Address = inet4Addresses[0].Addr()
     64 + }
     65 + if len(inet6Addresses) > 0 {
     66 + inet6Address = inet6Addresses[0].Addr()
     67 + }
    58 68   return &SystemDevice{
    59  - dialer.NewDefault(router, option.DialerOptions{
     69 + dialer: dialer.NewDefault(router, option.DialerOptions{
    60 70   BindInterface: interfaceName,
    61 71   }),
    62  - tunInterface, interfaceName, int(mtu), make(chan wgTun.Event),
     72 + device: tunInterface,
     73 + name: interfaceName,
     74 + mtu: int(mtu),
     75 + events: make(chan wgTun.Event),
     76 + addr4: inet4Address,
     77 + addr6: inet6Address,
    63 78   }, nil
    64 79  }
    65 80   
    skipped 3 lines
    69 84   
    70 85  func (w *SystemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
    71 86   return w.dialer.ListenPacket(ctx, destination)
     87 +}
     88 + 
     89 +func (w *SystemDevice) Inet4Address() netip.Addr {
     90 + return w.addr4
     91 +}
     92 + 
     93 +func (w *SystemDevice) Inet6Address() netip.Addr {
     94 + return w.addr6
    72 95  }
    73 96   
    74 97  func (w *SystemDevice) Start() error {
    skipped 5 lines
    80 103   return nil
    81 104  }
    82 105   
    83  -func (w *SystemDevice) Read(bytes []byte, index int) (int, error) {
    84  - return w.device.Read(bytes[index-tun.PacketOffset:])
     106 +func (w *SystemDevice) Read(p []byte, offset int) (int, error) {
     107 + return w.device.Read(p[offset-tun.PacketOffset:])
    85 108  }
    86 109   
    87  -func (w *SystemDevice) Write(bytes []byte, index int) (int, error) {
    88  - return w.device.Write(bytes[index:])
     110 +func (w *SystemDevice) Write(p []byte, offset int) (int, error) {
     111 + return w.device.Write(p[offset:])
    89 112  }
    90 113   
    91 114  func (w *SystemDevice) Flush() error {
    skipped 19 lines
Please wait...
Page is in error, reload to recover