|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// Copyright Authors of Cilium |
| 3 | + |
| 4 | +package subnet |
| 5 | + |
| 6 | +import ( |
| 7 | + "fmt" |
| 8 | + "log/slog" |
| 9 | + "net/netip" |
| 10 | + "strings" |
| 11 | + |
| 12 | + "github.com/cilium/hive/cell" |
| 13 | + "github.com/cilium/hive/job" |
| 14 | + "github.com/cilium/statedb" |
| 15 | + |
| 16 | + "github.com/cilium/cilium/pkg/dynamicconfig" |
| 17 | + subnetTable "github.com/cilium/cilium/pkg/maps/subnet" |
| 18 | +) |
| 19 | + |
| 20 | +type watcherParams struct { |
| 21 | + cell.In |
| 22 | + |
| 23 | + Logger *slog.Logger |
| 24 | + DynamicConfigTable statedb.Table[dynamicconfig.DynamicConfig] |
| 25 | + SubnetTable statedb.RWTable[subnetTable.SubnetTableEntry] |
| 26 | + DB *statedb.DB |
| 27 | + JobGroup job.Group |
| 28 | +} |
| 29 | + |
| 30 | +type SubnetWatcher struct { |
| 31 | + logger *slog.Logger |
| 32 | + dynamicConfigTable statedb.Table[dynamicconfig.DynamicConfig] |
| 33 | + subnetTable statedb.RWTable[subnetTable.SubnetTableEntry] |
| 34 | + db *statedb.DB |
| 35 | + jobGroup job.Group |
| 36 | +} |
| 37 | + |
| 38 | +func newSubnetWatcher(params watcherParams) *SubnetWatcher { |
| 39 | + return &SubnetWatcher{ |
| 40 | + logger: params.Logger, |
| 41 | + dynamicConfigTable: params.DynamicConfigTable, |
| 42 | + subnetTable: params.SubnetTable, |
| 43 | + db: params.DB, |
| 44 | + jobGroup: params.JobGroup, |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +func (w *SubnetWatcher) processSubnetConfigEntry(entry dynamicconfig.DynamicConfig) error { |
| 49 | + subnetEntries, err := decodeJson(entry.Value) |
| 50 | + if err != nil { |
| 51 | + return fmt.Errorf("failed to decode subnet-topology dynamic config value: %w", err) |
| 52 | + } |
| 53 | + |
| 54 | + // Write to the subnet table. |
| 55 | + // Reset the table and write all entries afresh. |
| 56 | + wTx := w.db.WriteTxn(w.subnetTable) |
| 57 | + defer wTx.Abort() |
| 58 | + |
| 59 | + if err := w.subnetTable.DeleteAll(wTx); err != nil { |
| 60 | + return fmt.Errorf("failed to reset subnet table: %w", err) |
| 61 | + } |
| 62 | + for _, entry := range subnetEntries { |
| 63 | + if _, _, err := w.subnetTable.Insert(wTx, entry); err != nil { |
| 64 | + return fmt.Errorf("failed to upsert subnet entry %v: %w", entry, err) |
| 65 | + } |
| 66 | + } |
| 67 | + wTx.Commit() |
| 68 | + return nil |
| 69 | +} |
| 70 | + |
| 71 | +// decodeJson decodes a JSON string into a slice of SubnetTableEntry. |
| 72 | +// Ex: data=10.0.0.1/24,10.10.0.1/24;10.20.0.1/24;2001:0db8:85a3::/64 |
| 73 | +// would decode into four SubnetTableEntry objects. |
| 74 | +// | Key | Value | |
| 75 | +// |------|-----------| |
| 76 | +// | 10.0.0.1/24 | 1 | |
| 77 | +// | 10.10.0.1/24 | 1 | |
| 78 | +// | 10.20.0.1/24 | 2 | |
| 79 | +// | 2001:0db8:85a3::/64 | 3 | |
| 80 | +func decodeJson(data string) ([]subnetTable.SubnetTableEntry, error) { |
| 81 | + data = strings.TrimSpace(data) |
| 82 | + if data == "" { |
| 83 | + return []subnetTable.SubnetTableEntry{}, nil |
| 84 | + } |
| 85 | + |
| 86 | + var entries []subnetTable.SubnetTableEntry |
| 87 | + |
| 88 | + // Split by semicolons to get groups |
| 89 | + groups := strings.Split(data, ";") |
| 90 | + |
| 91 | + for groupID, group := range groups { |
| 92 | + group = strings.TrimSpace(group) |
| 93 | + if group == "" { |
| 94 | + continue |
| 95 | + } |
| 96 | + |
| 97 | + // Split by commas to get individual subnets within a group |
| 98 | + subnets := strings.SplitSeq(group, ",") |
| 99 | + |
| 100 | + for subnet := range subnets { |
| 101 | + subnet = strings.TrimSpace(subnet) |
| 102 | + if subnet == "" { |
| 103 | + continue |
| 104 | + } |
| 105 | + |
| 106 | + // Validate CIDR format |
| 107 | + prefix, err := netip.ParsePrefix(subnet) |
| 108 | + if err != nil { |
| 109 | + return nil, fmt.Errorf("invalid CIDR %q: %w", subnet, err) |
| 110 | + } |
| 111 | + |
| 112 | + // Identity is groupID + 1 to avoid using identity 0. |
| 113 | + entries = append(entries, subnetTable.NewSubnetEntry(prefix, uint32(groupID+1))) |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + if len(entries) == 0 { |
| 118 | + return nil, fmt.Errorf("no valid subnets found in data") |
| 119 | + } |
| 120 | + |
| 121 | + return entries, nil |
| 122 | +} |
0 commit comments