mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-12 12:16:37 +08:00
feat: add custom IP selection for nodes, tunnels, and forwards
- Add extra_ips field to nodes for multi-IP servers - Add connect_ip field to chain_tunnel for specifying connection address - Add in_ip field to forward_port for specifying listen address - Frontend: add UI controls for extra IPs on node form - Frontend: add connect IP input for tunnel chain nodes - Frontend: add listen IP input for forward form - Backend: resolve forward ingress with custom listen IP priority Entire-Checkpoint: 557563462c16
This commit is contained in:
@@ -48,10 +48,11 @@ type Forward struct {
|
||||
func (Forward) TableName() string { return "forward" }
|
||||
|
||||
type ForwardPort struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
||||
ForwardID int64 `gorm:"column:forward_id;not null"`
|
||||
NodeID int64 `gorm:"column:node_id;not null"`
|
||||
Port int `gorm:"not null"`
|
||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
||||
ForwardID int64 `gorm:"column:forward_id;not null"`
|
||||
NodeID int64 `gorm:"column:node_id;not null"`
|
||||
Port int `gorm:"not null"`
|
||||
InIP sql.NullString `gorm:"column:in_ip;type:text"`
|
||||
}
|
||||
|
||||
func (ForwardPort) TableName() string { return "forward_port" }
|
||||
@@ -63,6 +64,7 @@ type Node struct {
|
||||
ServerIP string `gorm:"column:server_ip;type:varchar(100);not null"`
|
||||
ServerIPV4 sql.NullString `gorm:"column:server_ip_v4;type:varchar(100)"`
|
||||
ServerIPV6 sql.NullString `gorm:"column:server_ip_v6;type:varchar(100)"`
|
||||
ExtraIPs sql.NullString `gorm:"column:extra_ips;type:text"`
|
||||
Port string `gorm:"type:text;not null"`
|
||||
InterfaceName sql.NullString `gorm:"column:interface_name;type:varchar(200)"`
|
||||
Version sql.NullString `gorm:"type:varchar(100)"`
|
||||
@@ -133,6 +135,7 @@ type ChainTunnel struct {
|
||||
Strategy sql.NullString `gorm:"type:varchar(10)"`
|
||||
Inx sql.NullInt64 `gorm:"column:inx"`
|
||||
Protocol sql.NullString `gorm:"type:varchar(10)"`
|
||||
ConnectIP sql.NullString `gorm:"column:connect_ip;type:varchar(45)"`
|
||||
}
|
||||
|
||||
func (ChainTunnel) TableName() string { return "chain_tunnel" }
|
||||
@@ -337,6 +340,7 @@ type NodeBackup struct {
|
||||
ServerIP string `json:"serverIp"`
|
||||
ServerIPv4 string `json:"serverIpV4,omitempty"`
|
||||
ServerIPv6 string `json:"serverIpV6,omitempty"`
|
||||
ExtraIPs string `json:"extraIPs,omitempty"`
|
||||
Port string `json:"port"`
|
||||
InterfaceName string `json:"interfaceName,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
@@ -510,6 +514,7 @@ type TunnelRecord struct {
|
||||
type ForwardPortRecord struct {
|
||||
NodeID int64
|
||||
Port int
|
||||
InIP string
|
||||
}
|
||||
|
||||
// NodeRecord is a node view used by control plane.
|
||||
@@ -519,6 +524,7 @@ type NodeRecord struct {
|
||||
ServerIP string
|
||||
ServerIPv4 string
|
||||
ServerIPv6 string
|
||||
ExtraIPs string
|
||||
Status int
|
||||
PortRange string
|
||||
TCPListenAddr string
|
||||
@@ -538,6 +544,7 @@ type ChainNodeRecord struct {
|
||||
NodeName string
|
||||
Protocol string
|
||||
Strategy string
|
||||
ConnectIP string
|
||||
}
|
||||
|
||||
type UserTunnelLimiterInfo struct {
|
||||
|
||||
@@ -637,6 +637,7 @@ func (r *Repository) ListNodes() ([]map[string]interface{}, error) {
|
||||
"ip": n.ServerIP, "serverIp": n.ServerIP,
|
||||
"serverIpV4": nullableString(n.ServerIPV4),
|
||||
"serverIpV6": nullableString(n.ServerIPV6),
|
||||
"extraIPs": nullableString(n.ExtraIPs),
|
||||
"port": n.Port,
|
||||
"tcpListenAddr": n.TCPListenAddr,
|
||||
"udpListenAddr": n.UDPListenAddr,
|
||||
@@ -2731,10 +2732,11 @@ func resolveForwardIngress(db *gorm.DB, forwardID int64, tunnelID int64) (string
|
||||
type fpRow struct {
|
||||
Port sql.NullInt64
|
||||
ServerIP sql.NullString
|
||||
InIP sql.NullString
|
||||
}
|
||||
var fpRows []fpRow
|
||||
err := db.Model(&model.ForwardPort{}).
|
||||
Select("forward_port.port, node.server_ip").
|
||||
Select("forward_port.port, node.server_ip, forward_port.in_ip").
|
||||
Joins("LEFT JOIN node ON node.id = forward_port.node_id").
|
||||
Where("forward_port.forward_id = ?", forwardID).
|
||||
Order("forward_port.id ASC").
|
||||
@@ -2744,10 +2746,22 @@ func resolveForwardIngress(db *gorm.DB, forwardID int64, tunnelID int64) (string
|
||||
}
|
||||
|
||||
ports := make([]int64, 0)
|
||||
nodePairs := make([]string, 0)
|
||||
entries := make([]string, 0)
|
||||
seenPorts := make(map[int64]struct{})
|
||||
seenPairs := make(map[string]struct{})
|
||||
|
||||
var tunnelFirstIP string
|
||||
if tunnelInIP.Valid && strings.TrimSpace(tunnelInIP.String) != "" {
|
||||
tunnelIPs := strings.Split(tunnelInIP.String, ",")
|
||||
for _, ip := range tunnelIPs {
|
||||
ip = strings.TrimSpace(ip)
|
||||
if ip != "" {
|
||||
tunnelFirstIP = ip
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, row := range fpRows {
|
||||
if !row.Port.Valid {
|
||||
continue
|
||||
@@ -2756,11 +2770,21 @@ func resolveForwardIngress(db *gorm.DB, forwardID int64, tunnelID int64) (string
|
||||
seenPorts[row.Port.Int64] = struct{}{}
|
||||
ports = append(ports, row.Port.Int64)
|
||||
}
|
||||
if row.ServerIP.Valid && strings.TrimSpace(row.ServerIP.String) != "" {
|
||||
pair := fmt.Sprintf("%s:%d", strings.TrimSpace(row.ServerIP.String), row.Port.Int64)
|
||||
|
||||
var ip string
|
||||
if row.InIP.Valid && strings.TrimSpace(row.InIP.String) != "" {
|
||||
ip = strings.TrimSpace(row.InIP.String)
|
||||
} else if tunnelFirstIP != "" {
|
||||
ip = tunnelFirstIP
|
||||
} else if row.ServerIP.Valid && strings.TrimSpace(row.ServerIP.String) != "" {
|
||||
ip = strings.TrimSpace(row.ServerIP.String)
|
||||
}
|
||||
|
||||
if ip != "" {
|
||||
pair := fmt.Sprintf("%s:%d", ip, row.Port.Int64)
|
||||
if _, ok := seenPairs[pair]; !ok {
|
||||
seenPairs[pair] = struct{}{}
|
||||
nodePairs = append(nodePairs, pair)
|
||||
entries = append(entries, pair)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2771,27 +2795,6 @@ func resolveForwardIngress(db *gorm.DB, forwardID int64, tunnelID int64) (string
|
||||
|
||||
inPort := sql.NullInt64{Int64: ports[0], Valid: true}
|
||||
|
||||
entries := make([]string, 0)
|
||||
if tunnelInIP.Valid && strings.TrimSpace(tunnelInIP.String) != "" {
|
||||
tunnelIPs := strings.Split(tunnelInIP.String, ",")
|
||||
seen := make(map[string]struct{})
|
||||
for _, ip := range tunnelIPs {
|
||||
ip = strings.TrimSpace(ip)
|
||||
if ip == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[ip]; ok {
|
||||
continue
|
||||
}
|
||||
seen[ip] = struct{}{}
|
||||
for _, port := range ports {
|
||||
entries = append(entries, fmt.Sprintf("%s:%d", ip, port))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entries = append(entries, nodePairs...)
|
||||
}
|
||||
|
||||
return strings.Join(entries, ","), inPort, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,11 @@ func (r *Repository) ListForwardPorts(forwardID int64) ([]model.ForwardPortRecor
|
||||
}
|
||||
rows := make([]model.ForwardPortRecord, 0, len(ports))
|
||||
for _, p := range ports {
|
||||
rows = append(rows, model.ForwardPortRecord{NodeID: p.NodeID, Port: p.Port})
|
||||
inIP := ""
|
||||
if p.InIP.Valid {
|
||||
inIP = p.InIP.String
|
||||
}
|
||||
rows = append(rows, model.ForwardPortRecord{NodeID: p.NodeID, Port: p.Port, InIP: inIP})
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
@@ -177,6 +181,9 @@ func nodeRecordFromModel(n *model.Node) *model.NodeRecord {
|
||||
if n.ServerIPV6.Valid {
|
||||
rec.ServerIPv6 = strings.TrimSpace(n.ServerIPV6.String)
|
||||
}
|
||||
if n.ExtraIPs.Valid {
|
||||
rec.ExtraIPs = strings.TrimSpace(n.ExtraIPs.String)
|
||||
}
|
||||
if n.InterfaceName.Valid {
|
||||
rec.InterfaceName = strings.TrimSpace(n.InterfaceName.String)
|
||||
}
|
||||
@@ -289,10 +296,11 @@ func (r *Repository) ListChainNodesForTunnel(tunnelID int64) ([]model.ChainNodeR
|
||||
Name sql.NullString
|
||||
Protocol sql.NullString
|
||||
Strategy sql.NullString
|
||||
ConnectIP sql.NullString
|
||||
}
|
||||
var rows []row
|
||||
err := r.db.Model(&model.ChainTunnel{}).
|
||||
Select("chain_tunnel.chain_type, chain_tunnel.inx, chain_tunnel.node_id, chain_tunnel.port, node.name, chain_tunnel.protocol, chain_tunnel.strategy").
|
||||
Select("chain_tunnel.chain_type, chain_tunnel.inx, chain_tunnel.node_id, chain_tunnel.port, node.name, chain_tunnel.protocol, chain_tunnel.strategy, chain_tunnel.connect_ip").
|
||||
Joins("LEFT JOIN node ON node.id = chain_tunnel.node_id").
|
||||
Where("chain_tunnel.tunnel_id = ?", tunnelID).
|
||||
Order("chain_tunnel.chain_type ASC, chain_tunnel.inx ASC, chain_tunnel.id ASC").
|
||||
@@ -337,6 +345,9 @@ func (r *Repository) ListChainNodesForTunnel(tunnelID int64) ([]model.ChainNodeR
|
||||
} else {
|
||||
item.Strategy = row.Strategy.String
|
||||
}
|
||||
if row.ConnectIP.Valid {
|
||||
item.ConnectIP = row.ConnectIP.String
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result, nil
|
||||
|
||||
@@ -196,7 +196,7 @@ func (r *Repository) GetUserDefaultsForTunnel(userID int64) (flow int64, num int
|
||||
return user.Flow, user.Num, user.ExpTime, user.FlowResetTime, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateNode(name, secret, serverIP string, serverIPV4, serverIPV6, port, interfaceName, version interface{}, httpFlag, tlsFlag, socksFlag int, now int64, status int, tcpAddr, udpAddr string, inx, isRemote int, remoteURL, remoteToken, remoteConfig interface{}) error {
|
||||
func (r *Repository) CreateNode(name, secret, serverIP string, serverIPV4, serverIPV6, port, interfaceName, version interface{}, httpFlag, tlsFlag, socksFlag int, now int64, status int, tcpAddr, udpAddr string, inx, isRemote int, remoteURL, remoteToken, remoteConfig, extraIPs interface{}) error {
|
||||
if r == nil || r.db == nil {
|
||||
return errors.New("repository not initialized")
|
||||
}
|
||||
@@ -206,6 +206,7 @@ func (r *Repository) CreateNode(name, secret, serverIP string, serverIPV4, serve
|
||||
ServerIP: serverIP,
|
||||
ServerIPV4: nullStringFromInterface(serverIPV4),
|
||||
ServerIPV6: nullStringFromInterface(serverIPV6),
|
||||
ExtraIPs: nullStringFromInterface(extraIPs),
|
||||
Port: stringFromInterface(port),
|
||||
InterfaceName: nullStringFromInterface(interfaceName),
|
||||
Version: nullStringFromInterface(version),
|
||||
@@ -238,7 +239,7 @@ func (r *Repository) GetNodeStatusFields(nodeID int64) (status, httpFlag, tlsFla
|
||||
return node.Status, node.HTTP, node.TLS, node.Socks, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateNode(id int64, name, serverIP string, serverIPV4, serverIPV6, port, interfaceName interface{}, httpFlag, tlsFlag, socksFlag int, tcpAddr, udpAddr string, now int64) error {
|
||||
func (r *Repository) UpdateNode(id int64, name, serverIP string, serverIPV4, serverIPV6, port, interfaceName, extraIPs interface{}, httpFlag, tlsFlag, socksFlag int, tcpAddr, udpAddr string, now int64) error {
|
||||
if r == nil || r.db == nil {
|
||||
return errors.New("repository not initialized")
|
||||
}
|
||||
@@ -249,6 +250,7 @@ func (r *Repository) UpdateNode(id int64, name, serverIP string, serverIPV4, ser
|
||||
"server_ip": serverIP,
|
||||
"server_ip_v4": nullStringFromInterface(serverIPV4),
|
||||
"server_ip_v6": nullStringFromInterface(serverIPV6),
|
||||
"extra_ips": nullStringFromInterface(extraIPs),
|
||||
"port": stringFromInterface(port),
|
||||
"interface_name": nullStringFromInterface(interfaceName),
|
||||
"http": httpFlag,
|
||||
@@ -395,7 +397,7 @@ func (r *Repository) DeleteChainTunnelsByTunnelTx(tx *gorm.DB, tunnelID int64) e
|
||||
return tx.Where("tunnel_id = ?", tunnelID).Delete(&model.ChainTunnel{}).Error
|
||||
}
|
||||
|
||||
func (r *Repository) CreateChainTunnelTx(tx *gorm.DB, tunnelID int64, chainType string, nodeID int64, port sql.NullInt64, strategy string, inx int, protocol string) error {
|
||||
func (r *Repository) CreateChainTunnelTx(tx *gorm.DB, tunnelID int64, chainType string, nodeID int64, port sql.NullInt64, strategy string, inx int, protocol string, connectIp string) error {
|
||||
if tx == nil {
|
||||
return errors.New("database unavailable")
|
||||
}
|
||||
@@ -407,6 +409,7 @@ func (r *Repository) CreateChainTunnelTx(tx *gorm.DB, tunnelID int64, chainType
|
||||
Strategy: nullStringFromInterface(strategy),
|
||||
Inx: nullInt64FromInterface(inx),
|
||||
Protocol: nullStringFromInterface(protocol),
|
||||
ConnectIP: sql.NullString{String: connectIp, Valid: connectIp != ""},
|
||||
}
|
||||
return tx.Create(&ct).Error
|
||||
}
|
||||
@@ -692,6 +695,7 @@ func (r *Repository) DeleteForwardCascade(forwardID int64) error {
|
||||
func (r *Repository) ReplaceForwardPorts(forwardID int64, entries []struct {
|
||||
NodeID int64
|
||||
Port int
|
||||
InIP string
|
||||
}) error {
|
||||
if r == nil || r.db == nil {
|
||||
return errors.New("repository not initialized")
|
||||
@@ -705,7 +709,12 @@ func (r *Repository) ReplaceForwardPorts(forwardID int64, entries []struct {
|
||||
}
|
||||
rows := make([]model.ForwardPort, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
rows = append(rows, model.ForwardPort{ForwardID: forwardID, NodeID: e.NodeID, Port: e.Port})
|
||||
rows = append(rows, model.ForwardPort{
|
||||
ForwardID: forwardID,
|
||||
NodeID: e.NodeID,
|
||||
Port: e.Port,
|
||||
InIP: sql.NullString{String: e.InIP, Valid: e.InIP != ""},
|
||||
})
|
||||
}
|
||||
return tx.Create(&rows).Error
|
||||
})
|
||||
@@ -1168,7 +1177,7 @@ func (r *Repository) EnsureUserTunnelGrant(userID, tunnelID int64) (int64, bool,
|
||||
return ut.ID, true, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateForwardTx(userID int64, userName, name string, tunnelID int64, remoteAddr, strategy string, now int64, inx int, entryNodeIDs []int64, port int, speedID interface{}) (int64, error) {
|
||||
func (r *Repository) CreateForwardTx(userID int64, userName, name string, tunnelID int64, remoteAddr, strategy string, now int64, inx int, entryNodeIDs []int64, port int, inIp string, speedID interface{}) (int64, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return 0, errors.New("repository not initialized")
|
||||
}
|
||||
@@ -1198,6 +1207,7 @@ func (r *Repository) CreateForwardTx(userID int64, userName, name string, tunnel
|
||||
ForwardID: forwardID,
|
||||
NodeID: nodeID,
|
||||
Port: port,
|
||||
InIP: sql.NullString{String: inIp, Valid: inIp != ""},
|
||||
}
|
||||
if err := tx.Create(&fp).Error; err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user