| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- package service
- import (
- "strings"
- "testing"
- "github.com/mhsanaei/3x-ui/v3/internal/database"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- )
- // checkForwardedPortsConflict only ran from the AmneziaWG save path, so an
- // ordinary inbound could take a port a peer forwards on every interface.
- func TestAddInboundRefusesAPortAnAmneziaWGPeerForwards(t *testing.T) {
- const forwarded = 8443
- cases := []struct {
- name string
- port int
- wantErr bool
- }{
- {"the forwarded port", forwarded, true},
- {"a free port", forwarded + 1, false},
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- setupConflictDB(t)
- seedInboundConflict(t, "awg-forward", "0.0.0.0", 51820, model.AmneziaWG, ``,
- awgRelayWindowSettingsWithForward(t, "awg-forward", "8443"))
- _, _, err := (&InboundService{}).AddInbound(&model.Inbound{
- Tag: "user-inbound", Enable: true, Listen: "0.0.0.0", Port: tc.port,
- Protocol: model.VLESS, StreamSettings: `{"network":"tcp"}`, Settings: `{"clients":[]}`,
- })
- if !tc.wantErr {
- if err != nil {
- t.Fatalf("port %d is free; the create must be allowed: %v", tc.port, err)
- }
- return
- }
- if err == nil {
- t.Fatalf("port %d is forwarded by a peer of another inbound; the create must be refused", tc.port)
- }
- if !strings.Contains(err.Error(), "awg-forward@relay-window") {
- t.Fatalf("the refusal must name the peer holding the port, got %v", err)
- }
- })
- }
- }
- // A peer the forward supervisor opens no listener for holds no port: it has no
- // email, or no address the tunnel can route to, and Reconcile skips it either way.
- func TestAddInboundAllowsAPortNoPeerCanActuallyForward(t *testing.T) {
- cases := []struct {
- name string
- settings func(t *testing.T) string
- }{
- {
- name: "a peer with no email",
- settings: func(t *testing.T) string {
- t.Helper()
- return replaceFirst(t, awgRelayWindowSettingsWithForward(t, "awg-forward", "8443"),
- `"email":"awg-forward@relay-window"`, `"email":""`)
- },
- },
- {
- name: "an IPv6-only peer on a row without IPv6",
- settings: func(t *testing.T) string {
- t.Helper()
- return replaceFirst(t, awgRelayWindowSettingsWithForward(t, "awg-forward", "8443"),
- `"allowedIPs":["10.8.1.2/32"]`, `"allowedIPs":["fd00::2/128"]`)
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- setupConflictDB(t)
- seedInboundConflict(t, "awg-forward", "0.0.0.0", 51820, model.AmneziaWG, ``, tc.settings(t))
- if _, _, err := (&InboundService{}).AddInbound(&model.Inbound{
- Tag: "user-inbound", Enable: true, Listen: "0.0.0.0", Port: 8443,
- Protocol: model.VLESS, StreamSettings: `{"network":"tcp"}`, Settings: `{"clients":[]}`,
- }); err != nil {
- t.Fatalf("nothing binds 8443 for this peer; the create must be allowed: %v", err)
- }
- })
- }
- }
- // The refusal has to point at where the socket really is: the forward listens on
- // every interface, so repeating the candidate's requested address asserts a lie.
- func TestForwardedPortRefusalNamesTheWildcardBind(t *testing.T) {
- setupConflictDB(t)
- seedInboundConflict(t, "awg-forward", "0.0.0.0", 51820, model.AmneziaWG, ``,
- awgRelayWindowSettingsWithForward(t, "awg-forward", "8443"))
- _, _, err := (&InboundService{}).AddInbound(&model.Inbound{
- Tag: "user-inbound", Enable: true, Listen: "10.0.0.5", Port: 8443,
- Protocol: model.VLESS, StreamSettings: `{"network":"tcp"}`, Settings: `{"clients":[]}`,
- })
- if err == nil {
- t.Fatal("the port is forwarded on every interface, including 10.0.0.5; the create must be refused")
- }
- if !strings.Contains(err.Error(), " on * by its client ") {
- t.Fatalf("the refusal must place the forward on every interface, got %v", err)
- }
- }
- func replaceFirst(t *testing.T, s, old, new string) string {
- t.Helper()
- if !strings.Contains(s, old) {
- t.Fatalf("fixture no longer contains %s", old)
- }
- return strings.Replace(s, old, new, 1)
- }
- // The forward listener runs where the AmneziaWG row runs, so a node row sharing
- // a local peer's port stays legal -- the scoping every other guard here uses.
- func TestAddInboundAllowsANodeRowOnALocallyForwardedPort(t *testing.T) {
- setupConflictDB(t)
- seedInboundConflict(t, "awg-forward", "0.0.0.0", 51820, model.AmneziaWG, ``,
- awgRelayWindowSettingsWithForward(t, "awg-forward", "8443"))
- node := &model.Node{Name: "n1", Address: "127.0.0.1", Port: 2096, Scheme: "https", Enable: true, Status: "online"}
- if err := database.GetDB().Create(node).Error; err != nil {
- t.Fatalf("seed node: %v", err)
- }
- if _, _, err := (&InboundService{}).AddInbound(&model.Inbound{
- Tag: "node-inbound", Enable: true, Listen: "0.0.0.0", Port: 8443,
- Protocol: model.VLESS, StreamSettings: `{"network":"tcp"}`, Settings: `{"clients":[]}`,
- NodeID: &node.Id,
- }); err != nil {
- t.Fatalf("a node row does not bind here; the create must be allowed: %v", err)
- }
- }
|