mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
6 Commits
cloudflare
...
v1.14.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f2316aff0 | ||
|
|
1b828083a1 | ||
|
|
8280a9be13 | ||
|
|
503e4d8551 | ||
|
|
7f34daa2d2 | ||
|
|
e26e0d7be7 |
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
||||
cba7b9ac0399055aa49fbdc57c03c374f58e1597
|
||||
d181863d6a4aa2e7bb7eaf67c1d512c5e4827fde
|
||||
|
||||
@@ -2,6 +2,7 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
@@ -82,6 +83,8 @@ type InboundContext struct {
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
SourceMACAddress net.HardwareAddr
|
||||
SourceHostname string
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
|
||||
|
||||
23
adapter/neighbor.go
Normal file
23
adapter/neighbor.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type NeighborEntry struct {
|
||||
Address netip.Addr
|
||||
MACAddress net.HardwareAddr
|
||||
Hostname string
|
||||
}
|
||||
|
||||
type NeighborResolver interface {
|
||||
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
|
||||
LookupHostname(address netip.Addr) (string, bool)
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type NeighborUpdateListener interface {
|
||||
UpdateNeighborTable(entries []NeighborEntry)
|
||||
}
|
||||
@@ -36,6 +36,10 @@ type PlatformInterface interface {
|
||||
|
||||
UsePlatformNotification() bool
|
||||
SendNotification(notification *Notification) error
|
||||
|
||||
UsePlatformNeighborResolver() bool
|
||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
||||
}
|
||||
|
||||
type FindConnectionOwnerRequest struct {
|
||||
|
||||
@@ -26,6 +26,8 @@ type Router interface {
|
||||
RuleSet(tag string) (RuleSet, bool)
|
||||
Rules() []Rule
|
||||
NeedFindProcess() bool
|
||||
NeedFindNeighbor() bool
|
||||
NeighborResolver() NeighborResolver
|
||||
AppendTracker(tracker ConnectionTracker)
|
||||
ResetNetwork()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,31 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.14.0-alpha.1
|
||||
|
||||
* Add `source_mac_address` and `source_hostname` rule items **1**
|
||||
* Add `include_mac_address` and `exclude_mac_address` TUN options **2**
|
||||
* Update NaiveProxy to 145.0.7632.159 **3**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
New rule items for matching LAN devices by MAC address and hostname via neighbor resolution.
|
||||
Supported on Linux, macOS, or in graphical clients on Android and macOS.
|
||||
|
||||
See [Route Rule](/configuration/route/rule/#source_mac_address), [DNS Rule](/configuration/dns/rule/#source_mac_address) and [Neighbor Resolution](/configuration/shared/neighbor/).
|
||||
|
||||
**2**:
|
||||
|
||||
Limit or exclude devices from TUN routing by MAC address.
|
||||
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
See [TUN](/configuration/inbound/tun/#include_mac_address).
|
||||
|
||||
**3**:
|
||||
|
||||
This is not an official update from NaiveProxy. Instead, it's a Chromium codebase update maintained by Project S.
|
||||
|
||||
#### 1.13.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.0"
|
||||
|
||||
:material-plus: [interface_address](#interface_address)
|
||||
@@ -149,6 +154,12 @@ icon: material/alert-decagram
|
||||
"default_interface_address": [
|
||||
"2000::/3"
|
||||
],
|
||||
"source_mac_address": [
|
||||
"00:11:22:33:44:55"
|
||||
],
|
||||
"source_hostname": [
|
||||
"my-device"
|
||||
],
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -408,6 +419,26 @@ Matches network interface (same values as `network_type`) address.
|
||||
|
||||
Match default interface address.
|
||||
|
||||
#### source_mac_address
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
||||
|
||||
Match source device MAC address.
|
||||
|
||||
#### source_hostname
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
||||
|
||||
Match source device hostname from DHCP leases.
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
:material-plus: [interface_address](#interface_address)
|
||||
@@ -149,6 +154,12 @@ icon: material/alert-decagram
|
||||
"default_interface_address": [
|
||||
"2000::/3"
|
||||
],
|
||||
"source_mac_address": [
|
||||
"00:11:22:33:44:55"
|
||||
],
|
||||
"source_hostname": [
|
||||
"my-device"
|
||||
],
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -407,6 +418,26 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
匹配默认接口地址。
|
||||
|
||||
#### source_mac_address
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
||||
|
||||
匹配源设备 MAC 地址。
|
||||
|
||||
#### source_hostname
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
||||
|
||||
匹配源设备从 DHCP 租约获取的主机名。
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [include_mac_address](#include_mac_address)
|
||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.0"
|
||||
|
||||
:material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)
|
||||
@@ -125,6 +130,12 @@ icon: material/new-box
|
||||
"exclude_package": [
|
||||
"com.android.captiveportallogin"
|
||||
],
|
||||
"include_mac_address": [
|
||||
"00:11:22:33:44:55"
|
||||
],
|
||||
"exclude_mac_address": [
|
||||
"66:77:88:99:aa:bb"
|
||||
],
|
||||
"platform": {
|
||||
"http_proxy": {
|
||||
"enabled": false,
|
||||
@@ -548,6 +559,30 @@ Limit android packages in route.
|
||||
|
||||
Exclude android packages in route.
|
||||
|
||||
#### include_mac_address
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Limit MAC addresses in route. Not limited by default.
|
||||
|
||||
Conflict with `exclude_mac_address`.
|
||||
|
||||
#### exclude_mac_address
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Exclude MAC addresses in route.
|
||||
|
||||
Conflict with `include_mac_address`.
|
||||
|
||||
#### platform
|
||||
|
||||
Platform-specific settings, provided by client applications.
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [include_mac_address](#include_mac_address)
|
||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
:material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)
|
||||
@@ -126,6 +131,12 @@ icon: material/new-box
|
||||
"exclude_package": [
|
||||
"com.android.captiveportallogin"
|
||||
],
|
||||
"include_mac_address": [
|
||||
"00:11:22:33:44:55"
|
||||
],
|
||||
"exclude_mac_address": [
|
||||
"66:77:88:99:aa:bb"
|
||||
],
|
||||
"platform": {
|
||||
"http_proxy": {
|
||||
"enabled": false,
|
||||
@@ -536,6 +547,30 @@ TCP/IP 栈。
|
||||
|
||||
排除路由的 Android 应用包名。
|
||||
|
||||
#### include_mac_address
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 `auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
限制被路由的 MAC 地址。默认不限制。
|
||||
|
||||
与 `exclude_mac_address` 冲突。
|
||||
|
||||
#### exclude_mac_address
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 `auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
排除路由的 MAC 地址。
|
||||
|
||||
与 `include_mac_address` 冲突。
|
||||
|
||||
#### platform
|
||||
|
||||
平台特定的设置,由客户端应用提供。
|
||||
|
||||
@@ -4,6 +4,11 @@ icon: material/alert-decagram
|
||||
|
||||
# Route
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [find_neighbor](#find_neighbor)
|
||||
:material-plus: [dhcp_lease_files](#dhcp_lease_files)
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||
@@ -35,6 +40,9 @@ icon: material/alert-decagram
|
||||
"override_android_vpn": false,
|
||||
"default_interface": "",
|
||||
"default_mark": 0,
|
||||
"find_process": false,
|
||||
"find_neighbor": false,
|
||||
"dhcp_lease_files": [],
|
||||
"default_domain_resolver": "", // or {}
|
||||
"default_network_strategy": "",
|
||||
"default_network_type": [],
|
||||
@@ -107,6 +115,38 @@ Set routing mark by default.
|
||||
|
||||
Takes no effect if `outbound.routing_mark` is set.
|
||||
|
||||
#### find_process
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, Windows, and macOS.
|
||||
|
||||
Enable process search for logging when no `process_name`, `process_path`, `package_name`, `user` or `user_id` rules exist.
|
||||
|
||||
#### find_neighbor
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux and macOS.
|
||||
|
||||
Enable neighbor resolution for logging when no `source_mac_address` or `source_hostname` rules exist.
|
||||
|
||||
See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
||||
|
||||
#### dhcp_lease_files
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux and macOS.
|
||||
|
||||
Custom DHCP lease file paths for hostname and MAC address resolution.
|
||||
|
||||
Automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea) if empty.
|
||||
|
||||
#### default_domain_resolver
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
@@ -4,6 +4,11 @@ icon: material/alert-decagram
|
||||
|
||||
# 路由
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [find_neighbor](#find_neighbor)
|
||||
:material-plus: [dhcp_lease_files](#dhcp_lease_files)
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||
@@ -37,6 +42,9 @@ icon: material/alert-decagram
|
||||
"override_android_vpn": false,
|
||||
"default_interface": "",
|
||||
"default_mark": 0,
|
||||
"find_process": false,
|
||||
"find_neighbor": false,
|
||||
"dhcp_lease_files": [],
|
||||
"default_network_strategy": "",
|
||||
"default_fallback_delay": ""
|
||||
}
|
||||
@@ -106,6 +114,38 @@ icon: material/alert-decagram
|
||||
|
||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||
|
||||
#### find_process
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS。
|
||||
|
||||
在没有 `process_name`、`process_path`、`package_name`、`user` 或 `user_id` 规则时启用进程搜索以输出日志。
|
||||
|
||||
#### find_neighbor
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux 和 macOS。
|
||||
|
||||
在没有 `source_mac_address` 或 `source_hostname` 规则时启用邻居解析以输出日志。
|
||||
|
||||
参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
||||
|
||||
#### dhcp_lease_files
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux 和 macOS。
|
||||
|
||||
用于主机名和 MAC 地址解析的自定义 DHCP 租约文件路径。
|
||||
|
||||
为空时自动从常见 DHCP 服务器(dnsmasq、odhcpd、ISC dhcpd、Kea)检测。
|
||||
|
||||
#### default_domain_resolver
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.0"
|
||||
|
||||
:material-plus: [interface_address](#interface_address)
|
||||
@@ -159,6 +164,12 @@ icon: material/new-box
|
||||
"tailscale",
|
||||
"wireguard"
|
||||
],
|
||||
"source_mac_address": [
|
||||
"00:11:22:33:44:55"
|
||||
],
|
||||
"source_hostname": [
|
||||
"my-device"
|
||||
],
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
@@ -449,6 +460,26 @@ Match specified outbounds' preferred routes.
|
||||
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||
| `wireguard` | Match peers's allowed IPs |
|
||||
|
||||
#### source_mac_address
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
||||
|
||||
Match source device MAC address.
|
||||
|
||||
#### source_hostname
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
||||
|
||||
Match source device hostname from DHCP leases.
|
||||
|
||||
#### rule_set
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
:material-plus: [interface_address](#interface_address)
|
||||
@@ -156,6 +161,12 @@ icon: material/new-box
|
||||
"tailscale",
|
||||
"wireguard"
|
||||
],
|
||||
"source_mac_address": [
|
||||
"00:11:22:33:44:55"
|
||||
],
|
||||
"source_hostname": [
|
||||
"my-device"
|
||||
],
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
@@ -446,6 +457,26 @@ icon: material/new-box
|
||||
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||
| `wireguard` | 匹配对端的 allowed IPs |
|
||||
|
||||
#### source_mac_address
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
||||
|
||||
匹配源设备 MAC 地址。
|
||||
|
||||
#### source_hostname
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
||||
|
||||
匹配源设备从 DHCP 租约获取的主机名。
|
||||
|
||||
#### rule_set
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
49
docs/configuration/shared/neighbor.md
Normal file
49
docs/configuration/shared/neighbor.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
icon: material/lan
|
||||
---
|
||||
|
||||
# Neighbor Resolution
|
||||
|
||||
Match LAN devices by MAC address and hostname using
|
||||
[`source_mac_address`](/configuration/route/rule/#source_mac_address) and
|
||||
[`source_hostname`](/configuration/route/rule/#source_hostname) rule items.
|
||||
|
||||
Neighbor resolution is automatically enabled when these rule items exist.
|
||||
Use [`route.find_neighbor`](/configuration/route/#find_neighbor) to force enable it for logging without rules.
|
||||
|
||||
## Linux
|
||||
|
||||
Works natively. No special setup required.
|
||||
|
||||
Hostname resolution requires DHCP lease files,
|
||||
automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea).
|
||||
Custom paths can be set via [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files).
|
||||
|
||||
## Android
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients.
|
||||
|
||||
Requires Android 11 or above and ROOT.
|
||||
|
||||
Must use [VPNHotspot](https://github.com/Mygod/VPNHotspot) to share the VPN connection.
|
||||
ROM built-in features like "Use VPN for connected devices" can share VPN
|
||||
but cannot provide MAC address or hostname information.
|
||||
|
||||
Set **IP Masquerade Mode** to **None** in VPNHotspot settings.
|
||||
|
||||
Only route/DNS rules are supported. TUN include/exclude routes are not supported.
|
||||
|
||||
### Hostname Visibility
|
||||
|
||||
Hostname is only visible in sing-box if it is visible in VPNHotspot.
|
||||
For Apple devices, change **Private Wi-Fi Address** from **Rotating** to **Fixed** in the Wi-Fi settings
|
||||
of the connected network. Non-Apple devices are always visible.
|
||||
|
||||
## macOS
|
||||
|
||||
Requires the standalone version (macOS system extension).
|
||||
The App Store version can share the VPN as a hotspot but does not support MAC address or hostname reading.
|
||||
|
||||
See [VPN Hotspot](/manual/misc/vpn-hotspot/#macos) for Internet Sharing setup.
|
||||
49
docs/configuration/shared/neighbor.zh.md
Normal file
49
docs/configuration/shared/neighbor.zh.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
icon: material/lan
|
||||
---
|
||||
|
||||
# 邻居解析
|
||||
|
||||
通过
|
||||
[`source_mac_address`](/configuration/route/rule/#source_mac_address) 和
|
||||
[`source_hostname`](/configuration/route/rule/#source_hostname) 规则项匹配局域网设备的 MAC 地址和主机名。
|
||||
|
||||
当这些规则项存在时,邻居解析自动启用。
|
||||
使用 [`route.find_neighbor`](/configuration/route/#find_neighbor) 可在没有规则时强制启用以输出日志。
|
||||
|
||||
## Linux
|
||||
|
||||
原生支持,无需特殊设置。
|
||||
|
||||
主机名解析需要 DHCP 租约文件,
|
||||
自动从常见 DHCP 服务器(dnsmasq、odhcpd、ISC dhcpd、Kea)检测。
|
||||
可通过 [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files) 设置自定义路径。
|
||||
|
||||
## Android
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在图形客户端中支持。
|
||||
|
||||
需要 Android 11 或以上版本和 ROOT。
|
||||
|
||||
必须使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot) 共享 VPN 连接。
|
||||
ROM 自带的「通过 VPN 共享连接」等功能可以共享 VPN,
|
||||
但无法提供 MAC 地址或主机名信息。
|
||||
|
||||
在 VPNHotspot 设置中将 **IP 遮掩模式** 设为 **无**。
|
||||
|
||||
仅支持路由/DNS 规则。不支持 TUN 的 include/exclude 路由。
|
||||
|
||||
### 设备可见性
|
||||
|
||||
MAC 地址和主机名仅在 VPNHotspot 中可见时 sing-box 才能读取。
|
||||
对于 Apple 设备,需要在所连接网络的 Wi-Fi 设置中将**私有无线局域网地址**从**轮替**改为**固定**。
|
||||
非 Apple 设备始终可见。
|
||||
|
||||
## macOS
|
||||
|
||||
需要独立版本(macOS 系统扩展)。
|
||||
App Store 版本可以共享 VPN 热点但不支持 MAC 地址或主机名读取。
|
||||
|
||||
参阅 [VPN 热点](/manual/misc/vpn-hotspot/#macos) 了解互联网共享设置。
|
||||
@@ -144,6 +144,18 @@ func (s *platformInterfaceStub) SendNotification(notification *adapter.Notificat
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformNeighborResolver() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
53
experimental/libbox/neighbor.go
Normal file
53
experimental/libbox/neighbor.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type NeighborEntry struct {
|
||||
Address string
|
||||
MacAddress string
|
||||
Hostname string
|
||||
}
|
||||
|
||||
type NeighborEntryIterator interface {
|
||||
Next() *NeighborEntry
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
type NeighborSubscription struct {
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (s *NeighborSubscription) Close() {
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator {
|
||||
entries := make([]*NeighborEntry, 0, len(table))
|
||||
for address, mac := range table {
|
||||
entries = append(entries, &NeighborEntry{
|
||||
Address: address.String(),
|
||||
MacAddress: mac.String(),
|
||||
})
|
||||
}
|
||||
return &neighborEntryIterator{entries}
|
||||
}
|
||||
|
||||
type neighborEntryIterator struct {
|
||||
entries []*NeighborEntry
|
||||
}
|
||||
|
||||
func (i *neighborEntryIterator) HasNext() bool {
|
||||
return len(i.entries) > 0
|
||||
}
|
||||
|
||||
func (i *neighborEntryIterator) Next() *NeighborEntry {
|
||||
if len(i.entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
entry := i.entries[0]
|
||||
i.entries = i.entries[1:]
|
||||
return entry
|
||||
}
|
||||
123
experimental/libbox/neighbor_darwin.go
Normal file
123
experimental/libbox/neighbor_darwin.go
Normal file
@@ -0,0 +1,123 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/route"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
xroute "golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
|
||||
entries, err := route.ReadNeighborEntries()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initial neighbor dump")
|
||||
}
|
||||
table := make(map[netip.Addr]net.HardwareAddr)
|
||||
for _, entry := range entries {
|
||||
table[entry.Address] = entry.MACAddress
|
||||
}
|
||||
listener.UpdateNeighborTable(tableToIterator(table))
|
||||
routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "open route socket")
|
||||
}
|
||||
err = unix.SetNonblock(routeSocket, true)
|
||||
if err != nil {
|
||||
unix.Close(routeSocket)
|
||||
return nil, E.Cause(err, "set route socket nonblock")
|
||||
}
|
||||
subscription := &NeighborSubscription{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go subscription.loop(listener, routeSocket, table)
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, routeSocket int, table map[netip.Addr]net.HardwareAddr) {
|
||||
routeSocketFile := os.NewFile(uintptr(routeSocket), "route")
|
||||
defer routeSocketFile.Close()
|
||||
buffer := buf.NewPacket()
|
||||
defer buffer.Release()
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
tv := unix.NsecToTimeval(int64(3 * time.Second))
|
||||
_ = unix.SetsockoptTimeval(routeSocket, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
|
||||
n, err := routeSocketFile.Read(buffer.FreeBytes())
|
||||
if err != nil {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
messages, err := xroute.ParseRIB(xroute.RIBTypeRoute, buffer.FreeBytes()[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
changed := false
|
||||
for _, message := range messages {
|
||||
routeMessage, isRouteMessage := message.(*xroute.RouteMessage)
|
||||
if !isRouteMessage {
|
||||
continue
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_LLINFO == 0 {
|
||||
continue
|
||||
}
|
||||
address, mac, isDelete, ok := route.ParseRouteNeighborMessage(routeMessage)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if isDelete {
|
||||
if _, exists := table[address]; exists {
|
||||
delete(table, address)
|
||||
changed = true
|
||||
}
|
||||
} else {
|
||||
existing, exists := table[address]
|
||||
if !exists || !slices.Equal(existing, mac) {
|
||||
table[address] = mac
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
listener.UpdateNeighborTable(tableToIterator(table))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ReadBootpdLeases() NeighborEntryIterator {
|
||||
leaseIPToMAC, ipToHostname, macToHostname := route.ReloadLeaseFiles([]string{"/var/db/dhcpd_leases"})
|
||||
entries := make([]*NeighborEntry, 0, len(leaseIPToMAC))
|
||||
for address, mac := range leaseIPToMAC {
|
||||
entry := &NeighborEntry{
|
||||
Address: address.String(),
|
||||
MacAddress: mac.String(),
|
||||
}
|
||||
hostname, found := ipToHostname[address]
|
||||
if !found {
|
||||
hostname = macToHostname[mac.String()]
|
||||
}
|
||||
entry.Hostname = hostname
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return &neighborEntryIterator{entries}
|
||||
}
|
||||
88
experimental/libbox/neighbor_linux.go
Normal file
88
experimental/libbox/neighbor_linux.go
Normal file
@@ -0,0 +1,88 @@
|
||||
//go:build linux
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/route"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
|
||||
entries, err := route.ReadNeighborEntries()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initial neighbor dump")
|
||||
}
|
||||
table := make(map[netip.Addr]net.HardwareAddr)
|
||||
for _, entry := range entries {
|
||||
table[entry.Address] = entry.MACAddress
|
||||
}
|
||||
listener.UpdateNeighborTable(tableToIterator(table))
|
||||
connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
|
||||
Groups: 1 << (unix.RTNLGRP_NEIGH - 1),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "subscribe neighbor updates")
|
||||
}
|
||||
subscription := &NeighborSubscription{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go subscription.loop(listener, connection, table)
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, connection *netlink.Conn, table map[netip.Addr]net.HardwareAddr) {
|
||||
defer connection.Close()
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
err := connection.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
messages, err := connection.Receive()
|
||||
if err != nil {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
changed := false
|
||||
for _, message := range messages {
|
||||
address, mac, isDelete, ok := route.ParseNeighborMessage(message)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if isDelete {
|
||||
if _, exists := table[address]; exists {
|
||||
delete(table, address)
|
||||
changed = true
|
||||
}
|
||||
} else {
|
||||
existing, exists := table[address]
|
||||
if !exists || !slices.Equal(existing, mac) {
|
||||
table[address] = mac
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
listener.UpdateNeighborTable(tableToIterator(table))
|
||||
}
|
||||
}
|
||||
}
|
||||
9
experimental/libbox/neighbor_stub.go
Normal file
9
experimental/libbox/neighbor_stub.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !linux && !darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import "os"
|
||||
|
||||
func SubscribeNeighborTable(_ NeighborUpdateListener) (*NeighborSubscription, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
@@ -21,6 +21,13 @@ type PlatformInterface interface {
|
||||
SystemCertificates() StringIterator
|
||||
ClearDNSCache()
|
||||
SendNotification(notification *Notification) error
|
||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
||||
RegisterMyInterface(name string)
|
||||
}
|
||||
|
||||
type NeighborUpdateListener interface {
|
||||
UpdateNeighborTable(entries NeighborEntryIterator)
|
||||
}
|
||||
|
||||
type ConnectionOwner struct {
|
||||
|
||||
@@ -78,6 +78,7 @@ func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformO
|
||||
}
|
||||
options.FileDescriptor = dupFd
|
||||
w.myTunName = options.Name
|
||||
w.iif.RegisterMyInterface(options.Name)
|
||||
return tun.New(*options)
|
||||
}
|
||||
|
||||
@@ -220,6 +221,46 @@ func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notifi
|
||||
return w.iif.SendNotification((*Notification)(notification))
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformNeighborResolver() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
||||
return w.iif.StartNeighborMonitor(&neighborUpdateListenerWrapper{listener: listener})
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
||||
return w.iif.CloseNeighborMonitor(nil)
|
||||
}
|
||||
|
||||
type neighborUpdateListenerWrapper struct {
|
||||
listener adapter.NeighborUpdateListener
|
||||
}
|
||||
|
||||
func (w *neighborUpdateListenerWrapper) UpdateNeighborTable(entries NeighborEntryIterator) {
|
||||
var result []adapter.NeighborEntry
|
||||
for entries.HasNext() {
|
||||
entry := entries.Next()
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
address, err := netip.ParseAddr(entry.Address)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
macAddress, err := net.ParseMAC(entry.MacAddress)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
result = append(result, adapter.NeighborEntry{
|
||||
Address: address,
|
||||
MACAddress: macAddress,
|
||||
Hostname: entry.Hostname,
|
||||
})
|
||||
}
|
||||
w.listener.UpdateNeighborTable(result)
|
||||
}
|
||||
|
||||
func AvailablePort(startPort int32) (int32, error) {
|
||||
for port := int(startPort); ; port++ {
|
||||
if port > 65535 {
|
||||
|
||||
68
go.mod
68
go.mod
@@ -14,11 +14,13 @@ require (
|
||||
github.com/godbus/dbus/v5 v5.2.2
|
||||
github.com/gofrs/uuid/v5 v5.4.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91
|
||||
github.com/jsimonetti/rtnetlink v1.4.0
|
||||
github.com/keybase/go-keychain v0.0.1
|
||||
github.com/libdns/acmedns v0.5.0
|
||||
github.com/libdns/alidns v1.0.6
|
||||
github.com/libdns/cloudflare v0.2.2
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mdlayher/netlink v1.9.0
|
||||
github.com/metacubex/utls v1.8.4
|
||||
github.com/mholt/acmez/v3 v3.1.6
|
||||
github.com/miekg/dns v1.1.72
|
||||
@@ -27,8 +29,8 @@ require (
|
||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||
github.com/sagernet/cors v1.2.1
|
||||
github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399
|
||||
github.com/sagernet/cronet-go v0.0.0-20260306075351-e5943141aa40
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260306075351-e5943141aa40
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/gomobile v0.1.12
|
||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||
@@ -39,7 +41,7 @@ require (
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||
github.com/sagernet/sing-tun v0.8.2
|
||||
github.com/sagernet/sing-tun v0.8.3-0.20260305131414-5083da5745ed
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260303140313-3bcf9a4b9349
|
||||
@@ -92,11 +94,9 @@ require (
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/mdlayher/netlink v1.9.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
@@ -105,35 +105,35 @@ require (
|
||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260306074725-2e4f95b376d3 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
|
||||
128
go.sum
128
go.sum
@@ -162,68 +162,68 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399 h1:x3tVYQHdqqnKbEd9/H4KIGhtHTjA+KfiiaXedI3/w8Q=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399 h1:mD3ehudpYf1IFgCTv25d/B6KnBc/lLFq1jmSQIK24y0=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:MbYagcGGIaRo9tNrgafbCTO+Qc7eVEh32ZWMprSB8b0=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6 h1:ghRKgSaswefPwQF8AYtUlNyumILOB0ptJWxgZ8MFrEE=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:Behr7YCnQP2dsvzAJDIoMd5nTVU9/d6MMtk/S3MctwA=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6 h1:6UL9XdGU/44oTHj36e+EBDJ0RonFoObmd299NG/qQCU=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:Q9apxjtkj6iMIBQlTo71QsOTrNlhHneaXQb1Q0IshU8=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:0N+xlnMkFEeqgFe3X/PEvHt+/t+BPgxmbx7wzNcYppg=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:7f2vTXtePikBSV1bdD0zs5/WuZM+bRuej3mREpWL/qQ=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:HMlnhEYs+axOa0tAJ79se3QsYB8CpRCQo9mewWWFeeg=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:Ux/U6vF+1AoGLSJK3jVa9Kqkn64MX4Ivv7fy0ikDrpQ=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:5Dhuere2bQFzfGvKxA7TFgA5MoTtgcZMmJQuKwQKlyA=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6 h1:aMRcLow4UpZWZ28fR9FjveTL/4okrigZySIkEVZnlgA=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6 h1:y4g8oNtEfSdcKrBKsH5vMAjzGthvhHFNU80sanYDQEM=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:CXN6OPILi5trwffmYiiJ9rqJL3XAWx1menLrBBwA0gU=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:ZphFHQeFOTpqCWPwFcQRnrePXajml8LbKlYFJ5n0isU=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6 h1:nKzFK84oANHz7I6bab+25bBY+pdpAbO0b3NJroyLldo=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:HqqZUGRXcWvvwlbuvjk/efo8TKW1H/aHdqQTde+Xs9Q=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:D2v9lZZG5sm4x/CkG7uqc6ZU3YlhFQ+GmJfvZMK0h/s=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6 h1:TWveNeXHrA5r8XOlf+vw7U2b2M0ip6GNF89jcUi1ogw=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6 h1:DVCBoXOZI4PNG0cbCLg8lrphRXoLFcAIDLNmzsCVg3I=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:7s5xqNlBUWkIXdruPYi3/txXekQhGWxrYxbnB0cnARo=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6 h1:eyEb+Q7VH4hpE1nV+EmEnN2XX5WilgBpIsfCw4C/7no=
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6 h1:9F1W7+z1hHST6GSzdpQ8Q0NCkneAL18dkRA1HfxH09A=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6 h1:MmQIR3iJsdvw1ONBP3geK57i9c3+v9dXPMNdZYcYGKw=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6 h1:j6Pk1Wsl+PCbKRXtp7a912D2D6zqX5Nk51wDQU9TEDc=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:0DnFhbRfNqwguNCxiinA7BowQ/RaFt627sjW09JNp80=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:3CZmlEk2/WW5UHLFJZxXPJ9IJxX3td8U3PyqWSGMl3c=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:eHkVRptoZf3BuuskkjcclO2dwQrX4zluoVGODMrX7n0=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:UgFmE0cZo9euu8/7sTAhj1G8lldavwXBdcPNyTE29CQ=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:xbg3ZB9tLMGDQe4+aewG0Z4bEP/2pLtYBcDzILv5eEc=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:M0bTSTSTnSMlPY2WaZT6fL5TFICqk8v4cm+QVf8Fcao=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260306075351-e5943141aa40 h1:A9P5YN0Tq+quO9vISIOL+PkExbGWAroyNIk9pI309ls=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260306075351-e5943141aa40/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260306075351-e5943141aa40 h1:0W9yjyRZ/9peX7jFlruJgOhydBzqj0u7uRY+NUFlbCE=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260306075351-e5943141aa40/go.mod h1:U54HWP2v0xDyTEpAcof98Y923Lr1ymOvFWpa8aVBBAk=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260306074725-2e4f95b376d3 h1:Par4t1sZVTJodVxVoGoaSi4MTojaDrraHXCK5Xjt/rM=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:Wg7qunP2EtGnQSHaAL2a/shion6Y5QatyFtAoMcZjdg=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260306074725-2e4f95b376d3 h1:JZSGrRe1y5yR+REJLK2X1ZxHcUnXc110m7rEuqkhurk=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:DwgYmuUd36tXSJuu3wK1HntOifcRPifDc/s6X6LdVSQ=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:jjjSy31cytxMRYLoNlwA98YasRAe0P5EEsw5c4Pwvv0=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:2b/N8xhl+MBRIg70sHYuJ/3V3gJu3F4aVTndxFnbICU=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:CysHa5F+LqLumG3HUfUbQzWIbG13QMTUMkkc2DTHclU=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:Lf9FtR/87jNgc+0yeCCxlvlu2RLSrlaaYfVlYCJeFq0=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:1X6PNucfXzZB21EOP0aBn+m06UgL6e4oJZJ2bcqrbtM=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260306074725-2e4f95b376d3 h1:XvHeLlblB6nXilTqfDI+SxyIuR2FUkpNkL9mXNt/wNg=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260306074725-2e4f95b376d3 h1:ACHr8UvOHs/+S29L7UcCrTe3P53NuZbKzHmwCpteyoo=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:MzSFaCUaGn/a4jAGw7Qnm0t5ssnx1z87YEqwvG1ZhRU=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:i7lFKCd4AcKut4Co/jEzvb9d1d10K3t4un9NarqAyo0=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260306074725-2e4f95b376d3 h1:aLBHE3UGmBf+f+Vf5ceYDzsKPufDfYoMILrMhqwsJYI=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:5CZoDiP1u3REF7LcBYoQgBuWacnBcxWeERU5UrQDqHg=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:4W7D6UUZH5/636fE2VMHJ+YLofmYWaBhAlvaj23C20I=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260306074725-2e4f95b376d3 h1:zK/9ebQ3Ykcvomc+JEIou8rgIxbU1O6bBB7z2A3irO4=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260306074725-2e4f95b376d3 h1:q79ByUHlbxPcADvOZ2G8ayCnLBlF/fzHtvLennf2clo=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:3RNNwgX1rltXu7gIGD12gxlIJc1s8e2stB2BzMtl9tE=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260306074725-2e4f95b376d3 h1:/hD/Vk7/Jlg07Ic1atNjU1mXii91ziN6e3zxFYTKqio=
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260306074725-2e4f95b376d3 h1:d7Z63bQ/U7ZmB1MkC1dtAtIn6h40WrHey9S/vnfDb5g=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260306074725-2e4f95b376d3 h1:1c6ZqstM62BrbTFrCA4vINFTCooCM8uph6uIGfAEfqQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260306074725-2e4f95b376d3 h1:ZTHDXreHG+9XT0hD+MIu1etqPQAfKBApFS8Z1XMT7Nw=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260306074725-2e4f95b376d3 h1:ry0S9V5pSNTg2wXra1rBajSITvXRufgw0u3w/mE0GB4=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:iO5cm5MiqvKQB7QkY2b8QFgnMt3jDdOiDopX2aNsFOM=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:DC03qT5UTbDgUzJ78xajYXq5UYcFHBLHKIoH+PRpCf0=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3 h1:uLqZSA2OAynMxrokxVO2pW3unWA8DNjion/I4ihX/84=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260306074725-2e4f95b376d3 h1:WHTBryhjXaniv5fMjSr/FvWKyAhdomD7rLagh4ano10=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260306074725-2e4f95b376d3 h1:TmikX4Xtalpv2Jts/MuB5qwg+KmTKbrpPf5deZGLIqA=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260306074725-2e4f95b376d3/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg=
|
||||
@@ -248,8 +248,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-tun v0.8.2 h1:rQr/x3eQCHh3oleIaoJdPdJwqzZp4+QWcJLT0Wz2xKY=
|
||||
github.com/sagernet/sing-tun v0.8.2/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||
github.com/sagernet/sing-tun v0.8.3-0.20260305131414-5083da5745ed h1:0XZgwnEX2HgQ/0J0The6KPEAezBz5bLl18PMTRHNN9E=
|
||||
github.com/sagernet/sing-tun v0.8.3-0.20260305131414-5083da5745ed/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||
|
||||
@@ -129,6 +129,7 @@ nav:
|
||||
- UDP over TCP: configuration/shared/udp-over-tcp.md
|
||||
- TCP Brutal: configuration/shared/tcp-brutal.md
|
||||
- Wi-Fi State: configuration/shared/wifi-state.md
|
||||
- Neighbor Resolution: configuration/shared/neighbor.md
|
||||
- Endpoint:
|
||||
- configuration/endpoint/index.md
|
||||
- WireGuard: configuration/endpoint/wireguard.md
|
||||
|
||||
@@ -9,6 +9,8 @@ type RouteOptions struct {
|
||||
RuleSet []RuleSet `json:"rule_set,omitempty"`
|
||||
Final string `json:"final,omitempty"`
|
||||
FindProcess bool `json:"find_process,omitempty"`
|
||||
FindNeighbor bool `json:"find_neighbor,omitempty"`
|
||||
DHCPLeaseFiles badoption.Listable[string] `json:"dhcp_lease_files,omitempty"`
|
||||
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
||||
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
||||
DefaultInterface string `json:"default_interface,omitempty"`
|
||||
|
||||
@@ -103,6 +103,8 @@ type RawDefaultRule struct {
|
||||
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||
SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"`
|
||||
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
||||
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||
|
||||
@@ -106,6 +106,8 @@ type RawDefaultDNSRule struct {
|
||||
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||
SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"`
|
||||
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
||||
|
||||
@@ -39,6 +39,8 @@ type TunInboundOptions struct {
|
||||
IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"`
|
||||
IncludePackage badoption.Listable[string] `json:"include_package,omitempty"`
|
||||
ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"`
|
||||
IncludeMACAddress badoption.Listable[string] `json:"include_mac_address,omitempty"`
|
||||
ExcludeMACAddress badoption.Listable[string] `json:"exclude_mac_address,omitempty"`
|
||||
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
||||
Stack string `json:"stack,omitempty"`
|
||||
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
||||
|
||||
@@ -156,6 +156,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
if nfQueue == 0 {
|
||||
nfQueue = tun.DefaultAutoRedirectNFQueue
|
||||
}
|
||||
var includeMACAddress []net.HardwareAddr
|
||||
for i, macString := range options.IncludeMACAddress {
|
||||
mac, macErr := net.ParseMAC(macString)
|
||||
if macErr != nil {
|
||||
return nil, E.Cause(macErr, "parse include_mac_address[", i, "]")
|
||||
}
|
||||
includeMACAddress = append(includeMACAddress, mac)
|
||||
}
|
||||
var excludeMACAddress []net.HardwareAddr
|
||||
for i, macString := range options.ExcludeMACAddress {
|
||||
mac, macErr := net.ParseMAC(macString)
|
||||
if macErr != nil {
|
||||
return nil, E.Cause(macErr, "parse exclude_mac_address[", i, "]")
|
||||
}
|
||||
excludeMACAddress = append(excludeMACAddress, mac)
|
||||
}
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
||||
inbound := &Inbound{
|
||||
@@ -193,6 +209,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
IncludeMACAddress: includeMACAddress,
|
||||
ExcludeMACAddress: excludeMACAddress,
|
||||
InterfaceMonitor: networkManager.InterfaceMonitor(),
|
||||
EXP_MultiPendingPackets: multiPendingPackets,
|
||||
},
|
||||
|
||||
239
route/neighbor_resolver_darwin.go
Normal file
239
route/neighbor_resolver_darwin.go
Normal file
@@ -0,0 +1,239 @@
|
||||
//go:build darwin
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var defaultLeaseFiles = []string{
|
||||
"/var/db/dhcpd_leases",
|
||||
"/tmp/dhcp.leases",
|
||||
}
|
||||
|
||||
type neighborResolver struct {
|
||||
logger logger.ContextLogger
|
||||
leaseFiles []string
|
||||
access sync.RWMutex
|
||||
neighborIPToMAC map[netip.Addr]net.HardwareAddr
|
||||
leaseIPToMAC map[netip.Addr]net.HardwareAddr
|
||||
ipToHostname map[netip.Addr]string
|
||||
macToHostname map[string]string
|
||||
watcher *fswatch.Watcher
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) {
|
||||
if len(leaseFiles) == 0 {
|
||||
for _, path := range defaultLeaseFiles {
|
||||
info, err := os.Stat(path)
|
||||
if err == nil && info.Size() > 0 {
|
||||
leaseFiles = append(leaseFiles, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &neighborResolver{
|
||||
logger: resolverLogger,
|
||||
leaseFiles: leaseFiles,
|
||||
neighborIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
||||
leaseIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
||||
ipToHostname: make(map[netip.Addr]string),
|
||||
macToHostname: make(map[string]string),
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) Start() error {
|
||||
err := r.loadNeighborTable()
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "load neighbor table"))
|
||||
}
|
||||
r.doReloadLeaseFiles()
|
||||
go r.subscribeNeighborUpdates()
|
||||
if len(r.leaseFiles) > 0 {
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: r.leaseFiles,
|
||||
Logger: r.logger,
|
||||
Callback: func(_ string) {
|
||||
r.doReloadLeaseFiles()
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "create lease file watcher"))
|
||||
} else {
|
||||
r.watcher = watcher
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "start lease file watcher"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) Close() error {
|
||||
close(r.done)
|
||||
if r.watcher != nil {
|
||||
return r.watcher.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {
|
||||
r.access.RLock()
|
||||
defer r.access.RUnlock()
|
||||
mac, found := r.neighborIPToMAC[address]
|
||||
if found {
|
||||
return mac, true
|
||||
}
|
||||
mac, found = r.leaseIPToMAC[address]
|
||||
if found {
|
||||
return mac, true
|
||||
}
|
||||
mac, found = extractMACFromEUI64(address)
|
||||
if found {
|
||||
return mac, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) {
|
||||
r.access.RLock()
|
||||
defer r.access.RUnlock()
|
||||
hostname, found := r.ipToHostname[address]
|
||||
if found {
|
||||
return hostname, true
|
||||
}
|
||||
mac, macFound := r.neighborIPToMAC[address]
|
||||
if !macFound {
|
||||
mac, macFound = r.leaseIPToMAC[address]
|
||||
}
|
||||
if !macFound {
|
||||
mac, macFound = extractMACFromEUI64(address)
|
||||
}
|
||||
if macFound {
|
||||
hostname, found = r.macToHostname[mac.String()]
|
||||
if found {
|
||||
return hostname, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (r *neighborResolver) loadNeighborTable() error {
|
||||
entries, err := ReadNeighborEntries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
for _, entry := range entries {
|
||||
r.neighborIPToMAC[entry.Address] = entry.MACAddress
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) subscribeNeighborUpdates() {
|
||||
routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "subscribe neighbor updates"))
|
||||
return
|
||||
}
|
||||
err = unix.SetNonblock(routeSocket, true)
|
||||
if err != nil {
|
||||
unix.Close(routeSocket)
|
||||
r.logger.Warn(E.Cause(err, "set route socket nonblock"))
|
||||
return
|
||||
}
|
||||
routeSocketFile := os.NewFile(uintptr(routeSocket), "route")
|
||||
defer routeSocketFile.Close()
|
||||
buffer := buf.NewPacket()
|
||||
defer buffer.Release()
|
||||
for {
|
||||
select {
|
||||
case <-r.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
err = setReadDeadline(routeSocketFile, 3*time.Second)
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "set route socket read deadline"))
|
||||
return
|
||||
}
|
||||
n, err := routeSocketFile.Read(buffer.FreeBytes())
|
||||
if err != nil {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-r.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
r.logger.Warn(E.Cause(err, "receive neighbor update"))
|
||||
continue
|
||||
}
|
||||
messages, err := route.ParseRIB(route.RIBTypeRoute, buffer.FreeBytes()[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, message := range messages {
|
||||
routeMessage, isRouteMessage := message.(*route.RouteMessage)
|
||||
if !isRouteMessage {
|
||||
continue
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_LLINFO == 0 {
|
||||
continue
|
||||
}
|
||||
address, mac, isDelete, ok := ParseRouteNeighborMessage(routeMessage)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
r.access.Lock()
|
||||
if isDelete {
|
||||
delete(r.neighborIPToMAC, address)
|
||||
} else {
|
||||
r.neighborIPToMAC[address] = mac
|
||||
}
|
||||
r.access.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *neighborResolver) doReloadLeaseFiles() {
|
||||
leaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles)
|
||||
r.access.Lock()
|
||||
r.leaseIPToMAC = leaseIPToMAC
|
||||
r.ipToHostname = ipToHostname
|
||||
r.macToHostname = macToHostname
|
||||
r.access.Unlock()
|
||||
}
|
||||
|
||||
func setReadDeadline(file *os.File, timeout time.Duration) error {
|
||||
rawConn, err := file.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var controlErr error
|
||||
err = rawConn.Control(func(fd uintptr) {
|
||||
tv := unix.NsecToTimeval(int64(timeout))
|
||||
controlErr = unix.SetsockoptTimeval(int(fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return controlErr
|
||||
}
|
||||
386
route/neighbor_resolver_lease.go
Normal file
386
route/neighbor_resolver_lease.go
Normal file
@@ -0,0 +1,386 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func parseLeaseFile(path string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
if strings.HasSuffix(path, "dhcpd_leases") {
|
||||
parseBootpdLeases(file, ipToMAC, ipToHostname, macToHostname)
|
||||
return
|
||||
}
|
||||
if strings.HasSuffix(path, "kea-leases4.csv") {
|
||||
parseKeaCSV4(file, ipToMAC, ipToHostname, macToHostname)
|
||||
return
|
||||
}
|
||||
if strings.HasSuffix(path, "kea-leases6.csv") {
|
||||
parseKeaCSV6(file, ipToMAC, ipToHostname, macToHostname)
|
||||
return
|
||||
}
|
||||
if strings.HasSuffix(path, "dhcpd.leases") {
|
||||
parseISCDhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
||||
return
|
||||
}
|
||||
parseDnsmasqOdhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
||||
}
|
||||
|
||||
func ReloadLeaseFiles(leaseFiles []string) (leaseIPToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
leaseIPToMAC = make(map[netip.Addr]net.HardwareAddr)
|
||||
ipToHostname = make(map[netip.Addr]string)
|
||||
macToHostname = make(map[string]string)
|
||||
for _, path := range leaseFiles {
|
||||
parseLeaseFile(path, leaseIPToMAC, ipToHostname, macToHostname)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseDnsmasqOdhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
now := time.Now().Unix()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "duid ") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "# ") {
|
||||
parseOdhcpdLine(line[2:], ipToMAC, ipToHostname, macToHostname)
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 4 {
|
||||
continue
|
||||
}
|
||||
expiry, err := strconv.ParseInt(fields[0], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if expiry != 0 && expiry < now {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[1], ":") {
|
||||
mac, macErr := net.ParseMAC(fields[1])
|
||||
if macErr != nil {
|
||||
continue
|
||||
}
|
||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))
|
||||
if !addrOK {
|
||||
continue
|
||||
}
|
||||
address = address.Unmap()
|
||||
ipToMAC[address] = mac
|
||||
hostname := fields[3]
|
||||
if hostname != "*" {
|
||||
ipToHostname[address] = hostname
|
||||
macToHostname[mac.String()] = hostname
|
||||
}
|
||||
} else {
|
||||
var mac net.HardwareAddr
|
||||
if len(fields) >= 5 {
|
||||
duid, duidErr := parseDUID(fields[4])
|
||||
if duidErr == nil {
|
||||
mac, _ = extractMACFromDUID(duid)
|
||||
}
|
||||
}
|
||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))
|
||||
if !addrOK {
|
||||
continue
|
||||
}
|
||||
address = address.Unmap()
|
||||
if mac != nil {
|
||||
ipToMAC[address] = mac
|
||||
}
|
||||
hostname := fields[3]
|
||||
if hostname != "*" {
|
||||
ipToHostname[address] = hostname
|
||||
if mac != nil {
|
||||
macToHostname[mac.String()] = hostname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseOdhcpdLine(line string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 5 {
|
||||
return
|
||||
}
|
||||
validTime, err := strconv.ParseInt(fields[4], 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if validTime == 0 {
|
||||
return
|
||||
}
|
||||
if validTime > 0 && validTime < time.Now().Unix() {
|
||||
return
|
||||
}
|
||||
hostname := fields[3]
|
||||
if hostname == "-" || strings.HasPrefix(hostname, `broken\x20`) {
|
||||
hostname = ""
|
||||
}
|
||||
if len(fields) >= 8 && fields[2] == "ipv4" {
|
||||
mac, macErr := net.ParseMAC(fields[1])
|
||||
if macErr != nil {
|
||||
return
|
||||
}
|
||||
addressField := fields[7]
|
||||
slashIndex := strings.IndexByte(addressField, '/')
|
||||
if slashIndex >= 0 {
|
||||
addressField = addressField[:slashIndex]
|
||||
}
|
||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))
|
||||
if !addrOK {
|
||||
return
|
||||
}
|
||||
address = address.Unmap()
|
||||
ipToMAC[address] = mac
|
||||
if hostname != "" {
|
||||
ipToHostname[address] = hostname
|
||||
macToHostname[mac.String()] = hostname
|
||||
}
|
||||
return
|
||||
}
|
||||
var mac net.HardwareAddr
|
||||
duidHex := fields[1]
|
||||
duidBytes, hexErr := hex.DecodeString(duidHex)
|
||||
if hexErr == nil {
|
||||
mac, _ = extractMACFromDUID(duidBytes)
|
||||
}
|
||||
for i := 7; i < len(fields); i++ {
|
||||
addressField := fields[i]
|
||||
slashIndex := strings.IndexByte(addressField, '/')
|
||||
if slashIndex >= 0 {
|
||||
addressField = addressField[:slashIndex]
|
||||
}
|
||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))
|
||||
if !addrOK {
|
||||
continue
|
||||
}
|
||||
address = address.Unmap()
|
||||
if mac != nil {
|
||||
ipToMAC[address] = mac
|
||||
}
|
||||
if hostname != "" {
|
||||
ipToHostname[address] = hostname
|
||||
if mac != nil {
|
||||
macToHostname[mac.String()] = hostname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseISCDhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
scanner := bufio.NewScanner(file)
|
||||
var currentIP netip.Addr
|
||||
var currentMAC net.HardwareAddr
|
||||
var currentHostname string
|
||||
var currentActive bool
|
||||
var inLease bool
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, "lease ") && strings.HasSuffix(line, "{") {
|
||||
ipString := strings.TrimSuffix(strings.TrimPrefix(line, "lease "), " {")
|
||||
parsed, addrOK := netip.AddrFromSlice(net.ParseIP(ipString))
|
||||
if addrOK {
|
||||
currentIP = parsed.Unmap()
|
||||
inLease = true
|
||||
currentMAC = nil
|
||||
currentHostname = ""
|
||||
currentActive = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if line == "}" && inLease {
|
||||
if currentActive && currentMAC != nil {
|
||||
ipToMAC[currentIP] = currentMAC
|
||||
if currentHostname != "" {
|
||||
ipToHostname[currentIP] = currentHostname
|
||||
macToHostname[currentMAC.String()] = currentHostname
|
||||
}
|
||||
} else {
|
||||
delete(ipToMAC, currentIP)
|
||||
delete(ipToHostname, currentIP)
|
||||
}
|
||||
inLease = false
|
||||
continue
|
||||
}
|
||||
if !inLease {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "hardware ethernet ") {
|
||||
macString := strings.TrimSuffix(strings.TrimPrefix(line, "hardware ethernet "), ";")
|
||||
parsed, macErr := net.ParseMAC(macString)
|
||||
if macErr == nil {
|
||||
currentMAC = parsed
|
||||
}
|
||||
} else if strings.HasPrefix(line, "client-hostname ") {
|
||||
hostname := strings.TrimSuffix(strings.TrimPrefix(line, "client-hostname "), ";")
|
||||
hostname = strings.Trim(hostname, "\"")
|
||||
if hostname != "" {
|
||||
currentHostname = hostname
|
||||
}
|
||||
} else if strings.HasPrefix(line, "binding state ") {
|
||||
state := strings.TrimSuffix(strings.TrimPrefix(line, "binding state "), ";")
|
||||
currentActive = state == "active"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseKeaCSV4(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
scanner := bufio.NewScanner(file)
|
||||
firstLine := true
|
||||
for scanner.Scan() {
|
||||
if firstLine {
|
||||
firstLine = false
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(scanner.Text(), ",")
|
||||
if len(fields) < 10 {
|
||||
continue
|
||||
}
|
||||
if fields[9] != "0" {
|
||||
continue
|
||||
}
|
||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))
|
||||
if !addrOK {
|
||||
continue
|
||||
}
|
||||
address = address.Unmap()
|
||||
mac, macErr := net.ParseMAC(fields[1])
|
||||
if macErr != nil {
|
||||
continue
|
||||
}
|
||||
ipToMAC[address] = mac
|
||||
hostname := ""
|
||||
if len(fields) > 8 {
|
||||
hostname = fields[8]
|
||||
}
|
||||
if hostname != "" {
|
||||
ipToHostname[address] = hostname
|
||||
macToHostname[mac.String()] = hostname
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseKeaCSV6(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
scanner := bufio.NewScanner(file)
|
||||
firstLine := true
|
||||
for scanner.Scan() {
|
||||
if firstLine {
|
||||
firstLine = false
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(scanner.Text(), ",")
|
||||
if len(fields) < 14 {
|
||||
continue
|
||||
}
|
||||
if fields[13] != "0" {
|
||||
continue
|
||||
}
|
||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))
|
||||
if !addrOK {
|
||||
continue
|
||||
}
|
||||
address = address.Unmap()
|
||||
var mac net.HardwareAddr
|
||||
if fields[12] != "" {
|
||||
mac, _ = net.ParseMAC(fields[12])
|
||||
}
|
||||
if mac == nil {
|
||||
duid, duidErr := hex.DecodeString(strings.ReplaceAll(fields[1], ":", ""))
|
||||
if duidErr == nil {
|
||||
mac, _ = extractMACFromDUID(duid)
|
||||
}
|
||||
}
|
||||
hostname := ""
|
||||
if len(fields) > 11 {
|
||||
hostname = fields[11]
|
||||
}
|
||||
if mac != nil {
|
||||
ipToMAC[address] = mac
|
||||
}
|
||||
if hostname != "" {
|
||||
ipToHostname[address] = hostname
|
||||
if mac != nil {
|
||||
macToHostname[mac.String()] = hostname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseBootpdLeases(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
||||
now := time.Now().Unix()
|
||||
scanner := bufio.NewScanner(file)
|
||||
var currentName string
|
||||
var currentIP netip.Addr
|
||||
var currentMAC net.HardwareAddr
|
||||
var currentLease int64
|
||||
var inBlock bool
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "{" {
|
||||
inBlock = true
|
||||
currentName = ""
|
||||
currentIP = netip.Addr{}
|
||||
currentMAC = nil
|
||||
currentLease = 0
|
||||
continue
|
||||
}
|
||||
if line == "}" && inBlock {
|
||||
if currentMAC != nil && currentIP.IsValid() {
|
||||
if currentLease == 0 || currentLease >= now {
|
||||
ipToMAC[currentIP] = currentMAC
|
||||
if currentName != "" {
|
||||
ipToHostname[currentIP] = currentName
|
||||
macToHostname[currentMAC.String()] = currentName
|
||||
}
|
||||
}
|
||||
}
|
||||
inBlock = false
|
||||
continue
|
||||
}
|
||||
if !inBlock {
|
||||
continue
|
||||
}
|
||||
key, value, found := strings.Cut(line, "=")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "name":
|
||||
currentName = value
|
||||
case "ip_address":
|
||||
parsed, addrOK := netip.AddrFromSlice(net.ParseIP(value))
|
||||
if addrOK {
|
||||
currentIP = parsed.Unmap()
|
||||
}
|
||||
case "hw_address":
|
||||
typeAndMAC, hasSep := strings.CutPrefix(value, "1,")
|
||||
if hasSep {
|
||||
mac, macErr := net.ParseMAC(typeAndMAC)
|
||||
if macErr == nil {
|
||||
currentMAC = mac
|
||||
}
|
||||
}
|
||||
case "lease":
|
||||
leaseHex := strings.TrimPrefix(value, "0x")
|
||||
parsed, parseErr := strconv.ParseInt(leaseHex, 16, 64)
|
||||
if parseErr == nil {
|
||||
currentLease = parsed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
224
route/neighbor_resolver_linux.go
Normal file
224
route/neighbor_resolver_linux.go
Normal file
@@ -0,0 +1,224 @@
|
||||
//go:build linux
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var defaultLeaseFiles = []string{
|
||||
"/tmp/dhcp.leases",
|
||||
"/var/lib/dhcp/dhcpd.leases",
|
||||
"/var/lib/dhcpd/dhcpd.leases",
|
||||
"/var/lib/kea/kea-leases4.csv",
|
||||
"/var/lib/kea/kea-leases6.csv",
|
||||
}
|
||||
|
||||
type neighborResolver struct {
|
||||
logger logger.ContextLogger
|
||||
leaseFiles []string
|
||||
access sync.RWMutex
|
||||
neighborIPToMAC map[netip.Addr]net.HardwareAddr
|
||||
leaseIPToMAC map[netip.Addr]net.HardwareAddr
|
||||
ipToHostname map[netip.Addr]string
|
||||
macToHostname map[string]string
|
||||
watcher *fswatch.Watcher
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) {
|
||||
if len(leaseFiles) == 0 {
|
||||
for _, path := range defaultLeaseFiles {
|
||||
info, err := os.Stat(path)
|
||||
if err == nil && info.Size() > 0 {
|
||||
leaseFiles = append(leaseFiles, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &neighborResolver{
|
||||
logger: resolverLogger,
|
||||
leaseFiles: leaseFiles,
|
||||
neighborIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
||||
leaseIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
||||
ipToHostname: make(map[netip.Addr]string),
|
||||
macToHostname: make(map[string]string),
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) Start() error {
|
||||
err := r.loadNeighborTable()
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "load neighbor table"))
|
||||
}
|
||||
r.doReloadLeaseFiles()
|
||||
go r.subscribeNeighborUpdates()
|
||||
if len(r.leaseFiles) > 0 {
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: r.leaseFiles,
|
||||
Logger: r.logger,
|
||||
Callback: func(_ string) {
|
||||
r.doReloadLeaseFiles()
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "create lease file watcher"))
|
||||
} else {
|
||||
r.watcher = watcher
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "start lease file watcher"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) Close() error {
|
||||
close(r.done)
|
||||
if r.watcher != nil {
|
||||
return r.watcher.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {
|
||||
r.access.RLock()
|
||||
defer r.access.RUnlock()
|
||||
mac, found := r.neighborIPToMAC[address]
|
||||
if found {
|
||||
return mac, true
|
||||
}
|
||||
mac, found = r.leaseIPToMAC[address]
|
||||
if found {
|
||||
return mac, true
|
||||
}
|
||||
mac, found = extractMACFromEUI64(address)
|
||||
if found {
|
||||
return mac, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) {
|
||||
r.access.RLock()
|
||||
defer r.access.RUnlock()
|
||||
hostname, found := r.ipToHostname[address]
|
||||
if found {
|
||||
return hostname, true
|
||||
}
|
||||
mac, macFound := r.neighborIPToMAC[address]
|
||||
if !macFound {
|
||||
mac, macFound = r.leaseIPToMAC[address]
|
||||
}
|
||||
if !macFound {
|
||||
mac, macFound = extractMACFromEUI64(address)
|
||||
}
|
||||
if macFound {
|
||||
hostname, found = r.macToHostname[mac.String()]
|
||||
if found {
|
||||
return hostname, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (r *neighborResolver) loadNeighborTable() error {
|
||||
connection, err := rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "dial rtnetlink")
|
||||
}
|
||||
defer connection.Close()
|
||||
neighbors, err := connection.Neigh.List()
|
||||
if err != nil {
|
||||
return E.Cause(err, "list neighbors")
|
||||
}
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
for _, neigh := range neighbors {
|
||||
if neigh.Attributes == nil {
|
||||
continue
|
||||
}
|
||||
if neigh.Attributes.LLAddress == nil || len(neigh.Attributes.Address) == 0 {
|
||||
continue
|
||||
}
|
||||
address, ok := netip.AddrFromSlice(neigh.Attributes.Address)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
r.neighborIPToMAC[address] = slices.Clone(neigh.Attributes.LLAddress)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *neighborResolver) subscribeNeighborUpdates() {
|
||||
connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
|
||||
Groups: 1 << (unix.RTNLGRP_NEIGH - 1),
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "subscribe neighbor updates"))
|
||||
return
|
||||
}
|
||||
defer connection.Close()
|
||||
for {
|
||||
select {
|
||||
case <-r.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
err = connection.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
if err != nil {
|
||||
r.logger.Warn(E.Cause(err, "set netlink read deadline"))
|
||||
return
|
||||
}
|
||||
messages, err := connection.Receive()
|
||||
if err != nil {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-r.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
r.logger.Warn(E.Cause(err, "receive neighbor update"))
|
||||
continue
|
||||
}
|
||||
for _, message := range messages {
|
||||
address, mac, isDelete, ok := ParseNeighborMessage(message)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
r.access.Lock()
|
||||
if isDelete {
|
||||
delete(r.neighborIPToMAC, address)
|
||||
} else {
|
||||
r.neighborIPToMAC[address] = mac
|
||||
}
|
||||
r.access.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *neighborResolver) doReloadLeaseFiles() {
|
||||
leaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles)
|
||||
r.access.Lock()
|
||||
r.leaseIPToMAC = leaseIPToMAC
|
||||
r.ipToHostname = ipToHostname
|
||||
r.macToHostname = macToHostname
|
||||
r.access.Unlock()
|
||||
}
|
||||
50
route/neighbor_resolver_parse.go
Normal file
50
route/neighbor_resolver_parse.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func extractMACFromDUID(duid []byte) (net.HardwareAddr, bool) {
|
||||
if len(duid) < 4 {
|
||||
return nil, false
|
||||
}
|
||||
duidType := binary.BigEndian.Uint16(duid[0:2])
|
||||
hwType := binary.BigEndian.Uint16(duid[2:4])
|
||||
if hwType != 1 {
|
||||
return nil, false
|
||||
}
|
||||
switch duidType {
|
||||
case 1:
|
||||
if len(duid) < 14 {
|
||||
return nil, false
|
||||
}
|
||||
return net.HardwareAddr(slices.Clone(duid[8:14])), true
|
||||
case 3:
|
||||
if len(duid) < 10 {
|
||||
return nil, false
|
||||
}
|
||||
return net.HardwareAddr(slices.Clone(duid[4:10])), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func extractMACFromEUI64(address netip.Addr) (net.HardwareAddr, bool) {
|
||||
if !address.Is6() {
|
||||
return nil, false
|
||||
}
|
||||
b := address.As16()
|
||||
if b[11] != 0xff || b[12] != 0xfe {
|
||||
return nil, false
|
||||
}
|
||||
return net.HardwareAddr{b[8] ^ 0x02, b[9], b[10], b[13], b[14], b[15]}, true
|
||||
}
|
||||
|
||||
func parseDUID(s string) ([]byte, error) {
|
||||
cleaned := strings.ReplaceAll(s, ":", "")
|
||||
return hex.DecodeString(cleaned)
|
||||
}
|
||||
84
route/neighbor_resolver_platform.go
Normal file
84
route/neighbor_resolver_platform.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type platformNeighborResolver struct {
|
||||
logger logger.ContextLogger
|
||||
platform adapter.PlatformInterface
|
||||
access sync.RWMutex
|
||||
ipToMAC map[netip.Addr]net.HardwareAddr
|
||||
ipToHostname map[netip.Addr]string
|
||||
macToHostname map[string]string
|
||||
}
|
||||
|
||||
func newPlatformNeighborResolver(resolverLogger logger.ContextLogger, platform adapter.PlatformInterface) adapter.NeighborResolver {
|
||||
return &platformNeighborResolver{
|
||||
logger: resolverLogger,
|
||||
platform: platform,
|
||||
ipToMAC: make(map[netip.Addr]net.HardwareAddr),
|
||||
ipToHostname: make(map[netip.Addr]string),
|
||||
macToHostname: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *platformNeighborResolver) Start() error {
|
||||
return r.platform.StartNeighborMonitor(r)
|
||||
}
|
||||
|
||||
func (r *platformNeighborResolver) Close() error {
|
||||
return r.platform.CloseNeighborMonitor(r)
|
||||
}
|
||||
|
||||
func (r *platformNeighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {
|
||||
r.access.RLock()
|
||||
defer r.access.RUnlock()
|
||||
mac, found := r.ipToMAC[address]
|
||||
if found {
|
||||
return mac, true
|
||||
}
|
||||
return extractMACFromEUI64(address)
|
||||
}
|
||||
|
||||
func (r *platformNeighborResolver) LookupHostname(address netip.Addr) (string, bool) {
|
||||
r.access.RLock()
|
||||
defer r.access.RUnlock()
|
||||
hostname, found := r.ipToHostname[address]
|
||||
if found {
|
||||
return hostname, true
|
||||
}
|
||||
mac, found := r.ipToMAC[address]
|
||||
if !found {
|
||||
mac, found = extractMACFromEUI64(address)
|
||||
}
|
||||
if !found {
|
||||
return "", false
|
||||
}
|
||||
hostname, found = r.macToHostname[mac.String()]
|
||||
return hostname, found
|
||||
}
|
||||
|
||||
func (r *platformNeighborResolver) UpdateNeighborTable(entries []adapter.NeighborEntry) {
|
||||
ipToMAC := make(map[netip.Addr]net.HardwareAddr)
|
||||
ipToHostname := make(map[netip.Addr]string)
|
||||
macToHostname := make(map[string]string)
|
||||
for _, entry := range entries {
|
||||
ipToMAC[entry.Address] = entry.MACAddress
|
||||
if entry.Hostname != "" {
|
||||
ipToHostname[entry.Address] = entry.Hostname
|
||||
macToHostname[entry.MACAddress.String()] = entry.Hostname
|
||||
}
|
||||
}
|
||||
r.access.Lock()
|
||||
r.ipToMAC = ipToMAC
|
||||
r.ipToHostname = ipToHostname
|
||||
r.macToHostname = macToHostname
|
||||
r.access.Unlock()
|
||||
r.logger.Info("updated neighbor table: ", len(entries), " entries")
|
||||
}
|
||||
14
route/neighbor_resolver_stub.go
Normal file
14
route/neighbor_resolver_stub.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build !linux && !darwin
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
func newNeighborResolver(_ logger.ContextLogger, _ []string) (adapter.NeighborResolver, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
104
route/neighbor_table_darwin.go
Normal file
104
route/neighbor_table_darwin.go
Normal file
@@ -0,0 +1,104 @@
|
||||
//go:build darwin
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func ReadNeighborEntries() ([]adapter.NeighborEntry, error) {
|
||||
var entries []adapter.NeighborEntry
|
||||
ipv4Entries, err := readNeighborEntriesAF(syscall.AF_INET)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read IPv4 neighbors")
|
||||
}
|
||||
entries = append(entries, ipv4Entries...)
|
||||
ipv6Entries, err := readNeighborEntriesAF(syscall.AF_INET6)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read IPv6 neighbors")
|
||||
}
|
||||
entries = append(entries, ipv6Entries...)
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func readNeighborEntriesAF(addressFamily int) ([]adapter.NeighborEntry, error) {
|
||||
rib, err := route.FetchRIB(addressFamily, route.RIBType(syscall.NET_RT_FLAGS), syscall.RTF_LLINFO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messages, err := route.ParseRIB(route.RIBType(syscall.NET_RT_FLAGS), rib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entries []adapter.NeighborEntry
|
||||
for _, message := range messages {
|
||||
routeMessage, isRouteMessage := message.(*route.RouteMessage)
|
||||
if !isRouteMessage {
|
||||
continue
|
||||
}
|
||||
address, macAddress, ok := parseRouteNeighborEntry(routeMessage)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, adapter.NeighborEntry{
|
||||
Address: address,
|
||||
MACAddress: macAddress,
|
||||
})
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func parseRouteNeighborEntry(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, ok bool) {
|
||||
if len(message.Addrs) <= unix.RTAX_GATEWAY {
|
||||
return
|
||||
}
|
||||
gateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr)
|
||||
if !isLinkAddr || len(gateway.Addr) < 6 {
|
||||
return
|
||||
}
|
||||
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
||||
case *route.Inet4Addr:
|
||||
address = netip.AddrFrom4(destination.IP)
|
||||
case *route.Inet6Addr:
|
||||
address = netip.AddrFrom16(destination.IP)
|
||||
default:
|
||||
return
|
||||
}
|
||||
macAddress = net.HardwareAddr(make([]byte, len(gateway.Addr)))
|
||||
copy(macAddress, gateway.Addr)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func ParseRouteNeighborMessage(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) {
|
||||
isDelete = message.Type == unix.RTM_DELETE
|
||||
if len(message.Addrs) <= unix.RTAX_GATEWAY {
|
||||
return
|
||||
}
|
||||
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
||||
case *route.Inet4Addr:
|
||||
address = netip.AddrFrom4(destination.IP)
|
||||
case *route.Inet6Addr:
|
||||
address = netip.AddrFrom16(destination.IP)
|
||||
default:
|
||||
return
|
||||
}
|
||||
if !isDelete {
|
||||
gateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr)
|
||||
if !isLinkAddr || len(gateway.Addr) < 6 {
|
||||
return
|
||||
}
|
||||
macAddress = net.HardwareAddr(make([]byte, len(gateway.Addr)))
|
||||
copy(macAddress, gateway.Addr)
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
68
route/neighbor_table_linux.go
Normal file
68
route/neighbor_table_linux.go
Normal file
@@ -0,0 +1,68 @@
|
||||
//go:build linux
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func ReadNeighborEntries() ([]adapter.NeighborEntry, error) {
|
||||
connection, err := rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "dial rtnetlink")
|
||||
}
|
||||
defer connection.Close()
|
||||
neighbors, err := connection.Neigh.List()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "list neighbors")
|
||||
}
|
||||
var entries []adapter.NeighborEntry
|
||||
for _, neighbor := range neighbors {
|
||||
if neighbor.Attributes == nil {
|
||||
continue
|
||||
}
|
||||
if neighbor.Attributes.LLAddress == nil || len(neighbor.Attributes.Address) == 0 {
|
||||
continue
|
||||
}
|
||||
address, ok := netip.AddrFromSlice(neighbor.Attributes.Address)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, adapter.NeighborEntry{
|
||||
Address: address,
|
||||
MACAddress: slices.Clone(neighbor.Attributes.LLAddress),
|
||||
})
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func ParseNeighborMessage(message netlink.Message) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) {
|
||||
var neighMessage rtnetlink.NeighMessage
|
||||
err := neighMessage.UnmarshalBinary(message.Data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if neighMessage.Attributes == nil || len(neighMessage.Attributes.Address) == 0 {
|
||||
return
|
||||
}
|
||||
address, ok = netip.AddrFromSlice(neighMessage.Attributes.Address)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
isDelete = message.Header.Type == unix.RTM_DELNEIGH
|
||||
if !isDelete && neighMessage.Attributes.LLAddress == nil {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
macAddress = slices.Clone(neighMessage.Attributes.LLAddress)
|
||||
return
|
||||
}
|
||||
@@ -439,6 +439,23 @@ func (r *Router) matchRule(
|
||||
metadata.ProcessInfo = processInfo
|
||||
}
|
||||
}
|
||||
if r.neighborResolver != nil && metadata.SourceMACAddress == nil && metadata.Source.Addr.IsValid() {
|
||||
mac, macFound := r.neighborResolver.LookupMAC(metadata.Source.Addr)
|
||||
if macFound {
|
||||
metadata.SourceMACAddress = mac
|
||||
}
|
||||
hostname, hostnameFound := r.neighborResolver.LookupHostname(metadata.Source.Addr)
|
||||
if hostnameFound {
|
||||
metadata.SourceHostname = hostname
|
||||
if macFound {
|
||||
r.logger.InfoContext(ctx, "found neighbor: ", mac, ", hostname: ", hostname)
|
||||
} else {
|
||||
r.logger.InfoContext(ctx, "found neighbor hostname: ", hostname)
|
||||
}
|
||||
} else if macFound {
|
||||
r.logger.InfoContext(ctx, "found neighbor: ", mac)
|
||||
}
|
||||
}
|
||||
if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {
|
||||
domain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr)
|
||||
if !loaded {
|
||||
|
||||
@@ -31,9 +31,12 @@ type Router struct {
|
||||
network adapter.NetworkManager
|
||||
rules []adapter.Rule
|
||||
needFindProcess bool
|
||||
needFindNeighbor bool
|
||||
leaseFiles []string
|
||||
ruleSets []adapter.RuleSet
|
||||
ruleSetMap map[string]adapter.RuleSet
|
||||
processSearcher process.Searcher
|
||||
neighborResolver adapter.NeighborResolver
|
||||
pauseManager pause.Manager
|
||||
trackers []adapter.ConnectionTracker
|
||||
platformInterface adapter.PlatformInterface
|
||||
@@ -53,6 +56,8 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
|
||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||
ruleSetMap: make(map[string]adapter.RuleSet),
|
||||
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
||||
needFindNeighbor: hasRule(options.Rules, isNeighborRule) || hasDNSRule(dnsOptions.Rules, isNeighborDNSRule) || options.FindNeighbor,
|
||||
leaseFiles: options.DHCPLeaseFiles,
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||
}
|
||||
@@ -112,6 +117,7 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
r.network.Initialize(r.ruleSets)
|
||||
needFindProcess := r.needFindProcess
|
||||
needFindNeighbor := r.needFindNeighbor
|
||||
for _, ruleSet := range r.ruleSets {
|
||||
metadata := ruleSet.Metadata()
|
||||
if metadata.ContainsProcessRule {
|
||||
@@ -141,6 +147,36 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
r.needFindNeighbor = needFindNeighbor
|
||||
if needFindNeighbor {
|
||||
if r.platformInterface != nil && r.platformInterface.UsePlatformNeighborResolver() {
|
||||
monitor.Start("initialize neighbor resolver")
|
||||
resolver := newPlatformNeighborResolver(r.logger, r.platformInterface)
|
||||
err := resolver.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
r.logger.Error(E.Cause(err, "start neighbor resolver"))
|
||||
} else {
|
||||
r.neighborResolver = resolver
|
||||
}
|
||||
} else {
|
||||
monitor.Start("initialize neighbor resolver")
|
||||
resolver, err := newNeighborResolver(r.logger, r.leaseFiles)
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
if err != os.ErrInvalid {
|
||||
r.logger.Error(E.Cause(err, "create neighbor resolver"))
|
||||
}
|
||||
} else {
|
||||
err = resolver.Start()
|
||||
if err != nil {
|
||||
r.logger.Error(E.Cause(err, "start neighbor resolver"))
|
||||
} else {
|
||||
r.neighborResolver = resolver
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case adapter.StartStatePostStart:
|
||||
for i, rule := range r.rules {
|
||||
monitor.Start("initialize rule[", i, "]")
|
||||
@@ -172,6 +208,13 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||
func (r *Router) Close() error {
|
||||
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||
var err error
|
||||
if r.neighborResolver != nil {
|
||||
monitor.Start("close neighbor resolver")
|
||||
err = E.Append(err, r.neighborResolver.Close(), func(closeErr error) error {
|
||||
return E.Cause(closeErr, "close neighbor resolver")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
for i, rule := range r.rules {
|
||||
monitor.Start("close rule[", i, "]")
|
||||
err = E.Append(err, rule.Close(), func(err error) error {
|
||||
@@ -206,6 +249,14 @@ func (r *Router) NeedFindProcess() bool {
|
||||
return r.needFindProcess
|
||||
}
|
||||
|
||||
func (r *Router) NeedFindNeighbor() bool {
|
||||
return r.needFindNeighbor
|
||||
}
|
||||
|
||||
func (r *Router) NeighborResolver() adapter.NeighborResolver {
|
||||
return r.neighborResolver
|
||||
}
|
||||
|
||||
func (r *Router) ResetNetwork() {
|
||||
r.network.ResetNetwork()
|
||||
r.dns.ResetNetwork()
|
||||
|
||||
@@ -260,6 +260,16 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourceMACAddress) > 0 {
|
||||
item := NewSourceMACAddressItem(options.SourceMACAddress)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourceHostname) > 0 {
|
||||
item := NewSourceHostnameItem(options.SourceHostname)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.PreferredBy) > 0 {
|
||||
item := NewPreferredByItem(ctx, options.PreferredBy)
|
||||
rule.items = append(rule.items, item)
|
||||
|
||||
@@ -261,6 +261,16 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourceMACAddress) > 0 {
|
||||
item := NewSourceMACAddressItem(options.SourceMACAddress)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourceHostname) > 0 {
|
||||
item := NewSourceHostnameItem(options.SourceHostname)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.RuleSet) > 0 {
|
||||
//nolint:staticcheck
|
||||
if options.Deprecated_RulesetIPCIDRMatchSource {
|
||||
|
||||
42
route/rule/rule_item_source_hostname.go
Normal file
42
route/rule/rule_item_source_hostname.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*SourceHostnameItem)(nil)
|
||||
|
||||
type SourceHostnameItem struct {
|
||||
hostnames []string
|
||||
hostnameMap map[string]bool
|
||||
}
|
||||
|
||||
func NewSourceHostnameItem(hostnameList []string) *SourceHostnameItem {
|
||||
rule := &SourceHostnameItem{
|
||||
hostnames: hostnameList,
|
||||
hostnameMap: make(map[string]bool),
|
||||
}
|
||||
for _, hostname := range hostnameList {
|
||||
rule.hostnameMap[hostname] = true
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *SourceHostnameItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.SourceHostname == "" {
|
||||
return false
|
||||
}
|
||||
return r.hostnameMap[metadata.SourceHostname]
|
||||
}
|
||||
|
||||
func (r *SourceHostnameItem) String() string {
|
||||
var description string
|
||||
if len(r.hostnames) == 1 {
|
||||
description = "source_hostname=" + r.hostnames[0]
|
||||
} else {
|
||||
description = "source_hostname=[" + strings.Join(r.hostnames, " ") + "]"
|
||||
}
|
||||
return description
|
||||
}
|
||||
48
route/rule/rule_item_source_mac_address.go
Normal file
48
route/rule/rule_item_source_mac_address.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*SourceMACAddressItem)(nil)
|
||||
|
||||
type SourceMACAddressItem struct {
|
||||
addresses []string
|
||||
addressMap map[string]bool
|
||||
}
|
||||
|
||||
func NewSourceMACAddressItem(addressList []string) *SourceMACAddressItem {
|
||||
rule := &SourceMACAddressItem{
|
||||
addresses: addressList,
|
||||
addressMap: make(map[string]bool),
|
||||
}
|
||||
for _, address := range addressList {
|
||||
parsed, err := net.ParseMAC(address)
|
||||
if err == nil {
|
||||
rule.addressMap[parsed.String()] = true
|
||||
} else {
|
||||
rule.addressMap[address] = true
|
||||
}
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *SourceMACAddressItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.SourceMACAddress == nil {
|
||||
return false
|
||||
}
|
||||
return r.addressMap[metadata.SourceMACAddress.String()]
|
||||
}
|
||||
|
||||
func (r *SourceMACAddressItem) String() string {
|
||||
var description string
|
||||
if len(r.addresses) == 1 {
|
||||
description = "source_mac_address=" + r.addresses[0]
|
||||
} else {
|
||||
description = "source_mac_address=[" + strings.Join(r.addresses, " ") + "]"
|
||||
}
|
||||
return description
|
||||
}
|
||||
@@ -45,6 +45,14 @@ func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||
}
|
||||
|
||||
func isNeighborRule(rule option.DefaultRule) bool {
|
||||
return len(rule.SourceMACAddress) > 0 || len(rule.SourceHostname) > 0
|
||||
}
|
||||
|
||||
func isNeighborDNSRule(rule option.DefaultDNSRule) bool {
|
||||
return len(rule.SourceMACAddress) > 0 || len(rule.SourceHostname) > 0
|
||||
}
|
||||
|
||||
func isWIFIRule(rule option.DefaultRule) bool {
|
||||
return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user