Files
elmprodvpn/selective-vpn-api/app/transport_interfaces_state.go

285 lines
7.6 KiB
Go

package app
import (
"sort"
"strings"
)
func normalizeTransportIfaceID(raw string) string {
id := sanitizeID(raw)
if strings.TrimSpace(id) == "" {
return transportDefaultIfaceID
}
return id
}
func normalizeTransportInterfaceMode(raw TransportInterfaceMode, ifaceID string) TransportInterfaceMode {
mode := TransportInterfaceMode(strings.ToLower(strings.TrimSpace(string(raw))))
switch mode {
case TransportInterfaceModeShared, TransportInterfaceModeDedicated:
return mode
default:
if normalizeTransportIfaceID(ifaceID) == transportDefaultIfaceID {
return TransportInterfaceModeShared
}
return TransportInterfaceModeDedicated
}
}
func defaultTransportInterfaceName(ifaceID string) string {
if normalizeTransportIfaceID(ifaceID) == transportDefaultIfaceID {
return "Shared interface"
}
return ifaceID
}
func transportInterfaceRoutingTableHint(iface TransportInterface) string {
if strings.TrimSpace(iface.RoutingTable) != "" {
return strings.TrimSpace(iface.RoutingTable)
}
if iface.Config == nil {
return ""
}
if v := strings.TrimSpace(transportConfigString(iface.Config, "routing_table")); v != "" {
return v
}
if v := strings.TrimSpace(transportConfigString(iface.Config, "table")); v != "" {
return v
}
return ""
}
func resolveTransportInterfaceRoutingTable(iface TransportInterface, ifaceID string) string {
hint := transportInterfaceRoutingTableHint(iface)
if hint != "" {
return normalizeTransportRoutingTable(hint, transportRoutingTableForIfaceID(ifaceID))
}
if normalizeTransportIfaceID(ifaceID) == transportDefaultIfaceID {
return ""
}
return transportRoutingTableForIfaceID(ifaceID)
}
func normalizeTransportInterfacesState(st transportInterfacesState, clients []TransportClient) (transportInterfacesState, bool) {
changed := false
st.Version = transportStateVersion
if st.Items == nil {
st.Items = nil
}
byID := map[string]int{}
out := make([]TransportInterface, 0, len(st.Items)+1)
for _, raw := range st.Items {
it := raw
id := normalizeTransportIfaceID(it.ID)
if id == "" {
changed = true
continue
}
if it.ID != id {
it.ID = id
changed = true
}
if strings.TrimSpace(it.Name) == "" {
it.Name = defaultTransportInterfaceName(id)
changed = true
}
mode := normalizeTransportInterfaceMode(it.Mode, id)
if it.Mode != mode {
it.Mode = mode
changed = true
}
wantTable := resolveTransportInterfaceRoutingTable(it, id)
if strings.TrimSpace(it.RoutingTable) != wantTable {
it.RoutingTable = wantTable
changed = true
}
if it.Config != nil {
it.Config = cloneMap(it.Config)
}
if idx, ok := byID[id]; ok {
if preferTransportInterface(it, out[idx]) {
out[idx] = it
}
changed = true
continue
}
byID[id] = len(out)
out = append(out, it)
}
ensureIface := func(id string) {
normID := normalizeTransportIfaceID(id)
if normID == "" {
return
}
if _, ok := byID[normID]; ok {
return
}
it := TransportInterface{
ID: normID,
Name: defaultTransportInterfaceName(normID),
Mode: normalizeTransportInterfaceMode("", normID),
}
it.RoutingTable = resolveTransportInterfaceRoutingTable(it, normID)
out = append(out, it)
byID[normID] = len(out) - 1
changed = true
}
ensureIface(transportDefaultIfaceID)
for _, it := range clients {
ensureIface(it.IfaceID)
}
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
st.Items = out
return st, changed
}
func preferTransportInterface(cand, cur TransportInterface) bool {
cu := strings.TrimSpace(cand.UpdatedAt)
ou := strings.TrimSpace(cur.UpdatedAt)
if cu != ou {
if cu == "" {
return false
}
if ou == "" {
return true
}
return cu > ou
}
return strings.TrimSpace(cand.Name) > strings.TrimSpace(cur.Name)
}
func buildTransportInterfaceItems(ifaces []TransportInterface, clients []TransportClient) []TransportInterfaceItem {
group := map[string][]TransportClient{}
for _, it := range clients {
id := normalizeTransportIfaceID(it.IfaceID)
group[id] = append(group[id], it)
}
items := make([]TransportInterfaceItem, 0, len(ifaces))
seen := map[string]struct{}{}
for _, iface := range ifaces {
id := normalizeTransportIfaceID(iface.ID)
if id == "" {
continue
}
seen[id] = struct{}{}
clientsForIface := append([]TransportClient(nil), group[id]...)
sort.Slice(clientsForIface, func(i, j int) bool { return clientsForIface[i].ID < clientsForIface[j].ID })
clientIDs := make([]string, 0, len(clientsForIface))
upCount := 0
for _, cl := range clientsForIface {
clientIDs = append(clientIDs, cl.ID)
if cl.Status == TransportClientUp {
upCount++
}
}
items = append(items, TransportInterfaceItem{
ID: id,
Name: strings.TrimSpace(iface.Name),
Mode: normalizeTransportInterfaceMode(iface.Mode, id),
RuntimeIface: strings.TrimSpace(iface.RuntimeIface),
NetnsName: strings.TrimSpace(iface.NetnsName),
RoutingTable: strings.TrimSpace(iface.RoutingTable),
Config: cloneMap(iface.Config),
UpdatedAt: strings.TrimSpace(iface.UpdatedAt),
ClientIDs: clientIDs,
ClientCount: len(clientIDs),
UpCount: upCount,
})
}
for id, clientsForIface := range group {
if _, ok := seen[id]; ok {
continue
}
sort.Slice(clientsForIface, func(i, j int) bool { return clientsForIface[i].ID < clientsForIface[j].ID })
clientIDs := make([]string, 0, len(clientsForIface))
upCount := 0
routingTable := ""
for _, cl := range clientsForIface {
clientIDs = append(clientIDs, cl.ID)
if cl.Status == TransportClientUp {
upCount++
}
if routingTable == "" && strings.TrimSpace(cl.RoutingTable) != "" {
routingTable = strings.TrimSpace(cl.RoutingTable)
}
}
if routingTable == "" && id != transportDefaultIfaceID {
routingTable = transportRoutingTableForIfaceID(id)
}
items = append(items, TransportInterfaceItem{
ID: id,
Name: defaultTransportInterfaceName(id),
Mode: normalizeTransportInterfaceMode("", id),
RoutingTable: routingTable,
ClientIDs: clientIDs,
ClientCount: len(clientIDs),
UpCount: upCount,
})
}
sort.Slice(items, func(i, j int) bool { return items[i].ID < items[j].ID })
return items
}
func appendTransportVirtualInterfaceItems(items []TransportInterfaceItem, clients []TransportClient) []TransportInterfaceItem {
if len(clients) == 0 {
return items
}
byID := map[string]int{}
for idx, it := range items {
byID[normalizeTransportIfaceID(it.ID)] = idx
if id := strings.TrimSpace(sanitizeID(it.ID)); id != "" {
byID[id] = idx
}
}
for _, client := range clients {
if !isTransportPolicyVirtualClient(client) {
continue
}
id := strings.TrimSpace(sanitizeID(client.ID))
if id == "" {
continue
}
item := TransportInterfaceItem{
ID: id,
Name: strings.TrimSpace(client.Name),
Mode: normalizeTransportInterfaceMode("", client.IfaceID),
RuntimeIface: strings.TrimSpace(client.Iface),
NetnsName: "",
RoutingTable: strings.TrimSpace(client.RoutingTable),
UpdatedAt: strings.TrimSpace(client.UpdatedAt),
ClientIDs: []string{id},
ClientCount: 1,
UpCount: transportInterfaceItemUpCountForStatus(client.Status),
Config: map[string]any{
"virtual": true,
"virtual_owner": id,
},
}
if item.Name == "" {
item.Name = id
}
if idx, ok := byID[id]; ok {
items[idx] = item
continue
}
items = append(items, item)
byID[id] = len(items) - 1
}
sort.Slice(items, func(i, j int) bool { return items[i].ID < items[j].ID })
return items
}
func transportInterfaceItemUpCountForStatus(status TransportClientStatus) int {
if normalizeTransportStatus(status) == TransportClientUp {
return 1
}
return 0
}