2019-12-27 20:49:24 +00:00
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
2020-07-20 19:43:17 +03:00
package vm
2019-12-27 20:49:24 +00:00
import (
"bytes"
"context"
2020-01-17 22:25:15 +00:00
"crypto/sha256"
"encoding/hex"
2019-12-27 20:49:24 +00:00
"fmt"
2020-01-27 22:02:41 +00:00
"net"
2020-01-17 22:25:15 +00:00
"strconv"
2021-02-08 16:41:32 +03:00
"strings"
2019-12-27 20:49:24 +00:00
"text/template"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/google/uuid"
2020-01-27 22:02:41 +00:00
"github.com/jsimonetti/rtnetlink"
2020-08-14 18:57:15 +03:00
talosnet "github.com/talos-systems/net"
2020-08-12 00:05:35 +03:00
"github.com/talos-systems/talos/pkg/provision"
2019-12-27 20:49:24 +00:00
)
2021-02-08 16:41:32 +03:00
// CreateNetwork builds bridge interface name by taking part of checksum of the network name
2020-07-20 19:43:17 +03:00
// so that interface name is defined by network name, and different networks have
// different bridge interfaces.
2021-02-08 16:41:32 +03:00
//
//nolint: gocyclo
2020-07-20 19:43:17 +03:00
func ( p * Provisioner ) CreateNetwork ( ctx context . Context , state * State , network provision . NetworkRequest ) error {
2020-01-17 22:25:15 +00:00
networkNameHash := sha256 . Sum256 ( [ ] byte ( network . Name ) )
state . BridgeName = fmt . Sprintf ( "%s%s" , "talos" , hex . EncodeToString ( networkNameHash [ : ] ) [ : 8 ] )
2019-12-27 20:49:24 +00:00
// bring up the bridge interface for the first time to get gateway IP assigned
t := template . Must ( template . New ( "bridge" ) . Parse ( bridgeTemplate ) )
var buf bytes . Buffer
err := t . Execute ( & buf , struct {
NetworkName string
InterfaceName string
2020-01-17 22:25:15 +00:00
MTU string
2019-12-27 20:49:24 +00:00
} {
NetworkName : network . Name ,
2020-01-17 22:25:15 +00:00
InterfaceName : state . BridgeName ,
MTU : strconv . Itoa ( network . MTU ) ,
2019-12-27 20:49:24 +00:00
} )
if err != nil {
2020-01-25 16:36:35 +00:00
return fmt . Errorf ( "error templating bridge CNI config: %w" , err )
2019-12-27 20:49:24 +00:00
}
bridgeConfig , err := libcni . ConfFromBytes ( buf . Bytes ( ) )
if err != nil {
2020-01-25 16:36:35 +00:00
return fmt . Errorf ( "error parsing bridge CNI config: %w" , err )
2019-12-27 20:49:24 +00:00
}
cniConfig := libcni . NewCNIConfigWithCacheDir ( network . CNI . BinPath , network . CNI . CacheDir , nil )
ns , err := testutils . NewNS ( )
if err != nil {
return err
}
2020-01-17 22:25:15 +00:00
defer func ( ) {
ns . Close ( ) //nolint: errcheck
testutils . UnmountNS ( ns ) //nolint: errcheck
} ( )
2019-12-27 20:49:24 +00:00
// pick a fake address to use for provisioning an interface
2021-02-08 16:41:32 +03:00
fakeIPs := make ( [ ] string , len ( network . CIDRs ) )
for j := range fakeIPs {
var fakeIP net . IP
fakeIP , err = talosnet . NthIPInNetwork ( & network . CIDRs [ j ] , 2 )
if err != nil {
return err
}
fakeIPs [ j ] = talosnet . FormatCIDR ( fakeIP , network . CIDRs [ j ] )
}
gatewayAddrs := make ( [ ] string , len ( network . GatewayAddrs ) )
for j := range gatewayAddrs {
gatewayAddrs [ j ] = network . GatewayAddrs [ j ] . String ( )
2019-12-27 20:49:24 +00:00
}
containerID := uuid . New ( ) . String ( )
runtimeConf := libcni . RuntimeConf {
ContainerID : containerID ,
NetNS : ns . Path ( ) ,
IfName : "veth0" ,
Args : [ ] [ 2 ] string {
2021-02-08 16:41:32 +03:00
{ "IP" , strings . Join ( fakeIPs , "," ) } ,
{ "GATEWAY" , strings . Join ( gatewayAddrs , "," ) } ,
2019-12-27 20:49:24 +00:00
} ,
}
_ , err = cniConfig . AddNetwork ( ctx , bridgeConfig , & runtimeConf )
if err != nil {
return fmt . Errorf ( "error provisioning bridge CNI network: %w" , err )
}
err = cniConfig . DelNetwork ( ctx , bridgeConfig , & runtimeConf )
if err != nil {
return fmt . Errorf ( "error deleting bridge CNI network: %w" , err )
}
// prepare an actual network config to be used by the VMs
t = template . Must ( template . New ( "network" ) . Parse ( networkTemplate ) )
2020-01-25 16:36:35 +00:00
buf . Reset ( )
2019-12-27 20:49:24 +00:00
2020-01-25 16:36:35 +00:00
err = t . Execute ( & buf , struct {
2019-12-27 20:49:24 +00:00
NetworkName string
InterfaceName string
2020-01-17 22:25:15 +00:00
MTU string
2019-12-27 20:49:24 +00:00
} {
NetworkName : network . Name ,
2020-01-17 22:25:15 +00:00
InterfaceName : state . BridgeName ,
MTU : strconv . Itoa ( network . MTU ) ,
2019-12-27 20:49:24 +00:00
} )
if err != nil {
2020-01-25 16:36:35 +00:00
return fmt . Errorf ( "error templating VM CNI config: %w" , err )
2019-12-27 20:49:24 +00:00
}
2020-07-18 16:20:13 +03:00
if state . VMCNIConfig , err = libcni . ConfListFromBytes ( buf . Bytes ( ) ) ; err != nil {
2020-01-25 16:36:35 +00:00
return fmt . Errorf ( "error parsing VM CNI config: %w" , err )
}
return nil
2019-12-27 20:49:24 +00:00
}
2020-07-20 19:43:17 +03:00
// DestroyNetwork destroy bridge interface by name to clean up.
func ( p * Provisioner ) DestroyNetwork ( state * State ) error {
2020-01-27 22:02:41 +00:00
iface , err := net . InterfaceByName ( state . BridgeName )
if err != nil {
return fmt . Errorf ( "error looking up bridge interface %q: %w" , state . BridgeName , err )
}
rtconn , err := rtnetlink . Dial ( nil )
if err != nil {
return fmt . Errorf ( "error dialing rnetlink: %w" , err )
}
if err = rtconn . Link . Delete ( uint32 ( iface . Index ) ) ; err != nil {
return fmt . Errorf ( "error deleting bridge interface: %w" , err )
}
return nil
}
2019-12-27 20:49:24 +00:00
const bridgeTemplate = `
{
"name" : "{{ .NetworkName }}" ,
"cniVersion" : "0.4.0" ,
"type" : "bridge" ,
"bridge" : "{{ .InterfaceName }}" ,
"ipMasq" : true ,
"isGateway" : true ,
"isDefaultGateway" : true ,
"ipam" : {
"type" : "static"
2020-01-17 22:25:15 +00:00
} ,
"mtu" : { { . MTU } }
2019-12-27 20:49:24 +00:00
}
`
const networkTemplate = `
{
"name" : "{{ .NetworkName }}" ,
"cniVersion" : "0.4.0" ,
"plugins" : [
2020-01-25 16:36:35 +00:00
{
"type" : "bridge" ,
"bridge" : "{{ .InterfaceName }}" ,
"ipMasq" : true ,
"isGateway" : true ,
"isDefaultGateway" : true ,
"ipam" : {
"type" : "static"
} ,
"mtu" : { { . MTU } }
2020-01-17 22:25:15 +00:00
} ,
2020-01-25 16:36:35 +00:00
{
"type" : "firewall"
} ,
{
"type" : "tc-redirect-tap"
}
2019-12-27 20:49:24 +00:00
]
}
`