mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
fix: bracket IPv6 forward entry addresses (#383)
Return standard [host]:port values from forward list responses so IPv6 rule entries can be copied directly from the UI.
This commit is contained in:
@@ -5,9 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -3241,7 +3243,7 @@ func resolveForwardIngress(db *gorm.DB, forwardID int64, tunnelID int64) (string
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ip != "" {
|
if ip != "" {
|
||||||
pair := fmt.Sprintf("%s:%d", ip, row.Port.Int64)
|
pair := formatForwardIngressAddress(ip, row.Port.Int64)
|
||||||
if _, ok := seenPairs[pair]; !ok {
|
if _, ok := seenPairs[pair]; !ok {
|
||||||
seenPairs[pair] = struct{}{}
|
seenPairs[pair] = struct{}{}
|
||||||
entries = append(entries, pair)
|
entries = append(entries, pair)
|
||||||
@@ -3258,6 +3260,17 @@ func resolveForwardIngress(db *gorm.DB, forwardID int64, tunnelID int64) (string
|
|||||||
return strings.Join(entries, ","), inPort, nil
|
return strings.Join(entries, ","), inPort, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatForwardIngressAddress(host string, port int64) string {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if host == "" || port <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||||
|
host = strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(host, strconv.FormatInt(port, 10))
|
||||||
|
}
|
||||||
|
|
||||||
func nullableString(v sql.NullString) interface{} {
|
func nullableString(v sql.NullString) interface{} {
|
||||||
if v.Valid {
|
if v.Valid {
|
||||||
return v.String
|
return v.String
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package contract_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIssue349_ForwardListFormatsIPv6EntryAddressesContract(t *testing.T) {
|
||||||
|
secret := "contract-jwt-secret"
|
||||||
|
router, repo := setupContractRouter(t, secret)
|
||||||
|
adminToken := mustAdminToken(t, secret)
|
||||||
|
now := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
if err := repo.DB().Exec(`
|
||||||
|
INSERT INTO tunnel(name, traffic_ratio, type, protocol, flow, created_time, updated_time, status, in_ip, inx)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, "issue349-tunnel", 1.0, 1, "tcp", 99999, now, now, 1, nil, 0).Error; err != nil {
|
||||||
|
t.Fatalf("insert tunnel: %v", err)
|
||||||
|
}
|
||||||
|
tunnelID := mustLastInsertID(t, repo, "issue349-tunnel")
|
||||||
|
|
||||||
|
if err := repo.DB().Exec(`
|
||||||
|
INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, "issue349-entry-node-a", "entry-secret-a", "2001:db8::10", "", "2001:db8::10", "32000-32010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
|
||||||
|
t.Fatalf("insert node a: %v", err)
|
||||||
|
}
|
||||||
|
nodeAID := mustLastInsertID(t, repo, "issue349-entry-node-a")
|
||||||
|
|
||||||
|
if err := repo.DB().Exec(`
|
||||||
|
INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, "issue349-entry-node-b", "entry-secret-b", "2001:db8::30", "", "2001:db8::30", "32000-32010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 1).Error; err != nil {
|
||||||
|
t.Fatalf("insert node b: %v", err)
|
||||||
|
}
|
||||||
|
nodeBID := mustLastInsertID(t, repo, "issue349-entry-node-b")
|
||||||
|
|
||||||
|
if err := repo.DB().Exec(`
|
||||||
|
INSERT INTO forward(user_id, user_name, name, tunnel_id, remote_addr, strategy, in_flow, out_flow, created_time, updated_time, status, inx)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, 0, 0, ?, ?, 1, ?)
|
||||||
|
`, 1, "admin_user", "issue349-forward", tunnelID, "1.1.1.1:443", "fifo", now, now, 0).Error; err != nil {
|
||||||
|
t.Fatalf("insert forward: %v", err)
|
||||||
|
}
|
||||||
|
forwardID := mustLastInsertID(t, repo, "issue349-forward")
|
||||||
|
|
||||||
|
if err := repo.DB().Exec(`INSERT INTO forward_port(forward_id, node_id, port) VALUES(?, ?, ?)`, forwardID, nodeAID, 32001).Error; err != nil {
|
||||||
|
t.Fatalf("insert forward_port a: %v", err)
|
||||||
|
}
|
||||||
|
if err := repo.DB().Exec(`INSERT INTO forward_port(forward_id, node_id, port, in_ip) VALUES(?, ?, ?, ?)`, forwardID, nodeBID, 32002, "2001:db8::20").Error; err != nil {
|
||||||
|
t.Fatalf("insert forward_port b: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := requestContractEnvelope(t, router, adminToken, "/api/v1/forward/list", nil)
|
||||||
|
if out.Code != 0 {
|
||||||
|
t.Fatalf("forward list failed: code=%d msg=%q", out.Code, out.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := mustContractSlice(t, out.Data, "forward list data")
|
||||||
|
var target map[string]interface{}
|
||||||
|
for _, row := range rows {
|
||||||
|
item, ok := row.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if contractValueAsInt64(item["id"]) == forwardID {
|
||||||
|
target = item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target == nil {
|
||||||
|
t.Fatalf("target forward %d not found in /forward/list response", forwardID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := contractValueAsString(target["inIp"]); got != "[2001:db8::10]:32001,[2001:db8::20]:32002" {
|
||||||
|
t.Fatalf("expected bracketed IPv6 entry list, got %q", got)
|
||||||
|
}
|
||||||
|
if got := contractValueAsInt64(target["inPort"]); got != 32001 {
|
||||||
|
t.Fatalf("expected first entry port 32001, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# 066 - Issue 349 IPv6 Entry Format
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
- 修复规则入口 IPv6 地址在列表/复制场景下缺少方括号的问题,确保 API 返回与前端展示都能直接使用 `[IPv6]:port` 格式。
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [x] 定位规则入口地址的生成链路,确认问题来自后端入口地址拼接格式。
|
||||||
|
- [x] 修复 IPv6 入口地址拼接逻辑,统一输出可直接复制的标准格式。
|
||||||
|
- [x] 增加回归验证,覆盖 `/api/v1/forward/list` 的 IPv6 入口地址格式。
|
||||||
|
- [x] 运行相关测试并确认通过。
|
||||||
|
- [ ] 提交修复分支、创建 PR,并合并到 `main`。
|
||||||
Reference in New Issue
Block a user