20
"github.com/containernetworking/cni/libcni"
21
"github.com/containernetworking/plugins/pkg/testutils"
22
"github.com/coreos/go-iptables/iptables"
23
"github.com/google/uuid"
24
"github.com/jsimonetti/rtnetlink/v2"
25
"github.com/siderolabs/gen/xslices"
26
sideronet "github.com/siderolabs/net"
27
"github.com/vishvananda/netlink"
29
"github.com/siderolabs/talos/pkg/provision"
37
func (p *Provisioner) CreateNetwork(ctx context.Context, state *State, network provision.NetworkRequest, options provision.Options) error {
38
networkNameHash := sha256.Sum256([]byte(network.Name))
39
state.BridgeName = fmt.Sprintf("%s%s", "talos", hex.EncodeToString(networkNameHash[:])[:8])
42
t := template.Must(template.New("bridge").Parse(bridgeTemplate))
46
err := t.Execute(&buf, struct {
51
NetworkName: network.Name,
52
InterfaceName: state.BridgeName,
53
MTU: strconv.Itoa(network.MTU),
56
return fmt.Errorf("error templating bridge CNI config: %w", err)
59
bridgeConfig, err := libcni.ConfFromBytes(buf.Bytes())
61
return fmt.Errorf("error parsing bridge CNI config: %w", err)
64
cniConfig := libcni.NewCNIConfigWithCacheDir(network.CNI.BinPath, network.CNI.CacheDir, nil)
66
ns, err := testutils.NewNS()
73
testutils.UnmountNS(ns)
77
fakeIPs := make([]string, len(network.CIDRs))
78
for j := range fakeIPs {
81
fakeIP, err = sideronet.NthIPInNetwork(network.CIDRs[j], 2)
86
fakeIPs[j] = sideronet.FormatCIDR(fakeIP, network.CIDRs[j])
89
gatewayAddrs := xslices.Map(network.GatewayAddrs, netip.Addr.String)
91
containerID := uuid.New().String()
92
runtimeConf := libcni.RuntimeConf{
93
ContainerID: containerID,
97
{"IP", strings.Join(fakeIPs, ",")},
98
{"GATEWAY", strings.Join(gatewayAddrs, ",")},
99
{"IgnoreUnknown", "1"},
103
_, err = cniConfig.AddNetwork(ctx, bridgeConfig, &runtimeConf)
105
return fmt.Errorf("error provisioning bridge CNI network: %w", err)
108
err = cniConfig.DelNetwork(ctx, bridgeConfig, &runtimeConf)
110
return fmt.Errorf("error deleting bridge CNI network: %w", err)
114
t = template.Must(template.New("network").Parse(networkTemplate))
118
err = t.Execute(&buf, struct {
123
NetworkName: network.Name,
124
InterfaceName: state.BridgeName,
125
MTU: strconv.Itoa(network.MTU),
128
return fmt.Errorf("error templating VM CNI config: %w", err)
131
if state.VMCNIConfig, err = libcni.ConfListFromBytes(buf.Bytes()); err != nil {
132
return fmt.Errorf("error parsing VM CNI config: %w", err)
140
if err = p.allowBridgeTraffic(state.BridgeName); err != nil {
141
return fmt.Errorf("error configuring DOCKER-USER chain: %w", err)
145
if network.NetworkChaos {
146
if err = p.configureNetworkChaos(network, state, options); err != nil {
154
func (p *Provisioner) allowBridgeTraffic(bridgeName string) error {
155
ipt, err := iptables.New()
157
return fmt.Errorf("error initializing iptables: %w", err)
160
chainExists, err := ipt.ChainExists("filter", "DOCKER-USER")
162
return fmt.Errorf("error checking chain existence: %w", err)
166
if err = ipt.NewChain("filter", "DOCKER-USER"); err != nil {
167
return fmt.Errorf("error creating DOCKER-USER chain: %w", err)
171
if err := ipt.InsertUnique("filter", "DOCKER-USER", 1, "-i", bridgeName, "-o", bridgeName, "-j", "ACCEPT"); err != nil {
172
return fmt.Errorf("error inserting rule into DOCKER-USER chain: %w", err)
178
func (p *Provisioner) dropBridgeTrafficRule(bridgeName string) error {
179
ipt, err := iptables.New()
181
return fmt.Errorf("error initializing iptables: %w", err)
184
chainExists, err := ipt.ChainExists("filter", "DOCKER-USER")
186
return fmt.Errorf("error checking chain existence: %w", err)
193
if err := ipt.DeleteIfExists("filter", "DOCKER-USER", "-i", bridgeName, "-o", bridgeName, "-j", "ACCEPT"); err != nil {
194
return fmt.Errorf("error deleting rule in DOCKER-USER chain: %w", err)
201
func (p *Provisioner) configureNetworkChaos(network provision.NetworkRequest, state *State, options provision.Options) error {
202
if (network.Bandwidth != 0) && (network.Latency != 0 || network.Jitter != 0 || network.PacketLoss != 0 || network.PacketReorder != 0 || network.PacketCorrupt != 0) {
203
return errors.New("bandwidth and other chaos options cannot be used together")
206
link, err := netlink.LinkByName(state.BridgeName)
208
return fmt.Errorf("could not get link: %v", err)
211
fmt.Fprintln(options.LogWriter, "network chaos enabled on interface:", state.BridgeName)
213
if network.Bandwidth != 0 {
214
fmt.Fprintf(options.LogWriter, " bandwidth: %4d kbps\n", network.Bandwidth)
216
rate := network.Bandwidth * 1000 / 8
222
qdisc := &netlink.Tbf{
223
QdiscAttrs: netlink.QdiscAttrs{
224
LinkIndex: link.Attrs().Index,
225
Handle: netlink.MakeHandle(1, 0),
226
Parent: netlink.HANDLE_ROOT,
229
Buffer: uint32(buffer),
230
Limit: uint32(limit),
233
if err := netlink.QdiscAdd(qdisc); err != nil {
234
return fmt.Errorf("could not add netem qdisc: %v", err)
237
packetLoss := network.PacketLoss * 100
238
packetReorder := network.PacketReorder * 100
239
packetCorrupt := network.PacketCorrupt * 100
241
fmt.Fprintf(options.LogWriter, " jitter: %4dms\n", network.Jitter.Milliseconds())
242
fmt.Fprintf(options.LogWriter, " latency: %4dms\n", network.Latency.Milliseconds())
243
fmt.Fprintf(options.LogWriter, " packet loss: %4v%%\n", packetLoss)
244
fmt.Fprintf(options.LogWriter, " packet reordering: %4v%%\n", packetReorder)
245
fmt.Fprintf(options.LogWriter, " packet corruption: %4v%%\n", packetCorrupt)
247
qdisc := netlink.NewNetem(
249
LinkIndex: link.Attrs().Index,
250
Handle: netlink.MakeHandle(1, 0),
251
Parent: netlink.HANDLE_ROOT,
253
netlink.NetemQdiscAttrs{
254
Jitter: uint32(network.Jitter / 1000),
255
Latency: uint32(network.Latency / 1000),
256
Loss: float32(packetLoss),
257
ReorderProb: float32(packetReorder),
258
CorruptProb: float32(packetCorrupt),
261
if err := netlink.QdiscAdd(qdisc); err != nil {
262
return fmt.Errorf("could not add netem qdisc: %v", err)
270
func (p *Provisioner) DestroyNetwork(state *State) error {
271
iface, err := net.InterfaceByName(state.BridgeName)
273
return fmt.Errorf("error looking up bridge interface %q: %w", state.BridgeName, err)
276
rtconn, err := rtnetlink.Dial(nil)
278
return fmt.Errorf("error dialing rnetlink: %w", err)
281
if err = rtconn.Link.Delete(uint32(iface.Index)); err != nil {
282
return fmt.Errorf("error deleting bridge interface: %w", err)
285
if err = p.dropBridgeTrafficRule(state.BridgeName); err != nil {
286
return fmt.Errorf("error dropping bridge traffic rule: %w", err)
292
const bridgeTemplate = `
294
"name": "{{ .NetworkName }}",
295
"cniVersion": "0.4.0",
297
"bridge": "{{ .InterfaceName }}",
300
"isDefaultGateway": true,
308
const networkTemplate = `
310
"name": "{{ .NetworkName }}",
311
"cniVersion": "0.4.0",
315
"bridge": "{{ .InterfaceName }}",
318
"isDefaultGateway": true,
328
"type": "tc-redirect-tap"