mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
Add MAC and hostname rule items
This commit is contained in:
@@ -2,6 +2,7 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -82,6 +83,8 @@ type InboundContext struct {
|
|||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *ConnectionOwner
|
ProcessInfo *ConnectionOwner
|
||||||
|
SourceMACAddress net.HardwareAddr
|
||||||
|
SourceHostname string
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
|
|||||||
13
adapter/neighbor.go
Normal file
13
adapter/neighbor.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NeighborResolver interface {
|
||||||
|
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
|
||||||
|
LookupHostname(address netip.Addr) (string, bool)
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ type Router interface {
|
|||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
NeedFindProcess() bool
|
NeedFindProcess() bool
|
||||||
|
NeedFindNeighbor() bool
|
||||||
|
NeighborResolver() NeighborResolver
|
||||||
AppendTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/alert-decagram
|
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"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -149,6 +154,12 @@ icon: material/alert-decagram
|
|||||||
"default_interface_address": [
|
"default_interface_address": [
|
||||||
"2000::/3"
|
"2000::/3"
|
||||||
],
|
],
|
||||||
|
"source_mac_address": [
|
||||||
|
"00:11:22:33:44:55"
|
||||||
|
],
|
||||||
|
"source_hostname": [
|
||||||
|
"my-device"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -408,6 +419,26 @@ Matches network interface (same values as `network_type`) address.
|
|||||||
|
|
||||||
Match default interface address.
|
Match default interface address.
|
||||||
|
|
||||||
|
#### source_mac_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with `route.find_neighbor` enabled.
|
||||||
|
|
||||||
|
Match source device MAC address.
|
||||||
|
|
||||||
|
#### source_hostname
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with `route.find_neighbor` enabled.
|
||||||
|
|
||||||
|
Match source device hostname from DHCP leases.
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/alert-decagram
|
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 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -149,6 +154,12 @@ icon: material/alert-decagram
|
|||||||
"default_interface_address": [
|
"default_interface_address": [
|
||||||
"2000::/3"
|
"2000::/3"
|
||||||
],
|
],
|
||||||
|
"source_mac_address": [
|
||||||
|
"00:11:22:33:44:55"
|
||||||
|
],
|
||||||
|
"source_hostname": [
|
||||||
|
"my-device"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -407,6 +418,26 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配默认接口地址。
|
匹配默认接口地址。
|
||||||
|
|
||||||
|
#### source_mac_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux,且需要 `route.find_neighbor` 已启用。
|
||||||
|
|
||||||
|
匹配源设备 MAC 地址。
|
||||||
|
|
||||||
|
#### source_hostname
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux,且需要 `route.find_neighbor` 已启用。
|
||||||
|
|
||||||
|
匹配源设备从 DHCP 租约获取的主机名。
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|||||||
@@ -134,6 +134,12 @@ icon: material/new-box
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
|
"include_mac_address": [
|
||||||
|
"00:11:22:33:44:55"
|
||||||
|
],
|
||||||
|
"exclude_mac_address": [
|
||||||
|
"66:77:88:99:aa:bb"
|
||||||
|
],
|
||||||
"platform": {
|
"platform": {
|
||||||
"http_proxy": {
|
"http_proxy": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -560,6 +566,30 @@ Limit android packages in route.
|
|||||||
|
|
||||||
Exclude 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
|
||||||
|
|
||||||
Platform-specific settings, provided by client applications.
|
Platform-specific settings, provided by client applications.
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
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.3 中的更改"
|
!!! quote "sing-box 1.13.3 中的更改"
|
||||||
|
|
||||||
:material-alert: [strict_route](#strict_route)
|
:material-alert: [strict_route](#strict_route)
|
||||||
@@ -130,6 +135,12 @@ icon: material/new-box
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
|
"include_mac_address": [
|
||||||
|
"00:11:22:33:44:55"
|
||||||
|
],
|
||||||
|
"exclude_mac_address": [
|
||||||
|
"66:77:88:99:aa:bb"
|
||||||
|
],
|
||||||
"platform": {
|
"platform": {
|
||||||
"http_proxy": {
|
"http_proxy": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -543,6 +554,30 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
排除路由的 Android 应用包名。
|
排除路由的 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
|
#### platform
|
||||||
|
|
||||||
平台特定的设置,由客户端应用提供。
|
平台特定的设置,由客户端应用提供。
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
# Route
|
# 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"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||||
@@ -35,6 +40,8 @@ icon: material/alert-decagram
|
|||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "",
|
||||||
"default_mark": 0,
|
"default_mark": 0,
|
||||||
|
"find_neighbor": false,
|
||||||
|
"dhcp_lease_files": [],
|
||||||
"default_domain_resolver": "", // or {}
|
"default_domain_resolver": "", // or {}
|
||||||
"default_network_strategy": "",
|
"default_network_strategy": "",
|
||||||
"default_network_type": [],
|
"default_network_type": [],
|
||||||
@@ -107,6 +114,30 @@ Set routing mark by default.
|
|||||||
|
|
||||||
Takes no effect if `outbound.routing_mark` is set.
|
Takes no effect if `outbound.routing_mark` is set.
|
||||||
|
|
||||||
|
#### find_neighbor
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux.
|
||||||
|
|
||||||
|
Enable neighbor resolution for source MAC address and hostname lookup.
|
||||||
|
|
||||||
|
Required for `source_mac_address` and `source_hostname` rule items.
|
||||||
|
|
||||||
|
#### dhcp_lease_files
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux.
|
||||||
|
|
||||||
|
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
|
#### default_domain_resolver
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! 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 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||||
@@ -37,6 +42,8 @@ icon: material/alert-decagram
|
|||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "",
|
||||||
"default_mark": 0,
|
"default_mark": 0,
|
||||||
|
"find_neighbor": false,
|
||||||
|
"dhcp_lease_files": [],
|
||||||
"default_network_strategy": "",
|
"default_network_strategy": "",
|
||||||
"default_fallback_delay": ""
|
"default_fallback_delay": ""
|
||||||
}
|
}
|
||||||
@@ -106,6 +113,30 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||||
|
|
||||||
|
#### find_neighbor
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux。
|
||||||
|
|
||||||
|
启用邻居解析以查找源 MAC 地址和主机名。
|
||||||
|
|
||||||
|
`source_mac_address` 和 `source_hostname` 规则项需要此选项。
|
||||||
|
|
||||||
|
#### dhcp_lease_files
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux。
|
||||||
|
|
||||||
|
用于主机名和 MAC 地址解析的自定义 DHCP 租约文件路径。
|
||||||
|
|
||||||
|
为空时自动从常见 DHCP 服务器(dnsmasq、odhcpd、ISC dhcpd、Kea)检测。
|
||||||
|
|
||||||
#### default_domain_resolver
|
#### default_domain_resolver
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
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"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -159,6 +164,12 @@ icon: material/new-box
|
|||||||
"tailscale",
|
"tailscale",
|
||||||
"wireguard"
|
"wireguard"
|
||||||
],
|
],
|
||||||
|
"source_mac_address": [
|
||||||
|
"00:11:22:33:44:55"
|
||||||
|
],
|
||||||
|
"source_hostname": [
|
||||||
|
"my-device"
|
||||||
|
],
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@@ -449,6 +460,26 @@ Match specified outbounds' preferred routes.
|
|||||||
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||||
| `wireguard` | Match peers's allowed IPs |
|
| `wireguard` | Match peers's allowed IPs |
|
||||||
|
|
||||||
|
#### source_mac_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with `route.find_neighbor` enabled.
|
||||||
|
|
||||||
|
Match source device MAC address.
|
||||||
|
|
||||||
|
#### source_hostname
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with `route.find_neighbor` enabled.
|
||||||
|
|
||||||
|
Match source device hostname from DHCP leases.
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
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 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -157,6 +162,12 @@ icon: material/new-box
|
|||||||
"tailscale",
|
"tailscale",
|
||||||
"wireguard"
|
"wireguard"
|
||||||
],
|
],
|
||||||
|
"source_mac_address": [
|
||||||
|
"00:11:22:33:44:55"
|
||||||
|
],
|
||||||
|
"source_hostname": [
|
||||||
|
"my-device"
|
||||||
|
],
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@@ -447,6 +458,26 @@ icon: material/new-box
|
|||||||
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||||
| `wireguard` | 匹配对端的 allowed IPs |
|
| `wireguard` | 匹配对端的 allowed IPs |
|
||||||
|
|
||||||
|
#### source_mac_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux,且需要 `route.find_neighbor` 已启用。
|
||||||
|
|
||||||
|
匹配源设备 MAC 地址。
|
||||||
|
|
||||||
|
#### source_hostname
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux,且需要 `route.find_neighbor` 已启用。
|
||||||
|
|
||||||
|
匹配源设备从 DHCP 租约获取的主机名。
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -14,11 +14,13 @@ require (
|
|||||||
github.com/godbus/dbus/v5 v5.2.2
|
github.com/godbus/dbus/v5 v5.2.2
|
||||||
github.com/gofrs/uuid/v5 v5.4.0
|
github.com/gofrs/uuid/v5 v5.4.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91
|
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/keybase/go-keychain v0.0.1
|
||||||
github.com/libdns/acmedns v0.5.0
|
github.com/libdns/acmedns v0.5.0
|
||||||
github.com/libdns/alidns v1.0.6
|
github.com/libdns/alidns v1.0.6
|
||||||
github.com/libdns/cloudflare v0.2.2
|
github.com/libdns/cloudflare v0.2.2
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
|
github.com/mdlayher/netlink v1.9.0
|
||||||
github.com/metacubex/utls v1.8.4
|
github.com/metacubex/utls v1.8.4
|
||||||
github.com/mholt/acmez/v3 v3.1.6
|
github.com/mholt/acmez/v3 v3.1.6
|
||||||
github.com/miekg/dns v1.1.72
|
github.com/miekg/dns v1.1.72
|
||||||
@@ -39,7 +41,7 @@ require (
|
|||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.8.7
|
github.com/sagernet/sing-tun v0.8.8-0.20260410061515-018f5eaae695
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
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/smux v1.5.50-sing-box-mod.1
|
||||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
|
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
|
||||||
@@ -92,11 +94,9 @@ require (
|
|||||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.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/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/libdns/libdns v1.1.1 // 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/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -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-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 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.8.7 h1:q49cI7Cbp+BcgzaJitQ9QdLO77BqnnaQRkSEMoGmF3g=
|
github.com/sagernet/sing-tun v0.8.8-0.20260410061515-018f5eaae695 h1:2maqN3XuorEo5faXHIyYZQZ1/ybim4hImfCEWZwdPbk=
|
||||||
github.com/sagernet/sing-tun v0.8.7/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
github.com/sagernet/sing-tun v0.8.8-0.20260410061515-018f5eaae695/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 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
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=
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ type RouteOptions struct {
|
|||||||
RuleSet []RuleSet `json:"rule_set,omitempty"`
|
RuleSet []RuleSet `json:"rule_set,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
FindProcess bool `json:"find_process,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"`
|
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
||||||
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
||||||
DefaultInterface string `json:"default_interface,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"`
|
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"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_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"`
|
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,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"`
|
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"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_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"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,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"`
|
IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"`
|
||||||
IncludePackage badoption.Listable[string] `json:"include_package,omitempty"`
|
IncludePackage badoption.Listable[string] `json:"include_package,omitempty"`
|
||||||
ExcludePackage badoption.Listable[string] `json:"exclude_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"`
|
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
||||||
Stack string `json:"stack,omitempty"`
|
Stack string `json:"stack,omitempty"`
|
||||||
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
||||||
|
|||||||
@@ -160,6 +160,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
if nfQueue == 0 {
|
if nfQueue == 0 {
|
||||||
nfQueue = tun.DefaultAutoRedirectNFQueue
|
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)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
||||||
inbound := &Inbound{
|
inbound := &Inbound{
|
||||||
@@ -197,6 +213,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
IncludePackage: options.IncludePackage,
|
IncludePackage: options.IncludePackage,
|
||||||
ExcludePackage: options.ExcludePackage,
|
ExcludePackage: options.ExcludePackage,
|
||||||
|
IncludeMACAddress: includeMACAddress,
|
||||||
|
ExcludeMACAddress: excludeMACAddress,
|
||||||
InterfaceMonitor: networkManager.InterfaceMonitor(),
|
InterfaceMonitor: networkManager.InterfaceMonitor(),
|
||||||
EXP_MultiPendingPackets: multiPendingPackets,
|
EXP_MultiPendingPackets: multiPendingPackets,
|
||||||
},
|
},
|
||||||
|
|||||||
596
route/neighbor_resolver_linux.go
Normal file
596
route/neighbor_resolver_linux.go
Normal file
@@ -0,0 +1,596 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"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.reloadLeaseFiles()
|
||||||
|
go r.subscribeNeighborUpdates()
|
||||||
|
if len(r.leaseFiles) > 0 {
|
||||||
|
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||||
|
Path: r.leaseFiles,
|
||||||
|
Logger: r.logger,
|
||||||
|
Callback: func(_ string) {
|
||||||
|
r.reloadLeaseFiles()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
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 {
|
||||||
|
switch message.Header.Type {
|
||||||
|
case unix.RTM_NEWNEIGH:
|
||||||
|
var neighMessage rtnetlink.NeighMessage
|
||||||
|
unmarshalErr := neighMessage.UnmarshalBinary(message.Data)
|
||||||
|
if unmarshalErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if neighMessage.Attributes == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if neighMessage.Attributes.LLAddress == nil || len(neighMessage.Attributes.Address) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
address, ok := netip.AddrFromSlice(neighMessage.Attributes.Address)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.access.Lock()
|
||||||
|
r.neighborIPToMAC[address] = slices.Clone(neighMessage.Attributes.LLAddress)
|
||||||
|
r.access.Unlock()
|
||||||
|
case unix.RTM_DELNEIGH:
|
||||||
|
var neighMessage rtnetlink.NeighMessage
|
||||||
|
unmarshalErr := neighMessage.UnmarshalBinary(message.Data)
|
||||||
|
if unmarshalErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if neighMessage.Attributes == nil || len(neighMessage.Attributes.Address) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
address, ok := netip.AddrFromSlice(neighMessage.Attributes.Address)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.access.Lock()
|
||||||
|
delete(r.neighborIPToMAC, address)
|
||||||
|
r.access.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *neighborResolver) reloadLeaseFiles() {
|
||||||
|
leaseIPToMAC := make(map[netip.Addr]net.HardwareAddr)
|
||||||
|
ipToHostname := make(map[netip.Addr]string)
|
||||||
|
macToHostname := make(map[string]string)
|
||||||
|
for _, path := range r.leaseFiles {
|
||||||
|
r.parseLeaseFile(path, leaseIPToMAC, ipToHostname, macToHostname)
|
||||||
|
}
|
||||||
|
r.access.Lock()
|
||||||
|
r.leaseIPToMAC = leaseIPToMAC
|
||||||
|
r.ipToHostname = ipToHostname
|
||||||
|
r.macToHostname = macToHostname
|
||||||
|
r.access.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *neighborResolver) 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, "kea-leases4.csv") {
|
||||||
|
r.parseKeaCSV4(file, ipToMAC, ipToHostname, macToHostname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, "kea-leases6.csv") {
|
||||||
|
r.parseKeaCSV6(file, ipToMAC, ipToHostname, macToHostname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, "dhcpd.leases") {
|
||||||
|
r.parseISCDhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.parseDnsmasqOdhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *neighborResolver) 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, "# ") {
|
||||||
|
r.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 (r *neighborResolver) 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 (r *neighborResolver) 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 (r *neighborResolver) 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 (r *neighborResolver) 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 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)
|
||||||
|
}
|
||||||
14
route/neighbor_resolver_stub.go
Normal file
14
route/neighbor_resolver_stub.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -438,6 +438,23 @@ func (r *Router) matchRule(
|
|||||||
metadata.ProcessInfo = processInfo
|
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) {
|
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)
|
domain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
|
|||||||
@@ -35,10 +35,13 @@ type Router struct {
|
|||||||
network adapter.NetworkManager
|
network adapter.NetworkManager
|
||||||
rules []adapter.Rule
|
rules []adapter.Rule
|
||||||
needFindProcess bool
|
needFindProcess bool
|
||||||
|
needFindNeighbor bool
|
||||||
|
leaseFiles []string
|
||||||
ruleSets []adapter.RuleSet
|
ruleSets []adapter.RuleSet
|
||||||
ruleSetMap map[string]adapter.RuleSet
|
ruleSetMap map[string]adapter.RuleSet
|
||||||
processSearcher process.Searcher
|
processSearcher process.Searcher
|
||||||
processCache freelru.Cache[processCacheKey, processCacheEntry]
|
processCache freelru.Cache[processCacheKey, processCacheEntry]
|
||||||
|
neighborResolver adapter.NeighborResolver
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
trackers []adapter.ConnectionTracker
|
trackers []adapter.ConnectionTracker
|
||||||
platformInterface adapter.PlatformInterface
|
platformInterface adapter.PlatformInterface
|
||||||
@@ -58,6 +61,8 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
|
|||||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||||
ruleSetMap: make(map[string]adapter.RuleSet),
|
ruleSetMap: make(map[string]adapter.RuleSet),
|
||||||
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
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),
|
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||||
}
|
}
|
||||||
@@ -117,6 +122,7 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
r.network.Initialize(r.ruleSets)
|
r.network.Initialize(r.ruleSets)
|
||||||
needFindProcess := r.needFindProcess
|
needFindProcess := r.needFindProcess
|
||||||
|
needFindNeighbor := r.needFindNeighbor
|
||||||
for _, ruleSet := range r.ruleSets {
|
for _, ruleSet := range r.ruleSets {
|
||||||
metadata := ruleSet.Metadata()
|
metadata := ruleSet.Metadata()
|
||||||
if metadata.ContainsProcessRule {
|
if metadata.ContainsProcessRule {
|
||||||
@@ -151,6 +157,24 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
processCache.SetLifetime(200 * time.Millisecond)
|
processCache.SetLifetime(200 * time.Millisecond)
|
||||||
r.processCache = processCache
|
r.processCache = processCache
|
||||||
}
|
}
|
||||||
|
r.needFindNeighbor = needFindNeighbor
|
||||||
|
if needFindNeighbor {
|
||||||
|
monitor.Start("initialize neighbor resolver")
|
||||||
|
resolver, err := newNeighborResolver(r.logger, r.leaseFiles)
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
if err != os.ErrInvalid {
|
||||||
|
r.logger.Warn(E.Cause(err, "create neighbor resolver"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = resolver.Start()
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Warn(E.Cause(err, "start neighbor resolver"))
|
||||||
|
} else {
|
||||||
|
r.neighborResolver = resolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
case adapter.StartStatePostStart:
|
case adapter.StartStatePostStart:
|
||||||
for i, rule := range r.rules {
|
for i, rule := range r.rules {
|
||||||
monitor.Start("initialize rule[", i, "]")
|
monitor.Start("initialize rule[", i, "]")
|
||||||
@@ -182,6 +206,13 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
func (r *Router) Close() error {
|
func (r *Router) Close() error {
|
||||||
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||||
var err error
|
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 {
|
for i, rule := range r.rules {
|
||||||
monitor.Start("close rule[", i, "]")
|
monitor.Start("close rule[", i, "]")
|
||||||
err = E.Append(err, rule.Close(), func(err error) error {
|
err = E.Append(err, rule.Close(), func(err error) error {
|
||||||
@@ -223,6 +254,14 @@ func (r *Router) NeedFindProcess() bool {
|
|||||||
return r.needFindProcess
|
return r.needFindProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) NeedFindNeighbor() bool {
|
||||||
|
return r.needFindNeighbor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) NeighborResolver() adapter.NeighborResolver {
|
||||||
|
return r.neighborResolver
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) ResetNetwork() {
|
func (r *Router) ResetNetwork() {
|
||||||
r.network.ResetNetwork()
|
r.network.ResetNetwork()
|
||||||
r.dns.ResetNetwork()
|
r.dns.ResetNetwork()
|
||||||
|
|||||||
@@ -264,6 +264,16 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if 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 {
|
if len(options.PreferredBy) > 0 {
|
||||||
item := NewPreferredByItem(ctx, options.PreferredBy)
|
item := NewPreferredByItem(ctx, options.PreferredBy)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
|
|||||||
@@ -265,6 +265,16 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if 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 {
|
if len(options.RuleSet) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if options.Deprecated_RulesetIPCIDRMatchSource {
|
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
|
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 {
|
func isWIFIRule(rule option.DefaultRule) bool {
|
||||||
return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0
|
return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user