2019-01-01 08:23:01 +00:00
package flannel
import (
"context"
2019-09-03 23:41:54 +00:00
"fmt"
2021-08-17 09:27:54 +00:00
"net"
2019-09-06 00:39:18 +00:00
"os"
2019-01-09 16:54:15 +00:00
"path/filepath"
2019-01-01 08:23:01 +00:00
"strings"
2021-10-29 08:59:03 +00:00
"github.com/pkg/errors"
2019-01-09 16:54:15 +00:00
"github.com/rancher/k3s/pkg/agent/util"
"github.com/rancher/k3s/pkg/daemons/config"
2020-05-05 22:09:04 +00:00
"github.com/rancher/k3s/pkg/version"
2019-01-01 08:23:01 +00:00
"github.com/sirupsen/logrus"
2021-10-29 08:59:03 +00:00
corev1 "k8s.io/api/core/v1"
2019-01-01 08:23:01 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-10-29 08:59:03 +00:00
"k8s.io/apimachinery/pkg/fields"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
2021-08-17 09:27:54 +00:00
utilsnet "k8s.io/utils/net"
2019-01-01 08:23:01 +00:00
)
const (
cniConf = ` {
"name" : "cbr0" ,
"cniVersion" : "0.3.1" ,
"plugins" : [
{
"type" : "flannel" ,
"delegate" : {
2019-08-11 05:50:21 +00:00
"hairpinMode" : true ,
2019-01-01 08:23:01 +00:00
"forceAddress" : true ,
"isDefaultGateway" : true
}
} ,
{
"type" : "portmap" ,
"capabilities" : {
"portMappings" : true
}
}
]
}
`
2019-09-03 23:41:54 +00:00
flannelConf = ` {
"Network" : "%CIDR%" ,
2021-08-17 09:27:54 +00:00
"EnableIPv6" : % DUALSTACK % ,
"IPv6Network" : "%CIDR_IPV6%" ,
2019-09-03 23:41:54 +00:00
"Backend" : % backend %
2019-01-01 08:23:01 +00:00
}
`
2019-09-03 23:41:54 +00:00
vxlanBackend = ` {
"Type" : "vxlan"
} `
2019-11-27 17:25:55 +00:00
hostGWBackend = ` {
"Type" : "host-gw"
} `
2019-09-03 23:41:54 +00:00
ipsecBackend = ` {
"Type" : "ipsec" ,
"UDPEncap" : true ,
"PSK" : "%psk%"
} `
wireguardBackend = ` {
"Type" : "extension" ,
2021-06-01 17:24:06 +00:00
"PreStartupCommand" : "wg genkey | tee %flannelConfDir%/privatekey | wg pubkey" ,
"PostStartupCommand" : "export SUBNET_IP=$(echo $SUBNET | cut -d'/' -f 1); ip link del flannel.1 2>/dev/null; echo $PATH >&2; wg-add.sh flannel.1 && wg set flannel.1 listen-port 51820 private-key %flannelConfDir%/privatekey && ip addr add $SUBNET_IP/32 dev flannel.1 && ip link set flannel.1 up && ip route add $NETWORK dev flannel.1" ,
2019-09-03 23:41:54 +00:00
"ShutdownCommand" : "ip link del flannel.1" ,
2019-12-19 21:54:48 +00:00
"SubnetAddCommand" : "read PUBLICKEY; wg set flannel.1 peer $PUBLICKEY endpoint $PUBLIC_IP:51820 allowed-ips $SUBNET persistent-keepalive 25" ,
2019-09-03 23:41:54 +00:00
"SubnetRemoveCommand" : "read PUBLICKEY; wg set flannel.1 peer $PUBLICKEY remove"
} `
2021-08-17 09:27:54 +00:00
emptyIPv6Network = "::/0"
ipv4 = iota
ipv6
2019-01-01 08:23:01 +00:00
)
2019-09-03 23:41:54 +00:00
func Prepare ( ctx context . Context , nodeConfig * config . Node ) error {
if err := createCNIConf ( nodeConfig . AgentConfig . CNIConfDir ) ; err != nil {
2019-01-09 16:54:15 +00:00
return err
}
2019-09-03 23:41:54 +00:00
return createFlannelConf ( nodeConfig )
2019-01-09 16:54:15 +00:00
}
2021-10-29 08:59:03 +00:00
func Run ( ctx context . Context , nodeConfig * config . Node , nodes typedcorev1 . NodeInterface ) error {
2021-11-20 19:12:03 +00:00
logrus . Infof ( "Starting flannel with backend %s" , nodeConfig . FlannelBackend )
2021-10-29 08:59:03 +00:00
if err := waitForPodCIDR ( ctx , nodeConfig . AgentConfig . NodeName , nodes ) ; err != nil {
2021-11-20 19:12:03 +00:00
return errors . Wrap ( err , "flannel failed to wait for PodCIDR assignment" )
2019-01-01 08:23:01 +00:00
}
2021-08-17 09:27:54 +00:00
netMode , err := findNetMode ( nodeConfig . AgentConfig . ClusterCIDRs )
if err != nil {
2021-10-29 08:59:03 +00:00
return errors . Wrap ( err , "failed to check netMode for flannel" )
2021-08-17 09:27:54 +00:00
}
2019-01-09 16:54:15 +00:00
go func ( ) {
2022-01-14 15:54:55 +00:00
err := flannel ( ctx , nodeConfig . FlannelIface , nodeConfig . FlannelConfFile , nodeConfig . AgentConfig . KubeConfigKubelet , nodeConfig . FlannelIPv6Masq , netMode )
2021-09-08 17:56:18 +00:00
if err != nil && ! errors . Is ( err , context . Canceled ) {
logrus . Fatalf ( "flannel exited: %v" , err )
}
2019-01-09 16:54:15 +00:00
} ( )
2019-01-01 08:23:01 +00:00
2019-10-27 05:53:25 +00:00
return nil
2019-01-01 08:23:01 +00:00
}
2021-10-29 08:59:03 +00:00
// waitForPodCIDR watches nodes with this node's name, and returns when the PodCIDR has been set.
func waitForPodCIDR ( ctx context . Context , nodeName string , nodes typedcorev1 . NodeInterface ) error {
fieldSelector := fields . Set { metav1 . ObjectNameField : nodeName } . String ( )
watch , err := nodes . Watch ( ctx , metav1 . ListOptions { FieldSelector : fieldSelector } )
if err != nil {
return err
}
defer watch . Stop ( )
for ev := range watch . ResultChan ( ) {
node , ok := ev . Object . ( * corev1 . Node )
if ! ok {
return fmt . Errorf ( "could not convert event object to node: %v" , ev )
}
if node . Spec . PodCIDR != "" {
break
}
}
2021-11-20 19:12:03 +00:00
logrus . Info ( "Flannel found PodCIDR assigned for node " + nodeName )
2021-10-29 08:59:03 +00:00
return nil
}
2019-01-09 16:54:15 +00:00
func createCNIConf ( dir string ) error {
2021-11-20 19:12:03 +00:00
logrus . Debugf ( "Creating the CNI conf in directory %s" , dir )
2019-01-09 16:54:15 +00:00
if dir == "" {
return nil
}
p := filepath . Join ( dir , "10-flannel.conflist" )
return util . WriteFile ( p , cniConf )
2019-01-01 08:23:01 +00:00
}
2019-09-03 23:41:54 +00:00
func createFlannelConf ( nodeConfig * config . Node ) error {
2021-11-20 19:12:03 +00:00
logrus . Debugf ( "Creating the flannel configuration for backend %s in file %s" , nodeConfig . FlannelBackend , nodeConfig . FlannelConfFile )
if nodeConfig . FlannelConfFile == "" {
return errors . New ( "Flannel configuration not defined" )
2019-01-09 16:54:15 +00:00
}
2019-09-03 23:41:54 +00:00
if nodeConfig . FlannelConfOverride {
2021-11-20 19:12:03 +00:00
logrus . Infof ( "Using custom flannel conf defined at %s" , nodeConfig . FlannelConfFile )
2019-08-08 05:56:09 +00:00
return nil
}
2021-06-07 07:52:26 +00:00
confJSON := strings . ReplaceAll ( flannelConf , "%CIDR%" , nodeConfig . AgentConfig . ClusterCIDR . String ( ) )
2019-09-03 23:41:54 +00:00
var backendConf string
switch nodeConfig . FlannelBackend {
case config . FlannelBackendVXLAN :
backendConf = vxlanBackend
2019-11-27 17:25:55 +00:00
case config . FlannelBackendHostGW :
backendConf = hostGWBackend
2019-09-03 23:41:54 +00:00
case config . FlannelBackendIPSEC :
2021-06-07 07:52:26 +00:00
backendConf = strings . ReplaceAll ( ipsecBackend , "%psk%" , nodeConfig . AgentConfig . IPSECPSK )
2019-09-06 00:39:18 +00:00
if err := setupStrongSwan ( nodeConfig ) ; err != nil {
return err
}
2019-09-03 23:41:54 +00:00
case config . FlannelBackendWireguard :
2021-11-20 19:12:03 +00:00
backendConf = strings . ReplaceAll ( wireguardBackend , "%flannelConfDir%" , filepath . Dir ( nodeConfig . FlannelConfFile ) )
2019-09-03 23:41:54 +00:00
default :
return fmt . Errorf ( "Cannot configure unknown flannel backend '%s'" , nodeConfig . FlannelBackend )
}
2021-06-07 07:52:26 +00:00
confJSON = strings . ReplaceAll ( confJSON , "%backend%" , backendConf )
2019-09-03 23:41:54 +00:00
2021-08-17 09:27:54 +00:00
netMode , err := findNetMode ( nodeConfig . AgentConfig . ClusterCIDRs )
if err != nil {
2021-11-20 19:12:03 +00:00
logrus . Fatalf ( "Flannel error checking netMode: %v" , err )
2021-08-17 09:27:54 +00:00
return err
}
if netMode == ( ipv4 + ipv6 ) {
confJSON = strings . ReplaceAll ( confJSON , "%DUALSTACK%" , "true" )
for _ , cidr := range nodeConfig . AgentConfig . ClusterCIDRs {
if utilsnet . IsIPv6 ( cidr . IP ) {
// Only one ipv6 range available. This might change in future: https://github.com/kubernetes/enhancements/issues/2593
confJSON = strings . ReplaceAll ( confJSON , "%CIDR_IPV6%" , cidr . String ( ) )
}
}
} else {
confJSON = strings . ReplaceAll ( confJSON , "%DUALSTACK%" , "false" )
confJSON = strings . ReplaceAll ( confJSON , "%CIDR_IPV6%" , emptyIPv6Network )
}
2021-11-20 19:12:03 +00:00
logrus . Debugf ( "The flannel configuration is %s" , confJSON )
return util . WriteFile ( nodeConfig . FlannelConfFile , confJSON )
2019-01-01 08:23:01 +00:00
}
2019-09-06 00:39:18 +00:00
func setupStrongSwan ( nodeConfig * config . Node ) error {
2019-10-18 05:38:48 +00:00
// if data dir env is not set point to root
2020-05-05 22:09:04 +00:00
dataDir := os . Getenv ( version . ProgramUpper + "_DATA_DIR" )
2019-09-06 00:39:18 +00:00
if dataDir == "" {
2019-10-18 05:38:48 +00:00
dataDir = "/"
2019-09-06 00:39:18 +00:00
}
2020-04-25 07:22:48 +00:00
dataDir = filepath . Join ( dataDir , "etc" , "strongswan" )
2019-09-06 00:39:18 +00:00
info , err := os . Lstat ( nodeConfig . AgentConfig . StrongSwanDir )
// something exists but is not a symlink, return
if err == nil && info . Mode ( ) & os . ModeSymlink == 0 {
return nil
}
2019-10-18 05:38:48 +00:00
if err == nil {
target , err := os . Readlink ( nodeConfig . AgentConfig . StrongSwanDir )
// current link is the same, return
if err == nil && target == dataDir {
return nil
}
}
2019-09-06 00:39:18 +00:00
// clean up strongswan old link
os . Remove ( nodeConfig . AgentConfig . StrongSwanDir )
// make new strongswan link
return os . Symlink ( dataDir , nodeConfig . AgentConfig . StrongSwanDir )
2019-11-26 20:30:12 +00:00
}
2021-08-17 09:27:54 +00:00
// fundNetMode returns the mode (ipv4, ipv6 or dual-stack) in which flannel is operating
func findNetMode ( cidrs [ ] * net . IPNet ) ( int , error ) {
dualStack , err := utilsnet . IsDualStackCIDRs ( cidrs )
if err != nil {
return 0 , err
}
if dualStack {
return ipv4 + ipv6 , nil
}
for _ , cidr := range cidrs {
if utilsnet . IsIPv4CIDR ( cidr ) {
return ipv4 , nil
}
if utilsnet . IsIPv6CIDR ( cidr ) {
return ipv6 , nil
}
}
return 0 , errors . New ( "Failed checking netMode" )
}