package app import ( "fmt" "strconv" "strings" ) func transportRoutingTableForID(id string) string { s := strings.TrimSpace(id) if s == "" { return "agvpn_client" } s = strings.ReplaceAll(s, "-", "_") s = strings.ReplaceAll(s, ".", "_") return normalizeTransportRoutingTable("agvpn_"+s, "agvpn_client") } func transportRoutingTableForIfaceID(ifaceID string) string { id := normalizeTransportIfaceID(ifaceID) if id == transportDefaultIfaceID { return "agvpn_shared" } s := strings.ReplaceAll(id, "-", "_") return normalizeTransportRoutingTable("agvpn_if_"+s, "agvpn_client") } func transportRoutingTableForIDUnique(id string, used map[string]struct{}) string { base := transportRoutingTableForID(id) if _, ok := used[base]; !ok { used[base] = struct{}{} return base } for i := 2; i < 1000; i++ { suffix := "_" + strconv.Itoa(i) maxBase := 31 - len(suffix) if maxBase < 1 { maxBase = 1 } prefix := base if len(prefix) > maxBase { prefix = prefix[:maxBase] } cand := prefix + suffix if _, ok := used[cand]; ok { continue } used[cand] = struct{}{} return cand } used[base] = struct{}{} return base } func normalizeTransportRoutingTable(raw string, fallback string) string { table := strings.ToLower(strings.TrimSpace(raw)) if table == "" { table = strings.ToLower(strings.TrimSpace(fallback)) } if table == "" { return "agvpn_client" } table = strings.ReplaceAll(table, "-", "_") table = strings.ReplaceAll(table, ".", "_") table = strings.ReplaceAll(table, " ", "_") var b strings.Builder b.Grow(len(table)) lastUnderscore := false for i := 0; i < len(table); i++ { ch := table[i] isAZ := ch >= 'a' && ch <= 'z' is09 := ch >= '0' && ch <= '9' if isAZ || is09 { b.WriteByte(ch) lastUnderscore = false continue } if !lastUnderscore { b.WriteByte('_') lastUnderscore = true } } table = strings.Trim(b.String(), "_") if table == "" { table = "agvpn_client" } if !strings.HasPrefix(table, "agvpn_") { table = "agvpn_" + table } if len(table) > 31 { table = strings.Trim(table[:31], "_") } if table == "" { return "agvpn_client" } return table } func parseTransportMarkHex(raw string) (uint64, bool) { s := strings.ToLower(strings.TrimSpace(raw)) if !strings.HasPrefix(s, "0x") { return 0, false } n, err := strconv.ParseUint(strings.TrimPrefix(s, "0x"), 16, 64) if err != nil { return 0, false } if !transportMarkAllowed(n) { return 0, false } return n, true } func parseTransportPref(raw int) (int, bool) { if !transportPrefAllowed(raw) { return 0, false } return raw, true } func transportMarkAllowed(mark uint64) bool { if mark < transportMarkStart || mark > transportMarkEnd { return false } if mark <= transportMarkReserveEnd { return false } return true } func transportPrefAllowed(pref int) bool { if pref < transportPrefStart || pref > transportPrefEnd { return false } if pref <= transportPrefReserveEnd { return false } if (pref-transportPrefStart)%transportPrefStep != 0 { return false } return true } func nextTransportMark(used map[uint64]struct{}) uint64 { for v := transportMarkStart; v <= transportMarkEnd; v++ { if !transportMarkAllowed(v) { continue } if _, ok := used[v]; ok { continue } return v } // Fallback: reuse first available from pool even if exhausted. for v := transportMarkStart; v <= transportMarkEnd; v++ { if !transportMarkAllowed(v) { continue } return v } return transportMarkStart } func nextTransportPref(used map[int]struct{}) int { for p := transportPrefStart; p <= transportPrefEnd; p += transportPrefStep { if !transportPrefAllowed(p) { continue } if _, ok := used[p]; ok { continue } return p } // Fallback: reuse first available from pool even if exhausted. for p := transportPrefStart; p <= transportPrefEnd; p += transportPrefStep { if !transportPrefAllowed(p) { continue } return p } return transportPrefStart } func formatTransportMarkHex(mark uint64) string { return fmt.Sprintf("0x%x", mark) }