feat(inbounds): local SOCKS/HTTP + dokodemo inbounds [T1/T2]
Adds a Type discriminator to `config inbound` (tproxy|socks|http|dokodemo);
absent Type => tproxy, so existing configs are byte-identical. Local socks/http
listeners and the dokodemo-wrap listener get no sockopt/tproxy and are covered by
the same routing rules (no dedicated inboundTag), so their traffic follows user
policy.
Critical fix: RenderNft/nftEnabledInboundDevs/nftPrimaryInbound now filter on
isTproxyInbound() — previously EVERY enabled inbound injected a LAN tproxy divert,
so a local socks listener would have wrongly diverted LAN traffic to its port.
- model.go: Inbound.{Type,Listen,Port,Auth,User,Pass,TargetAddr,TargetPort,
TargetNetwork} + inboundType()/isTproxyInbound(); uci.go parses them.
- generate.go: buildInbounds dispatches by type (emitTproxyInbound/
emitLocalProxyInbound/emitDokodemoInbound); sniff default off for dokodemo.
- apply.go + nftstats.go: only tproxy inbounds participate in the nft plane.
- settings.js: type selector + dependent listener/auth/target fields.
- tests: inbound_local_test.go (socks/http/dokodemo emit, password auth, legacy
tproxy default, TestNftIgnoresNonTproxyInbounds).
Verified live on the 512M VM: socks inbound {protocol:socks,port:1080,udp:true}
→ xray -test OK; nft does NOT contain port 1080; listener up on 127.0.0.1:1080;
mgmt-bypass chain intact. r11.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -35,11 +35,21 @@ config globals 'globals'
|
||||
config inbound
|
||||
option name 'lan'
|
||||
option enabled '1'
|
||||
option network 'lan' # UCI-интерфейс(ы) LAN для перехвата
|
||||
option type 'tproxy' # tproxy|socks|http|dokodemo (default tproxy)
|
||||
option network 'lan' # tproxy: UCI-интерфейс(ы) LAN для перехвата
|
||||
option tproxy_port '12345'
|
||||
option tcp '1'
|
||||
option udp '1'
|
||||
option sniff '1' # recover SNI/Host/QUIC
|
||||
# --- локальные socks/http/dokodemo (type != tproxy); НЕ участвуют в tproxy-nft ---
|
||||
option listen '127.0.0.1' # socks/http/dokodemo: адрес прослушки
|
||||
option port '1080' # socks/http/dokodemo: порт
|
||||
option auth 'noauth' # socks/http: noauth|password
|
||||
option user '' # auth=password
|
||||
option pass ''
|
||||
option target_addr '' # dokodemo: фикс. адрес назначения (awg-wrap)
|
||||
option target_port '' # dokodemo: фикс. порт
|
||||
option target_network 'udp' # dokodemo: tcp|udp|tcp,udp
|
||||
```
|
||||
|
||||
### subscription (0..N)
|
||||
|
||||
@@ -163,6 +163,7 @@ egress-binding и DNS-рендер; далее T1 (цепочки в UI, per-cli
|
||||
|---|---|
|
||||
| **mux/XUDP + per-node sockopt** (T1) | mux-нода → `{enabled,concurrency:8,xudpConcurrency:16,xudpProxyUDP443:reject}`; loop-guard `mark==255` сохранён; `xray -test OK`. Vision→mux пропускается. |
|
||||
| **WireGuard-outbound** (T2) | `wireguard://`-схема + импорт wg-quick `.conf`; xray 25.1.30 **принимает** wireguard-outbound (`xray -test OK`), endpoint/mark:255 верны; fingerprint по peer-pubkey. AmneziaWG — best-effort (`wgAWGSupported`, mainline не поддерживает). |
|
||||
| **Локальные SOCKS/HTTP + dokodemo-inbounds** (T1/T2) | socks-inbound `{protocol:socks,port:1080,udp:true}` → `xray -test OK`; **nft НЕ содержит порт 1080** (isTproxyInbound-фильтр — локальные листенеры не диверсятся); listener поднялся `127.0.0.1:1080`; mgmt-bypass в nft цел; type пустой ⇒ tproxy (обратная совместимость). |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -66,9 +66,10 @@ return view.extend({
|
||||
o.default = '60s';
|
||||
o.placeholder = '60s';
|
||||
|
||||
// ---------------- inbounds (multi-LAN) ----------------
|
||||
s = m.section(form.GridSection, 'inbound', _('Inbounds (transparent proxy)'),
|
||||
_('One or more LAN networks intercepted via TPROXY.'));
|
||||
// ---------------- inbounds (tproxy + local socks/http/dokodemo) ----------------
|
||||
s = m.section(form.GridSection, 'inbound', _('Inbounds'),
|
||||
_('Transparent TPROXY LAN interception, plus optional local SOCKS/HTTP ' +
|
||||
'proxies and a dokodemo wrap listener.'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
s.nodescriptions = true;
|
||||
@@ -82,13 +83,74 @@ return view.extend({
|
||||
o.default = '1';
|
||||
o.editable = true;
|
||||
|
||||
o = s.option(form.ListValue, 'type', _('Type'));
|
||||
o.value('tproxy', _('Transparent (TPROXY)'));
|
||||
o.value('socks', _('Local SOCKS'));
|
||||
o.value('http', _('Local HTTP'));
|
||||
o.value('dokodemo', _('Dokodemo (wrap)'));
|
||||
o.default = 'tproxy';
|
||||
|
||||
// tproxy fields
|
||||
o = s.option(form.Value, 'network', _('LAN network(s)'),
|
||||
_('UCI interface name(s) of the LAN(s) to intercept.'));
|
||||
o.placeholder = 'lan';
|
||||
o.depends('type', 'tproxy');
|
||||
o.depends('type', '');
|
||||
|
||||
o = s.option(form.Value, 'tproxy_port', _('TPROXY port'));
|
||||
o.datatype = 'port';
|
||||
o.default = '12345';
|
||||
o.depends('type', 'tproxy');
|
||||
o.depends('type', '');
|
||||
|
||||
// local socks/http/dokodemo listener
|
||||
o = s.option(form.Value, 'listen', _('Listen address'),
|
||||
_('127.0.0.1 = router/apps only (recommended). A LAN IP / 0.0.0.0 exposes it to LAN.'));
|
||||
o.default = '127.0.0.1';
|
||||
o.depends('type', 'socks');
|
||||
o.depends('type', 'http');
|
||||
o.depends('type', 'dokodemo');
|
||||
|
||||
o = s.option(form.Value, 'port', _('Listen port'));
|
||||
o.datatype = 'port';
|
||||
o.depends('type', 'socks');
|
||||
o.depends('type', 'http');
|
||||
o.depends('type', 'dokodemo');
|
||||
|
||||
// socks/http auth
|
||||
o = s.option(form.ListValue, 'auth', _('Auth'));
|
||||
o.value('noauth', _('None'));
|
||||
o.value('password', _('Username/password'));
|
||||
o.default = 'noauth';
|
||||
o.depends('type', 'socks');
|
||||
o.depends('type', 'http');
|
||||
|
||||
o = s.option(form.Value, 'user', _('Username'));
|
||||
o.depends('auth', 'password');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'pass', _('Password'));
|
||||
o.depends('auth', 'password');
|
||||
o.password = true;
|
||||
o.modalonly = true;
|
||||
|
||||
// dokodemo target
|
||||
o = s.option(form.Value, 'target_addr', _('Wrap target address'));
|
||||
o.depends('type', 'dokodemo');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'target_port', _('Wrap target port'));
|
||||
o.datatype = 'port';
|
||||
o.depends('type', 'dokodemo');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.ListValue, 'target_network', _('Wrap network'));
|
||||
o.value('udp');
|
||||
o.value('tcp');
|
||||
o.value('tcp,udp');
|
||||
o.default = 'udp';
|
||||
o.depends('type', 'dokodemo');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Flag, 'tcp', _('TCP'));
|
||||
o.default = '1';
|
||||
|
||||
@@ -14,7 +14,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=shater-core
|
||||
PKG_VERSION:=0.1.0
|
||||
PKG_RELEASE:=10
|
||||
PKG_RELEASE:=11
|
||||
|
||||
PKG_MAINTAINER:=Shater <maqrota@icloud.com>
|
||||
PKG_LICENSE:=GPL-2.0-or-later
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=xrayctl
|
||||
PKG_VERSION:=0.1.0
|
||||
PKG_RELEASE:=10
|
||||
PKG_RELEASE:=11
|
||||
|
||||
PKG_MAINTAINER:=shater
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
+3
-1
@@ -433,8 +433,10 @@ func RenderNft(m *Model) string {
|
||||
}
|
||||
|
||||
// 2) Per-inbound catch-all divert (all remaining LAN-ingress traffic).
|
||||
// Only tproxy inbounds divert; local socks/http/dokodemo listeners must not
|
||||
// inject a bogus LAN tproxy rule pointing at their listener port.
|
||||
for _, in := range m.Inbounds {
|
||||
if !in.Enabled {
|
||||
if !isTproxyInbound(in) {
|
||||
continue
|
||||
}
|
||||
dev := ifaceDevice(in.Network)
|
||||
|
||||
+104
-29
@@ -258,39 +258,114 @@ func (b *builder) buildInbounds() {
|
||||
if !in.Enabled {
|
||||
continue
|
||||
}
|
||||
nets := []string{}
|
||||
if in.TCP {
|
||||
nets = append(nets, "tcp")
|
||||
switch in.inboundType() {
|
||||
case "socks", "http":
|
||||
b.emitLocalProxyInbound(in)
|
||||
case "dokodemo":
|
||||
b.emitDokodemoInbound(in)
|
||||
default: // tproxy (and legacy no-Type)
|
||||
b.emitTproxyInbound(in)
|
||||
}
|
||||
if in.UDP {
|
||||
nets = append(nets, "udp")
|
||||
}
|
||||
if len(nets) == 0 {
|
||||
nets = []string{"tcp", "udp"}
|
||||
}
|
||||
ib := map[string]any{
|
||||
"tag": "tproxy-" + orDefault(in.Name, "lan"),
|
||||
"protocol": "dokodemo-door",
|
||||
"port": in.TproxyPort,
|
||||
"settings": map[string]any{
|
||||
"network": strings.Join(nets, ","),
|
||||
"followRedirect": true,
|
||||
},
|
||||
"streamSettings": map[string]any{
|
||||
"sockopt": map[string]any{"tproxy": "tproxy", "mark": loopMark},
|
||||
},
|
||||
}
|
||||
if in.Sniff {
|
||||
ib["sniffing"] = map[string]any{
|
||||
"enabled": true,
|
||||
"destOverride": []any{"http", "tls", "quic"},
|
||||
"routeOnly": false,
|
||||
}
|
||||
}
|
||||
b.inbounds = append(b.inbounds, ib)
|
||||
}
|
||||
}
|
||||
|
||||
// inNets joins an inbound's enabled networks, defaulting to tcp,udp.
|
||||
func inNets(in Inbound) string {
|
||||
nets := []string{}
|
||||
if in.TCP {
|
||||
nets = append(nets, "tcp")
|
||||
}
|
||||
if in.UDP {
|
||||
nets = append(nets, "udp")
|
||||
}
|
||||
if len(nets) == 0 {
|
||||
nets = []string{"tcp", "udp"}
|
||||
}
|
||||
return strings.Join(nets, ",")
|
||||
}
|
||||
|
||||
func sniffBlock() map[string]any {
|
||||
return map[string]any{
|
||||
"enabled": true,
|
||||
"destOverride": []any{"http", "tls", "quic"},
|
||||
"routeOnly": false,
|
||||
}
|
||||
}
|
||||
|
||||
// emitTproxyInbound is the transparent TPROXY dokodemo-door inbound (the default).
|
||||
func (b *builder) emitTproxyInbound(in Inbound) {
|
||||
ib := map[string]any{
|
||||
"tag": "tproxy-" + orDefault(in.Name, "lan"),
|
||||
"protocol": "dokodemo-door",
|
||||
"port": in.TproxyPort,
|
||||
"settings": map[string]any{
|
||||
"network": inNets(in),
|
||||
"followRedirect": true,
|
||||
},
|
||||
"streamSettings": map[string]any{
|
||||
"sockopt": map[string]any{"tproxy": "tproxy", "mark": loopMark},
|
||||
},
|
||||
}
|
||||
if in.Sniff {
|
||||
ib["sniffing"] = sniffBlock()
|
||||
}
|
||||
b.inbounds = append(b.inbounds, ib)
|
||||
}
|
||||
|
||||
// emitLocalProxyInbound emits a local socks/http listener (router/apps). No
|
||||
// sockopt/tproxy — it is not a diverted inbound; it is covered by the same
|
||||
// routing rules (no dedicated inboundTag) so its traffic follows user policy.
|
||||
func (b *builder) emitLocalProxyInbound(in Inbound) {
|
||||
proto := in.inboundType() // socks | http
|
||||
settings := map[string]any{}
|
||||
if proto == "socks" {
|
||||
settings["udp"] = in.UDP
|
||||
}
|
||||
if strings.EqualFold(in.Auth, "password") && in.User != "" {
|
||||
settings["auth"] = "password"
|
||||
settings["accounts"] = []any{map[string]any{"user": in.User, "pass": in.Pass}}
|
||||
} else if proto == "socks" {
|
||||
settings["auth"] = "noauth"
|
||||
}
|
||||
ib := map[string]any{
|
||||
"tag": proto + "-in-" + orDefault(in.Name, proto),
|
||||
"protocol": proto,
|
||||
"listen": orDefault(in.Listen, "127.0.0.1"),
|
||||
"port": in.Port,
|
||||
"settings": settings,
|
||||
}
|
||||
if in.Sniff {
|
||||
ib["sniffing"] = sniffBlock()
|
||||
}
|
||||
b.inbounds = append(b.inbounds, ib)
|
||||
}
|
||||
|
||||
// emitDokodemoInbound emits a plain (non-tproxy) dokodemo-door listener for
|
||||
// wrapping specific traffic (e.g. awg-wrap) to a fixed destination.
|
||||
func (b *builder) emitDokodemoInbound(in Inbound) {
|
||||
settings := map[string]any{
|
||||
"network": orDefault(in.TargetNetwork, inNets(in)),
|
||||
"followRedirect": false,
|
||||
}
|
||||
if in.TargetAddr != "" {
|
||||
settings["address"] = in.TargetAddr
|
||||
}
|
||||
if in.TargetPort != 0 {
|
||||
settings["port"] = in.TargetPort
|
||||
}
|
||||
ib := map[string]any{
|
||||
"tag": "dokodemo-" + orDefault(in.Name, "wrap"),
|
||||
"protocol": "dokodemo-door",
|
||||
"listen": orDefault(in.Listen, "127.0.0.1"),
|
||||
"port": in.Port,
|
||||
"settings": settings,
|
||||
}
|
||||
if in.Sniff {
|
||||
ib["sniffing"] = sniffBlock()
|
||||
}
|
||||
b.inbounds = append(b.inbounds, ib)
|
||||
}
|
||||
|
||||
// --- groups (standalone balancers referenced by rules/explain) ---
|
||||
|
||||
func (b *builder) buildGroups() error {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func inboundByTag(cfg map[string]any, sub string) map[string]any {
|
||||
for _, o := range cfg["inbounds"].([]any) {
|
||||
ib := o.(map[string]any)
|
||||
if tag, _ := ib["tag"].(string); strings.Contains(tag, sub) {
|
||||
return ib
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildWithInbounds(t *testing.T, ins ...Inbound) map[string]any {
|
||||
t.Helper()
|
||||
m := &Model{Globals: defaultGlobals()}
|
||||
m.Inbounds = ins
|
||||
m.Nodes = []Node{{Name: "n1", Enabled: true, URI: vlessReality}}
|
||||
m.Rules = []Rule{{Name: "r", Enabled: true, Order: 10, Target: "node:n1"}}
|
||||
cfg, err := BuildConfig(m)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildConfig: %v", err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func TestSocksInboundEmitted(t *testing.T) {
|
||||
cfg := buildWithInbounds(t, Inbound{Type: "socks", Name: "local", Enabled: true, Listen: "127.0.0.1", Port: 1080, UDP: true, Sniff: true, Auth: "noauth"})
|
||||
ib := inboundByTag(cfg, "socks-in-local")
|
||||
if ib == nil || ib["protocol"] != "socks" {
|
||||
t.Fatalf("socks inbound missing: %v", ib)
|
||||
}
|
||||
if ib["listen"] != "127.0.0.1" || ib["port"] != 1080 {
|
||||
t.Fatalf("listen/port wrong: %v", ib)
|
||||
}
|
||||
st := ib["settings"].(map[string]any)
|
||||
if st["udp"] != true || st["auth"] != "noauth" {
|
||||
t.Fatalf("settings wrong: %v", st)
|
||||
}
|
||||
if _, ok := ib["streamSettings"]; ok {
|
||||
t.Fatalf("local socks must have no streamSettings/sockopt: %v", ib["streamSettings"])
|
||||
}
|
||||
if ib["sniffing"] == nil {
|
||||
t.Fatalf("sniffing expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSocksInboundPasswordAuth(t *testing.T) {
|
||||
cfg := buildWithInbounds(t, Inbound{Type: "socks", Name: "a", Enabled: true, Port: 1080, Auth: "password", User: "u", Pass: "p"})
|
||||
st := inboundByTag(cfg, "socks-in-a")["settings"].(map[string]any)
|
||||
if st["auth"] != "password" {
|
||||
t.Fatalf("auth = %v", st["auth"])
|
||||
}
|
||||
acc := st["accounts"].([]any)[0].(map[string]any)
|
||||
if acc["user"] != "u" || acc["pass"] != "p" {
|
||||
t.Fatalf("accounts = %v", acc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpInboundEmitted(t *testing.T) {
|
||||
cfg := buildWithInbounds(t, Inbound{Type: "http", Name: "h", Enabled: true, Port: 8123})
|
||||
ib := inboundByTag(cfg, "http-in-h")
|
||||
if ib == nil || ib["protocol"] != "http" || ib["port"] != 8123 {
|
||||
t.Fatalf("http inbound wrong: %v", ib)
|
||||
}
|
||||
if _, ok := ib["streamSettings"]; ok {
|
||||
t.Fatalf("http inbound must have no sockopt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDokodemoWrapInbound(t *testing.T) {
|
||||
cfg := buildWithInbounds(t, Inbound{Type: "dokodemo", Name: "wrap", Enabled: true, Port: 5353, TargetAddr: "10.13.13.1", TargetPort: 51820, TargetNetwork: "udp", Sniff: false})
|
||||
ib := inboundByTag(cfg, "dokodemo-wrap")
|
||||
if ib == nil || ib["protocol"] != "dokodemo-door" {
|
||||
t.Fatalf("dokodemo inbound wrong: %v", ib)
|
||||
}
|
||||
st := ib["settings"].(map[string]any)
|
||||
if st["address"] != "10.13.13.1" || st["port"] != 51820 || st["network"] != "udp" || st["followRedirect"] != false {
|
||||
t.Fatalf("dokodemo settings wrong: %v", st)
|
||||
}
|
||||
if ib["sniffing"] != nil {
|
||||
t.Fatalf("sniffing should be off for dokodemo-wrap")
|
||||
}
|
||||
if _, ok := ib["streamSettings"]; ok {
|
||||
t.Fatalf("dokodemo must have no sockopt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyTproxyInboundStillEmitted(t *testing.T) {
|
||||
// Type empty => tproxy; must keep the sockopt tproxy/mark.
|
||||
cfg := buildWithInbounds(t, Inbound{Name: "lan", Enabled: true, TproxyPort: 12345, TCP: true, UDP: true})
|
||||
ib := inboundByTag(cfg, "tproxy-lan")
|
||||
if ib == nil || ib["protocol"] != "dokodemo-door" {
|
||||
t.Fatalf("legacy tproxy inbound missing: %v", ib)
|
||||
}
|
||||
so := ib["streamSettings"].(map[string]any)["sockopt"].(map[string]any)
|
||||
if so["tproxy"] != "tproxy" || so["mark"] != loopMark {
|
||||
t.Fatalf("tproxy sockopt wrong: %v", so)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNftIgnoresNonTproxyInbounds(t *testing.T) {
|
||||
m := &Model{Globals: defaultGlobals()}
|
||||
m.Inbounds = []Inbound{
|
||||
{Name: "lan", Enabled: true, Network: "lan", TproxyPort: 12345, TCP: true, UDP: true},
|
||||
{Type: "socks", Name: "local", Enabled: true, Port: 1080, UDP: true},
|
||||
}
|
||||
nft := RenderNft(m)
|
||||
if !strings.Contains(nft, "12345") {
|
||||
t.Fatalf("tproxy port 12345 must appear in nft")
|
||||
}
|
||||
if strings.Contains(nft, "1080") {
|
||||
t.Fatalf("socks listener port 1080 must NOT appear as a tproxy divert:\n%s", nft)
|
||||
}
|
||||
}
|
||||
+48
-6
@@ -51,15 +51,57 @@ func defaultGlobals() Globals {
|
||||
}
|
||||
}
|
||||
|
||||
// Inbound is a `config inbound` (multi-LAN tproxy entry).
|
||||
// Inbound is a `config inbound`. Type selects the shape:
|
||||
//
|
||||
// tproxy (default) — transparent dokodemo-door + TPROXY sockopt (multi-LAN)
|
||||
// socks — local SOCKS5 listener (router/apps) [T1]
|
||||
// http — local HTTP proxy listener [T1]
|
||||
// dokodemo — plain redirect target for wrapping traffic (awg-wrap)[T2]
|
||||
//
|
||||
// Only tproxy inbounds are wired into the nft TPROXY plane (see isTproxyInbound);
|
||||
// socks/http/dokodemo are plain listeners with no sockopt/mark. Absent Type =>
|
||||
// tproxy, so existing configs are unchanged.
|
||||
type Inbound struct {
|
||||
Name string
|
||||
Enabled bool
|
||||
Name string
|
||||
Enabled bool
|
||||
Type string // tproxy|socks|http|dokodemo (default tproxy)
|
||||
|
||||
// tproxy
|
||||
Network string
|
||||
TproxyPort int
|
||||
TCP bool
|
||||
UDP bool
|
||||
Sniff bool
|
||||
|
||||
// socks/http/dokodemo local listener
|
||||
Listen string // bind addr; default 127.0.0.1
|
||||
Port int
|
||||
|
||||
// socks/http auth
|
||||
Auth string // noauth|password (default noauth)
|
||||
User string
|
||||
Pass string
|
||||
|
||||
// dokodemo target (fixed destination for wrapped traffic)
|
||||
TargetAddr string
|
||||
TargetPort int
|
||||
TargetNetwork string // tcp|udp|tcp,udp (default udp)
|
||||
|
||||
// shared
|
||||
TCP bool
|
||||
UDP bool
|
||||
Sniff bool
|
||||
}
|
||||
|
||||
// inboundType returns the effective type, defaulting empty -> tproxy.
|
||||
func (in Inbound) inboundType() string {
|
||||
if in.Type == "" {
|
||||
return "tproxy"
|
||||
}
|
||||
return strings.ToLower(in.Type)
|
||||
}
|
||||
|
||||
// isTproxyInbound reports whether an inbound participates in the TPROXY nft plane
|
||||
// (only tproxy inbounds divert LAN traffic; local socks/http/dokodemo do not).
|
||||
func isTproxyInbound(in Inbound) bool {
|
||||
return in.Enabled && in.inboundType() == "tproxy"
|
||||
}
|
||||
|
||||
// Subscription is a `config subscription`.
|
||||
|
||||
+3
-3
@@ -90,17 +90,17 @@ func nftIifExpr(devs []string) string {
|
||||
func nftEnabledInboundDevs(m *Model) []string {
|
||||
var out []string
|
||||
for _, in := range m.Inbounds {
|
||||
if in.Enabled {
|
||||
if isTproxyInbound(in) {
|
||||
out = append(out, ifaceDevice(in.Network))
|
||||
}
|
||||
}
|
||||
return nftDedupStr(out)
|
||||
}
|
||||
|
||||
// nftPrimaryInbound returns the first enabled inbound (port/mark source).
|
||||
// nftPrimaryInbound returns the first enabled tproxy inbound (port/mark source).
|
||||
func nftPrimaryInbound(m *Model) (Inbound, bool) {
|
||||
for _, in := range m.Inbounds {
|
||||
if in.Enabled {
|
||||
if isTproxyInbound(in) {
|
||||
return in, true
|
||||
}
|
||||
}
|
||||
|
||||
+18
-7
@@ -40,14 +40,25 @@ func ParseUCIExport(text string) (*Model, error) {
|
||||
case "globals":
|
||||
applyGlobals(&m.Globals, s)
|
||||
case "inbound":
|
||||
typ := s.optOr("type", "tproxy")
|
||||
m.Inbounds = append(m.Inbounds, Inbound{
|
||||
Name: s.opt("name"),
|
||||
Enabled: s.optBool("enabled", true),
|
||||
Network: s.opt("network"),
|
||||
TproxyPort: parseInt(s.opt("tproxy_port"), 12345),
|
||||
TCP: s.optBool("tcp", true),
|
||||
UDP: s.optBool("udp", true),
|
||||
Sniff: s.optBool("sniff", true),
|
||||
Name: s.opt("name"),
|
||||
Enabled: s.optBool("enabled", true),
|
||||
Type: typ,
|
||||
Network: s.opt("network"),
|
||||
TproxyPort: parseInt(s.opt("tproxy_port"), 12345),
|
||||
Listen: s.optOr("listen", "127.0.0.1"),
|
||||
Port: parseInt(s.opt("port"), 0),
|
||||
Auth: s.optOr("auth", "noauth"),
|
||||
User: s.opt("user"),
|
||||
Pass: s.opt("pass"),
|
||||
TargetAddr: s.opt("target_addr"),
|
||||
TargetPort: parseInt(s.opt("target_port"), 0),
|
||||
TargetNetwork: s.optOr("target_network", "udp"),
|
||||
TCP: s.optBool("tcp", true),
|
||||
UDP: s.optBool("udp", true),
|
||||
// sniff defaults on for tproxy/socks/http, off for dokodemo-wrap.
|
||||
Sniff: s.optBool("sniff", typ != "dokodemo"),
|
||||
})
|
||||
case "subscription":
|
||||
m.Subscriptions = append(m.Subscriptions, Subscription{
|
||||
|
||||
Reference in New Issue
Block a user