Skip to content

Commit

Permalink
Feature: support ipv6 single stack network
Browse files Browse the repository at this point in the history
  • Loading branch information
tydra-wang committed May 24, 2022
1 parent 190f6fe commit cfe0d6e
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 26 deletions.
13 changes: 11 additions & 2 deletions cmd/yurt-tunnel-agent/app/options/options.go
Expand Up @@ -25,10 +25,12 @@ import (

"github.com/spf13/pflag"
"k8s.io/klog/v2"
utilnet "k8s.io/utils/net"
"sigs.k8s.io/apiserver-network-proxy/pkg/agent"

"github.com/openyurtio/openyurt/cmd/yurt-tunnel-agent/app/config"
"github.com/openyurtio/openyurt/pkg/projectinfo"
utilip "github.com/openyurtio/openyurt/pkg/util/ip"
"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
kubeutil "github.com/openyurtio/openyurt/pkg/yurttunnel/kubernetes"
)
Expand All @@ -52,7 +54,6 @@ type AgentOptions struct {
// NewAgentOptions creates a new AgentOptions with a default config.
func NewAgentOptions() *AgentOptions {
o := &AgentOptions{
MetaHost: "127.0.0.1",
MetaPort: constants.YurttunnelAgentMetaPort,
}

Expand All @@ -75,6 +76,10 @@ func (o *AgentOptions) Validate() error {
}
}

if o.MetaHost == "" {
o.MetaHost = utilip.MustGetLoopbackIP(utilnet.IsIPv6String(o.NodeIP))
}

if !agentIdentifiersAreValid(o.AgentIdentifiers) {
return errors.New("--agent-identifiers are invalid, format should be host={node-name}")
}
Expand Down Expand Up @@ -134,7 +139,11 @@ func (o *AgentOptions) Config() (*config.Config, error) {
}

if len(c.AgentIdentifiers) == 0 {
c.AgentIdentifiers = fmt.Sprintf("ipv4=%s&host=%s", o.NodeIP, o.NodeName)
ipFamily := "ipv4"
if utilnet.IsIPv6String(o.NodeIP) {
ipFamily = "ipv6"
}
c.AgentIdentifiers = fmt.Sprintf("%s=%s&host=%s", ipFamily, o.NodeIP, o.NodeName)
}
klog.Infof("%s is set for agent identifies", c.AgentIdentifiers)

Expand Down
6 changes: 6 additions & 0 deletions cmd/yurt-tunnel-server/app/config/config.go
Expand Up @@ -25,6 +25,7 @@ import (
"k8s.io/client-go/kubernetes"

"github.com/openyurtio/openyurt/pkg/projectinfo"
"github.com/openyurtio/openyurt/pkg/util/iptables"
"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
)

Expand All @@ -34,6 +35,7 @@ type Config struct {
EnableIptables bool
EnableDNSController bool
IptablesSyncPeriod int
IPFamily iptables.Protocol
DNSSyncPeriod int
CertDNSNames []string
CertIPs []net.IP
Expand Down Expand Up @@ -72,3 +74,7 @@ func (c *Config) Complete() *CompletedConfig {
}
return &CompletedConfig{&cc}
}

func (c *Config) IsIPv6() bool {
return c.IPFamily == iptables.ProtocolIpv6
}
12 changes: 11 additions & 1 deletion cmd/yurt-tunnel-server/app/options/options.go
Expand Up @@ -25,11 +25,14 @@ import (
"github.com/spf13/pflag"
"k8s.io/client-go/informers"
"k8s.io/klog/v2"
utilnet "k8s.io/utils/net"
"sigs.k8s.io/apiserver-network-proxy/pkg/server"

"github.com/openyurtio/openyurt/cmd/yurt-tunnel-server/app/config"
"github.com/openyurtio/openyurt/pkg/projectinfo"
"github.com/openyurtio/openyurt/pkg/util/certmanager"
utilip "github.com/openyurtio/openyurt/pkg/util/ip"
"github.com/openyurtio/openyurt/pkg/util/iptables"
"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
kubeutil "github.com/openyurtio/openyurt/pkg/yurttunnel/kubernetes"
)
Expand Down Expand Up @@ -60,7 +63,6 @@ type ServerOptions struct {
func NewServerOptions() *ServerOptions {
o := &ServerOptions{
BindAddr: "0.0.0.0",
InsecureBindAddr: "127.0.0.1",
EnableIptables: true,
EnableDNSController: true,
IptablesSyncPeriod: 60,
Expand All @@ -81,6 +83,9 @@ func (o *ServerOptions) Validate() error {
return fmt.Errorf("%s's bind address can't be empty",
projectinfo.GetServerName())
}
if len(o.InsecureBindAddr) == 0 {
o.InsecureBindAddr = utilip.MustGetLoopbackIP(utilnet.IsIPv6String(o.BindAddr))
}
return nil
}

Expand Down Expand Up @@ -136,6 +141,11 @@ func (o *ServerOptions) Config() (*config.Config, error) {
}
}

if utilnet.IsIPv6String(o.BindAddr) {
cfg.IPFamily = iptables.ProtocolIpv6
} else {
cfg.IPFamily = iptables.ProtocolIpv4
}
cfg.ListenAddrForAgent = net.JoinHostPort(o.BindAddr, o.TunnelAgentConnectPort)
cfg.ListenAddrForMaster = net.JoinHostPort(o.BindAddr, o.SecurePort)
cfg.ListenInsecureAddrForMaster = net.JoinHostPort(o.InsecureBindAddr, o.InsecurePort)
Expand Down
7 changes: 4 additions & 3 deletions cmd/yurt-tunnel-server/app/start.go
Expand Up @@ -93,11 +93,12 @@ func Run(cfg *config.CompletedConfig, stopCh <-chan struct{}) error {
}
// 1. start the IP table manager
if cfg.EnableIptables {
iptablesMgr := iptables.NewIptablesManager(cfg.Client,
iptablesMgr := iptables.NewIptablesManagerWithIPFamily(cfg.Client,
cfg.SharedInformerFactory.Core().V1().Nodes(),
cfg.ListenAddrForMaster,
cfg.ListenInsecureAddrForMaster,
cfg.IptablesSyncPeriod)
cfg.IptablesSyncPeriod,
cfg.IPFamily)
if iptablesMgr == nil {
return fmt.Errorf("fail to create a new IptableManager")
}
Expand All @@ -115,7 +116,7 @@ func Run(cfg *config.CompletedConfig, stopCh <-chan struct{}) error {

// 3. create handler wrappers
mInitializer := initializer.NewMiddlewareInitializer(cfg.SharedInformerFactory)
wrappers, err := wraphandler.InitHandlerWrappers(mInitializer)
wrappers, err := wraphandler.InitHandlerWrappers(mInitializer, cfg.IsIPv6())
if err != nil {
klog.Errorf("failed to init handler wrappers, %v", err)
return err
Expand Down
6 changes: 6 additions & 0 deletions cmd/yurthub/app/options/options.go
Expand Up @@ -17,12 +17,14 @@ limitations under the License.
package options

import (
"errors"
"fmt"
"net"
"path/filepath"
"time"

"github.com/spf13/pflag"
utilnet "k8s.io/utils/net"

"github.com/openyurtio/openyurt/pkg/projectinfo"
"github.com/openyurtio/openyurt/pkg/yurthub/storage/disk"
Expand Down Expand Up @@ -125,6 +127,10 @@ func ValidateOptions(options *YurtHubOptions) error {
return fmt.Errorf("working mode %s is not supported", options.WorkingMode)
}

if options.EnableDummyIf && utilnet.IsIPv6String(options.YurtHubHost) {
return errors.New("dummy ip not supported in ipv6")
}

if err := verifyDummyIP(options.HubAgentDummyIfIP); err != nil {
return fmt.Errorf("dummy ip %s is not invalid, %w", options.HubAgentDummyIfIP, err)
}
Expand Down
11 changes: 8 additions & 3 deletions pkg/util/certmanager/certmanager.go
Expand Up @@ -38,6 +38,7 @@ import (

"github.com/openyurtio/openyurt/pkg/projectinfo"
"github.com/openyurtio/openyurt/pkg/util/certmanager/store"
utilip "github.com/openyurtio/openyurt/pkg/util/ip"
"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
"github.com/openyurtio/openyurt/pkg/yurttunnel/server/serveraddr"
)
Expand All @@ -64,7 +65,7 @@ func NewYurttunnelServerCertManager(
)

// the ips and dnsNames should be acquired through api-server at the first time, because the informer factory has not started yet.
_ = wait.PollUntil(5*time.Second, func() (bool, error) {
werr := wait.PollUntil(5*time.Second, func() (bool, error) {
dnsNames, ips, err = serveraddr.GetYurttunelServerDNSandIP(clientset)
if err != nil {
klog.Errorf("failed to get yurt tunnel server dns and ip, %v", err)
Expand All @@ -89,10 +90,14 @@ func NewYurttunnelServerCertManager(

return true, nil
}, stopCh)
// add user specified DNS names and IP addresses
if werr != nil {
return nil, werr
}

// add user specified DNS anems and IP addresses
dnsNames = append(dnsNames, clCertNames...)
ips = append(ips, clIPs...)
klog.Infof("subject of tunnel server certificate, ips=%#+v, dnsNames=%#+v", ips, dnsNames)
klog.Infof("subject of tunnel server certificate, ips=%s, dnsNames=%#+v", utilip.JoinIPStrings(ips), dnsNames)

// the dynamic ip acquire func
getIPs := func() ([]net.IP, error) {
Expand Down
52 changes: 52 additions & 0 deletions pkg/util/ip/ip.go
@@ -0,0 +1,52 @@
package ip

import (
"net"
"strings"

"k8s.io/klog/v2"
utilnet "k8s.io/utils/net"
)

const (
DefaultLoopbackIP4 = "127.0.0.1"
DefaultLoopbackIP6 = "::1"
)

// MustGetLoopbackIP is a wrapper for GetLoopbackIP. If any error occurs or loopback interface is not found,
// will fall back to 127.0.0.1 for ipv4 or ::1 for ipv6.
func MustGetLoopbackIP(wantIPv6 bool) string {
ip, err := GetLoopbackIP(wantIPv6)
if err != nil {
klog.Errorf("failed to get loopback addr: %v", err)
}
if ip != "" {
return ip
}
if wantIPv6 {
return DefaultLoopbackIP6
}
return DefaultLoopbackIP4
}

// GetLoopbackIP returns the ip address of local loopback interface.
func GetLoopbackIP(wantIPv6 bool) (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && ipnet.IP.IsLoopback() && wantIPv6 == utilnet.IsIPv6(ipnet.IP) {
return ipnet.IP.String(), nil
}
}
return "", nil
}

func JoinIPStrings(ips []net.IP) string {
var strs []string
for _, ip := range ips {
strs = append(strs, ip.String())
}
return strings.Join(strs, ",")
}
28 changes: 28 additions & 0 deletions pkg/util/ip/ip_test.go
@@ -0,0 +1,28 @@
package ip

import (
"testing"
)

func TestGetLoopbackIP(t *testing.T) {
lo4, err := GetLoopbackIP(false)
if err != nil {
t.Errorf("failed to get ipv4 loopback address: %v", err)
}
t.Logf("got ipv4 loopback address: %s", lo4)
if lo4 != "127.0.0.1" {
t.Errorf("got ipv4 loopback addr: '%s', expect: '127.0.0.1'", lo4)
}

lo6, err := GetLoopbackIP(true)
if err != nil {
t.Errorf("failed to get ipv6 loopback address: %v", err)
}
if lo6 != "" {
// dual stack env
t.Logf("got ipv6 loopback address: %s", lo6)
if lo6 != "::1" {
t.Errorf("got ipv6 loopback addr: '%s', expect: '::1'", lo6)
}
}
}
7 changes: 5 additions & 2 deletions pkg/yurttunnel/handlerwrapper/localhostproxy/handler.go
Expand Up @@ -35,6 +35,7 @@ import (
"k8s.io/klog/v2"

"github.com/openyurtio/openyurt/pkg/projectinfo"
utilip "github.com/openyurtio/openyurt/pkg/util/ip"
"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
hw "github.com/openyurtio/openyurt/pkg/yurttunnel/handlerwrapper"
"github.com/openyurtio/openyurt/pkg/yurttunnel/util"
Expand All @@ -47,11 +48,13 @@ type localHostProxyMiddleware struct {
localhostPorts map[string]struct{}
nodeInformerSynced cache.InformerSynced
cmInformerSynced cache.InformerSynced
loopbackAddr string
}

func NewLocalHostProxyMiddleware() hw.Middleware {
func NewLocalHostProxyMiddleware(isIPv6 bool) hw.Middleware {
return &localHostProxyMiddleware{
localhostPorts: make(map[string]struct{}),
loopbackAddr: utilip.MustGetLoopbackIP(isIPv6),
}
}

Expand Down Expand Up @@ -99,7 +102,7 @@ func (plm *localHostProxyMiddleware) WrapHandler(handler http.Handler) http.Hand
req.Header.Set(constants.ProxyHostHeaderKey, nodeName)
}

proxyDest = fmt.Sprintf("127.0.0.1:%s", port)
proxyDest = net.JoinHostPort(plm.loopbackAddr, port)
oldHost := req.URL.Host
req.Host = proxyDest
req.Header.Set("Host", proxyDest)
Expand Down
4 changes: 2 additions & 2 deletions pkg/yurttunnel/handlerwrapper/wraphandler/wraphandler.go
Expand Up @@ -27,7 +27,7 @@ import (
"github.com/openyurtio/openyurt/pkg/yurttunnel/handlerwrapper/tracerequest"
)

func InitHandlerWrappers(mi initializer.MiddlewareInitializer) (hw.HandlerWrappers, error) {
func InitHandlerWrappers(mi initializer.MiddlewareInitializer, isIPv6 bool) (hw.HandlerWrappers, error) {
wrappers := make(hw.HandlerWrappers, 0)
// register all of middleware here
//
Expand All @@ -42,7 +42,7 @@ func InitHandlerWrappers(mi initializer.MiddlewareInitializer) (hw.HandlerWrappe
//
// then the middleware m2 will be called before the mw1
wrappers = append(wrappers, tracerequest.NewTraceReqMiddleware())
wrappers = append(wrappers, localhostproxy.NewLocalHostProxyMiddleware())
wrappers = append(wrappers, localhostproxy.NewLocalHostProxyMiddleware(isIPv6))

// init all of wrappers
for i := range wrappers {
Expand Down
2 changes: 1 addition & 1 deletion pkg/yurttunnel/server/interceptor.go
Expand Up @@ -88,7 +88,7 @@ func NewRequestInterceptor(udsSockFile string, cfg *tls.Config) *RequestIntercep
}
}

fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s%s\r\n\r\n", addr, "127.0.0.1", connectHeaders)
fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: localhost%s\r\n\r\n", addr, connectHeaders)
br := newBufioReader(proxyConn)
defer putBufioReader(br)
res, err := http.ReadResponse(br, nil)
Expand Down
8 changes: 5 additions & 3 deletions pkg/yurttunnel/server/serveraddr/addr.go
Expand Up @@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"net"
"strconv"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -30,6 +31,7 @@ import (
"k8s.io/klog/v2"

"github.com/openyurtio/openyurt/pkg/projectinfo"
utilip "github.com/openyurtio/openyurt/pkg/util/ip"
"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
)

Expand Down Expand Up @@ -57,7 +59,7 @@ func GetTunnelServerAddr(clientset kubernetes.Interface) (string, error) {

for _, tmpIP := range ips {
// we use the first non-loopback IP address.
if tmpIP.String() != "127.0.0.1" {
if s := tmpIP.String(); s != utilip.DefaultLoopbackIP4 && s != utilip.DefaultLoopbackIP6 {
ip = tmpIP
break
}
Expand Down Expand Up @@ -86,7 +88,7 @@ func GetTunnelServerAddr(clientset kubernetes.Interface) (string, error) {
return "", errors.New("fail to get the port number")
}

return fmt.Sprintf("%s:%d", host, tcpPort), nil
return net.JoinHostPort(host, strconv.Itoa(int(tcpPort))), nil
}

// GetYurttunelServerDNSandIP gets DNS names and IPS for generating tunnel server certificate.
Expand Down Expand Up @@ -231,7 +233,7 @@ func extractTunnelServerDNSandIPs(svc *corev1.Service, eps []*corev1.Endpoints,
if svc.Spec.ClusterIP != "None" {
ips = append(ips, net.ParseIP(svc.Spec.ClusterIP))
}
ips = append(ips, net.ParseIP("127.0.0.1"))
ips = append(ips, net.ParseIP(utilip.DefaultLoopbackIP4), net.ParseIP(utilip.DefaultLoopbackIP6))

// 3. extract dns and ip from the endpoint
for _, eps := range eps {
Expand Down

0 comments on commit cfe0d6e

Please sign in to comment.