|
|
@@ -2,6 +2,7 @@ package service
|
|
|
|
|
|
import (
|
|
|
"path/filepath"
|
|
|
+ "strings"
|
|
|
"sync"
|
|
|
"testing"
|
|
|
|
|
|
@@ -137,7 +138,7 @@ func TestCheckPortConflict_TCPandUDPCoexistOnSamePort(t *testing.T) {
|
|
|
if err != nil {
|
|
|
t.Fatalf("checkPortConflict: %v", err)
|
|
|
}
|
|
|
- if exist {
|
|
|
+ if exist != nil {
|
|
|
t.Fatalf("vless/tcp and hysteria2/udp on the same port must be allowed to coexist")
|
|
|
}
|
|
|
}
|
|
|
@@ -159,7 +160,7 @@ func TestCheckPortConflict_TCPCollidesWithTCP(t *testing.T) {
|
|
|
if err != nil {
|
|
|
t.Fatalf("checkPortConflict: %v", err)
|
|
|
}
|
|
|
- if !exist {
|
|
|
+ if exist == nil {
|
|
|
t.Fatalf("two tcp inbounds on the same port must still conflict")
|
|
|
}
|
|
|
}
|
|
|
@@ -181,7 +182,7 @@ func TestCheckPortConflict_UDPCollidesWithUDP(t *testing.T) {
|
|
|
if err != nil {
|
|
|
t.Fatalf("checkPortConflict: %v", err)
|
|
|
}
|
|
|
- if !exist {
|
|
|
+ if exist == nil {
|
|
|
t.Fatalf("two udp inbounds on the same port must conflict")
|
|
|
}
|
|
|
}
|
|
|
@@ -201,7 +202,7 @@ func TestCheckPortConflict_ShadowsocksDualListenBlocksBoth(t *testing.T) {
|
|
|
Protocol: model.VLESS,
|
|
|
StreamSettings: `{"network":"tcp"}`,
|
|
|
}
|
|
|
- if exist, err := svc.checkPortConflict(tcpClash, 0); err != nil || !exist {
|
|
|
+ if exist, err := svc.checkPortConflict(tcpClash, 0); err != nil || exist == nil {
|
|
|
t.Fatalf("tcp inbound should clash with shadowsocks tcp,udp; exist=%v err=%v", exist, err)
|
|
|
}
|
|
|
|
|
|
@@ -211,7 +212,7 @@ func TestCheckPortConflict_ShadowsocksDualListenBlocksBoth(t *testing.T) {
|
|
|
Port: 443,
|
|
|
Protocol: model.Hysteria,
|
|
|
}
|
|
|
- if exist, err := svc.checkPortConflict(udpClash, 0); err != nil || !exist {
|
|
|
+ if exist, err := svc.checkPortConflict(udpClash, 0); err != nil || exist == nil {
|
|
|
t.Fatalf("udp inbound should clash with shadowsocks tcp,udp; exist=%v err=%v", exist, err)
|
|
|
}
|
|
|
}
|
|
|
@@ -229,7 +230,7 @@ func TestCheckPortConflict_DifferentPortNeverConflicts(t *testing.T) {
|
|
|
Protocol: model.VLESS,
|
|
|
StreamSettings: `{"network":"tcp"}`,
|
|
|
}
|
|
|
- if exist, err := svc.checkPortConflict(other, 0); err != nil || exist {
|
|
|
+ if exist, err := svc.checkPortConflict(other, 0); err != nil || exist != nil {
|
|
|
t.Fatalf("different port must not conflict; exist=%v err=%v", exist, err)
|
|
|
}
|
|
|
}
|
|
|
@@ -251,7 +252,7 @@ func TestCheckPortConflict_ListenOverlapPreserved(t *testing.T) {
|
|
|
Protocol: model.VLESS,
|
|
|
StreamSettings: `{"network":"tcp"}`,
|
|
|
}
|
|
|
- if exist, err := svc.checkPortConflict(other, 0); err != nil || exist {
|
|
|
+ if exist, err := svc.checkPortConflict(other, 0); err != nil || exist != nil {
|
|
|
t.Fatalf("different specific listen must not conflict; exist=%v err=%v", exist, err)
|
|
|
}
|
|
|
|
|
|
@@ -263,7 +264,7 @@ func TestCheckPortConflict_ListenOverlapPreserved(t *testing.T) {
|
|
|
Protocol: model.VLESS,
|
|
|
StreamSettings: `{"network":"tcp"}`,
|
|
|
}
|
|
|
- if exist, err := svc.checkPortConflict(anyAddr, 0); err != nil || !exist {
|
|
|
+ if exist, err := svc.checkPortConflict(anyAddr, 0); err != nil || exist == nil {
|
|
|
t.Fatalf("any-addr on same port+transport must conflict with specific; exist=%v err=%v", exist, err)
|
|
|
}
|
|
|
}
|
|
|
@@ -386,8 +387,8 @@ func TestCheckPortConflict_NodeScope(t *testing.T) {
|
|
|
if err != nil {
|
|
|
t.Fatalf("checkPortConflict: %v", err)
|
|
|
}
|
|
|
- if got != c.want {
|
|
|
- t.Fatalf("got conflict=%v, want %v", got, c.want)
|
|
|
+ if (got != nil) != c.want {
|
|
|
+ t.Fatalf("got conflict=%v, want %v", got != nil, c.want)
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
@@ -482,7 +483,113 @@ func TestCheckPortConflict_IgnoreSelfOnUpdate(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
svc := &InboundService{}
|
|
|
- if exist, err := svc.checkPortConflict(&existing, existing.Id); err != nil || exist {
|
|
|
+ if exist, err := svc.checkPortConflict(&existing, existing.Id); err != nil || exist != nil {
|
|
|
t.Fatalf("self-update must not be flagged as conflict; exist=%v err=%v", exist, err)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// streamSettings.network=quic rides on UDP at L4, so a QUIC inbound must
|
|
|
+// conflict with a UDP-only neighbour (hysteria) on the same port but not
|
|
|
+// with a TCP-only one. covers the gap left by the original kcp-only check.
|
|
|
+func TestCheckPortConflict_QUICTreatedAsUDP(t *testing.T) {
|
|
|
+ quic := &model.Inbound{
|
|
|
+ Tag: "vless-quic-443",
|
|
|
+ Listen: "0.0.0.0",
|
|
|
+ Port: 443,
|
|
|
+ Protocol: model.VLESS,
|
|
|
+ StreamSettings: `{"network":"quic"}`,
|
|
|
+ }
|
|
|
+
|
|
|
+ t.Run("conflicts with hysteria/udp", func(t *testing.T) {
|
|
|
+ setupConflictDB(t)
|
|
|
+ seedInboundConflict(t, "hyst-443", "0.0.0.0", 443, model.Hysteria, ``, ``)
|
|
|
+ svc := &InboundService{}
|
|
|
+ if exist, err := svc.checkPortConflict(quic, 0); err != nil || exist == nil {
|
|
|
+ t.Fatalf("quic on same port as hysteria must conflict; exist=%v err=%v", exist, err)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ t.Run("coexists with vless/tcp", func(t *testing.T) {
|
|
|
+ setupConflictDB(t)
|
|
|
+ seedInboundConflict(t, "vless-tcp-443", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
|
|
|
+ svc := &InboundService{}
|
|
|
+ if exist, err := svc.checkPortConflict(quic, 0); err != nil || exist != nil {
|
|
|
+ t.Fatalf("quic and tcp on same port must coexist; exist=%v err=%v", exist, err)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// tunnel (dokodemo-door) carries its L4 transport list in
|
|
|
+// settings.allowedNetwork, not settings.network. verify the predicate
|
|
|
+// picks the right field for each protocol.
|
|
|
+func TestCheckPortConflict_TunnelAllowedNetwork(t *testing.T) {
|
|
|
+ setupConflictDB(t)
|
|
|
+ seedInboundConflict(t, "tunnel-udp-443", "0.0.0.0", 443, model.Tunnel, ``, `{"allowedNetwork":"udp"}`)
|
|
|
+
|
|
|
+ svc := &InboundService{}
|
|
|
+
|
|
|
+ // tcp inbound on same port should coexist with udp-only tunnel.
|
|
|
+ tcpNeighbour := &model.Inbound{
|
|
|
+ Tag: "vless-443",
|
|
|
+ Listen: "0.0.0.0",
|
|
|
+ Port: 443,
|
|
|
+ Protocol: model.VLESS,
|
|
|
+ StreamSettings: `{"network":"tcp"}`,
|
|
|
+ }
|
|
|
+ if exist, err := svc.checkPortConflict(tcpNeighbour, 0); err != nil || exist != nil {
|
|
|
+ t.Fatalf("tunnel/udp and vless/tcp on same port must coexist; exist=%v err=%v", exist, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // udp neighbour (hysteria) on same port must conflict.
|
|
|
+ udpNeighbour := &model.Inbound{
|
|
|
+ Tag: "hyst-443",
|
|
|
+ Listen: "0.0.0.0",
|
|
|
+ Port: 443,
|
|
|
+ Protocol: model.Hysteria,
|
|
|
+ }
|
|
|
+ if exist, err := svc.checkPortConflict(udpNeighbour, 0); err != nil || exist == nil {
|
|
|
+ t.Fatalf("tunnel/udp and hysteria on same port must conflict; exist=%v err=%v", exist, err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// the rich conflict detail surfaced to the user must name the offending
|
|
|
+// inbound (by remark when available) and the shared L4 transport(s).
|
|
|
+func TestCheckPortConflict_DetailMessage(t *testing.T) {
|
|
|
+ setupConflictDB(t)
|
|
|
+ seeded := &model.Inbound{
|
|
|
+ Tag: "vless-443",
|
|
|
+ Remark: "my-vless",
|
|
|
+ Enable: true,
|
|
|
+ Listen: "0.0.0.0",
|
|
|
+ Port: 443,
|
|
|
+ Protocol: model.VLESS,
|
|
|
+ StreamSettings: `{"network":"tcp"}`,
|
|
|
+ Settings: `{}`,
|
|
|
+ }
|
|
|
+ if err := database.GetDB().Create(seeded).Error; err != nil {
|
|
|
+ t.Fatalf("seed inbound: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ svc := &InboundService{}
|
|
|
+ candidate := &model.Inbound{
|
|
|
+ Tag: "trojan-443",
|
|
|
+ Listen: "0.0.0.0",
|
|
|
+ Port: 443,
|
|
|
+ Protocol: model.Trojan,
|
|
|
+ StreamSettings: `{"network":"ws"}`,
|
|
|
+ }
|
|
|
+ got, err := svc.checkPortConflict(candidate, 0)
|
|
|
+ if err != nil || got == nil {
|
|
|
+ t.Fatalf("expected conflict, got=%v err=%v", got, err)
|
|
|
+ }
|
|
|
+ msg := got.String()
|
|
|
+ if !strings.Contains(msg, "my-vless") {
|
|
|
+ t.Fatalf("message should mention the conflicting inbound's remark; got %q", msg)
|
|
|
+ }
|
|
|
+ if !strings.Contains(msg, "tcp") {
|
|
|
+ t.Fatalf("message should mention the shared L4 transport; got %q", msg)
|
|
|
+ }
|
|
|
+ if !strings.Contains(msg, "443") {
|
|
|
+ t.Fatalf("message should mention the port; got %q", msg)
|
|
|
+ }
|
|
|
+}
|