Skip to content

Commit

Permalink
[IPv6] Add support for IPv6 address in antctl and agent's apiserver
Browse files Browse the repository at this point in the history
1. Support using IPv6 address in OVS tracing.
2. Support displaying Node's and Pod's IPv6 address in agent apiserver.
  • Loading branch information
wenyingd committed Aug 24, 2020
1 parent 672edf8 commit b4d1470
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 42 deletions.
2 changes: 1 addition & 1 deletion pkg/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestInitstore(t *testing.T) {
container1, found1 := store.GetContainerInterface(uuid1)
if !found1 {
t.Errorf("Failed to load OVS port into local store")
} else if container1.OFPort != 11 || container1.GetIPv4Addr() == nil || container1.GetIPv4Addr().String() != p1IP || container1.MAC.String() != p1MAC || container1.InterfaceName != "p1" {
} else if container1.OFPort != 11 || len(container1.IPs) == 0 || container1.IPs[0].String() != p1IP || container1.MAC.String() != p1MAC || container1.InterfaceName != "p1" {
t.Errorf("Failed to load OVS port configuration into local store")
}
_, found2 := store.GetContainerInterface(uuid2)
Expand Down
114 changes: 93 additions & 21 deletions pkg/agent/apiserver/handlers/ovstracing/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ import (
"encoding/json"
"errors"
"fmt"
net2 "k8s.io/utils/net"
"net"
"net/http"
"strings"

corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"

"github.com/vmware-tanzu/antrea/pkg/agent/apiserver/handlers"
"github.com/vmware-tanzu/antrea/pkg/agent/interfacestore"
"github.com/vmware-tanzu/antrea/pkg/agent/querier"
"github.com/vmware-tanzu/antrea/pkg/agent/util"
"github.com/vmware-tanzu/antrea/pkg/ovs/ovsctl"
)

Expand All @@ -47,13 +50,24 @@ type tracingPeer struct {
ip net.IP
}

func (p *tracingPeer) getAddressFamily() uint8 {
if p.ip == nil {
return 0
}
if p.ip.To4() != nil {
return util.FamilyIPv4
}
return util.FamilyIPv6
}

type request struct {
// tracingPeer.ip is invalid for inputPort, as inputPort can only be
// specified by ovsPort or Pod Namespace/name.
inputPort *tracingPeer
source *tracingPeer
destination *tracingPeer
flow string
addrFamily uint8
}

func getServiceClusterIP(aq querier.AgentQuerier, name, namespace string) (net.IP, *handlers.HandlerError) {
Expand All @@ -68,28 +82,42 @@ func getServiceClusterIP(aq querier.AgentQuerier, name, namespace string) (net.I
return net.ParseIP(srv.Spec.ClusterIP).To4(), nil
}

// getPeerAddress looks up a Pod and returns its IP and MAC addresses. It
// first looks up the Pod from the InterfaceStore, and returns the Pod's IP and
// MAC addresses if found. If fails, it then gets the Pod from Kubernetes API,
// and returns the IP address in Pod resource Status if found.
func getPeerAddress(aq querier.AgentQuerier, peer *tracingPeer) (net.IP, *interfacestore.InterfaceConfig, *handlers.HandlerError) {
if peer.ip != nil {
return peer.ip, nil, nil
}

func getLocalOVSInterface(aq querier.AgentQuerier, peer *tracingPeer) (*interfacestore.InterfaceConfig, *handlers.HandlerError) {
if peer.ovsPort != "" {
intf, ok := aq.GetInterfaceStore().GetInterfaceByName(peer.ovsPort)
if !ok {
err := handlers.NewHandlerError(fmt.Errorf("OVS port %s not found", peer.ovsPort), http.StatusNotFound)
return nil, nil, err
return nil, err
}
return intf.GetIPv4Addr(), intf, nil
return intf, nil
}

interfaces := aq.GetInterfaceStore().GetContainerInterfacesByPod(peer.name, peer.namespace)
if len(interfaces) > 0 {
// Local Pod.
return interfaces[0].GetIPv4Addr(), interfaces[0], nil
return interfaces[0], nil
}

return nil, nil
}

// getPeerAddress looks up a Pod and returns its IP and MAC addresses. It
// first looks up the Pod from the InterfaceStore, and returns the Pod's IP and
// MAC addresses if found. If fails, it then gets the Pod from Kubernetes API,
// and returns the IP address in Pod resource Status if found.
func getPeerAddress(aq querier.AgentQuerier, peer *tracingPeer, addrFamily uint8) (net.IP, *interfacestore.InterfaceConfig, *handlers.HandlerError) {
if peer.ip != nil {
return peer.ip, nil, nil
}

if intf, err := getLocalOVSInterface(aq, peer); err != nil {
return nil, nil, err
} else if intf != nil {
ipAddr, err := util.GetIPWithFamily(intf.IPs, addrFamily)
if err != nil {
return nil, nil, handlers.NewHandlerError(err, http.StatusNotFound)
}
return ipAddr, intf, nil
}

// Try getting the Pod from K8s API.
Expand All @@ -103,7 +131,21 @@ func getPeerAddress(aq querier.AgentQuerier, peer *tracingPeer) (net.IP, *interf
return nil, nil, handlers.NewHandlerError(errors.New("Kubernetes API error"), http.StatusInternalServerError)
}
// Return IP only assuming it should be a remote Pod.
return net.ParseIP(pod.Status.PodIP).To4(), nil, nil
podIP, err := getPodIPWithAddressFamily(pod, addrFamily)
if err != nil {
return nil, nil, handlers.NewHandlerError(err, http.StatusNotFound)
}
return podIP, nil, nil
}

func getPodIPWithAddressFamily(pod *corev1.Pod, addrFamily uint8) (net.IP, error) {
podIPs := []net.IP{net.ParseIP(pod.Status.PodIP)}
if len(pod.Status.PodIPs) > 0 {
for _, podIP := range pod.Status.PodIPs {
podIPs = append(podIPs, net.ParseIP(podIP.IP))
}
}
return util.GetIPWithFamily(podIPs, addrFamily)
}

func prepareTracingRequest(aq querier.AgentQuerier, req *request) (*ovsctl.TracingRequest, *handlers.HandlerError) {
Expand Down Expand Up @@ -131,7 +173,7 @@ func prepareTracingRequest(aq querier.AgentQuerier, req *request) (*ovsctl.Traci
}

if req.source != nil {
ip, intf, err := getPeerAddress(aq, req.source)
ip, intf, err := getPeerAddress(aq, req.source, req.addrFamily)
if err != nil {
return nil, err
}
Expand All @@ -148,7 +190,7 @@ func prepareTracingRequest(aq querier.AgentQuerier, req *request) (*ovsctl.Traci

gatewayConfig := aq.GetNodeConfig().GatewayConfig
if req.destination != nil {
ip, intf, err := getPeerAddress(aq, req.destination)
ip, intf, err := getPeerAddress(aq, req.destination, req.addrFamily)
if err != nil && err.HTTPStatusCode == http.StatusNotFound && req.destination.name != "" {
// The destination might be a Service.
ip, err = getServiceClusterIP(aq, req.destination.name, req.destination.namespace)
Expand Down Expand Up @@ -210,9 +252,9 @@ func prepareTracingRequest(aq querier.AgentQuerier, req *request) (*ovsctl.Traci
}

// parseTracingPeer parses Pod/Service name and Namespace or OVS port name or
// IPv4 address from the string. nil is returned if the string is not of a
// IP address from the string. nil is returned if the string is not of a
// valid Pod/Service reference ("Namespace/name") or OVS port name format, and
// not an IPv4 address.
// not an IP address.
func parseTracingPeer(str string) *tracingPeer {
parts := strings.Split(str, "/")
n := len(parts)
Expand All @@ -232,10 +274,7 @@ func parseTracingPeer(str string) *tracingPeer {
// Probably an OVS port name.
return &tracingPeer{ovsPort: str}
}
// Do not support IPv6 address.
if ip.To4() != nil {
return &tracingPeer{ip: ip}
}
return &tracingPeer{ip: ip}
}
return nil
}
Expand All @@ -244,8 +283,33 @@ func validateRequest(r *http.Request) (*request, *handlers.HandlerError) {
port := r.URL.Query().Get("port")
src := r.URL.Query().Get("source")
dst := r.URL.Query().Get("destination")
addrFamily := r.URL.Query().Get("addressFamily")

request := request{flow: r.URL.Query().Get("flow")}
if addrFamily == "6" {
request.addrFamily = util.FamilyIPv6
} else if addrFamily == "4" {
request.addrFamily = util.FamilyIPv4
} else if request.flow != "" {
found := false
for _, s := range ovsctl.IPAndNWProtos {
if strings.Contains(request.flow, s) {
if strings.HasSuffix(s, "6") {
request.addrFamily = util.FamilyIPv6
} else {
request.addrFamily = util.FamilyIPv4
}
found = true
break
}
}
if !found {
request.addrFamily = util.FamilyIPv4
}
} else {
request.addrFamily = util.FamilyIPv4
}

if port != "" {
request.inputPort = parseTracingPeer(port)
// Input port cannot be specified with an IP.
Expand All @@ -258,12 +322,20 @@ func validateRequest(r *http.Request) (*request, *handlers.HandlerError) {
if request.source == nil {
return nil, handlers.NewHandlerError(errors.New("invalid source format"), http.StatusBadRequest)
}
srcAddrFamily := request.source.getAddressFamily()
if srcAddrFamily != 0 && srcAddrFamily != request.addrFamily {
return nil, handlers.NewHandlerError(errors.New("address family incompatible between source and request"), http.StatusBadRequest)
}
}
if dst != "" {
request.destination = parseTracingPeer(dst)
if request.destination == nil {
return nil, handlers.NewHandlerError(errors.New("invalid destination format"), http.StatusBadRequest)
}
dstAddrFamily := request.destination.getAddressFamily()
if dstAddrFamily != 0 && dstAddrFamily != request.destination.getAddressFamily() {
return nil, handlers.NewHandlerError(errors.New("address family incompatible between destination and request"), http.StatusBadRequest)
}
}
return &request, nil
}
Expand Down
22 changes: 19 additions & 3 deletions pkg/agent/apiserver/handlers/ovstracing/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var (
inPodInterface = &interfacestore.InterfaceConfig{
Type: interfacestore.ContainerInterface,
InterfaceName: "inPod",
IPs: []net.IP{net.ParseIP("10.1.1.11")},
IPs: []net.IP{net.ParseIP("10.1.1.11"), net.ParseIP("2001:0db8::ff00:42:11")},
MAC: podMAC,
}
srcPodInterface = &interfacestore.InterfaceConfig{
Expand All @@ -67,7 +67,7 @@ var (
dstPodInterface = &interfacestore.InterfaceConfig{
Type: interfacestore.ContainerInterface,
InterfaceName: "dstPod",
IPs: []net.IP{net.ParseIP("10.1.1.13")},
IPs: []net.IP{net.ParseIP("10.1.1.13"), net.ParseIP("2001:0db8::ff00:42:13")},
MAC: podMAC,
}
)
Expand Down Expand Up @@ -107,7 +107,7 @@ func TestPodFlows(t *testing.T) {
expectedStatus: http.StatusBadRequest,
},
{
test: "IPv6 source",
test: "IPv6 source conflict",
query: "?source=2001:0db8:0000:0000:0000:ff00:0042:8329",
expectedStatus: http.StatusBadRequest,
},
Expand Down Expand Up @@ -148,6 +148,13 @@ func TestPodFlows(t *testing.T) {
calledTrace: true,
expectedStatus: http.StatusBadRequest,
},
{
test: "Non-existing source IPv6 address",
port: "srcPod",
query: "?addressFamily=6&&source=srcNS/srcPod&&destination=dstNS/dstPod",
calledTrace: false,
expectedStatus: http.StatusNotFound,
},
{
test: "Default command",
calledTrace: true,
Expand All @@ -160,6 +167,13 @@ func TestPodFlows(t *testing.T) {
calledTrace: true,
expectedStatus: http.StatusOK,
},
{
test: "Pod-to-Pod IPv6 traffic",
port: "pod",
query: "?addressFamily=6&&port=inNS/inPod&&destination=dstNS/dstPod",
calledTrace: true,
expectedStatus: http.StatusOK,
},
{
test: "Tunnel traffic",
port: "antrea-tun0",
Expand All @@ -186,6 +200,8 @@ func TestPodFlows(t *testing.T) {
q.EXPECT().GetInterfaceStore().Return(i).Times(1)
if tc.port == "pod" {
i.EXPECT().GetContainerInterfacesByPod("inPod", "inNS").Return(nil).Times(1)
} else if tc.port == "srcPod" {
i.EXPECT().GetContainerInterfacesByPod("srcPod", "srcNS").Return([]*interfacestore.InterfaceConfig{srcPodInterface}).Times(1)
} else {
i.EXPECT().GetInterfaceByName(tc.port).Return(nil, false).Times(1)
}
Expand Down
12 changes: 11 additions & 1 deletion pkg/agent/apiserver/handlers/podinterface/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package podinterface

import (
"encoding/json"
"net"
"net/http"
"strings"

"github.com/vmware-tanzu/antrea/pkg/agent/interfacestore"
"github.com/vmware-tanzu/antrea/pkg/agent/querier"
Expand All @@ -40,14 +42,22 @@ func generateResponse(i *interfacestore.InterfaceConfig) Response {
PodName: i.ContainerInterfaceConfig.PodName,
PodNamespace: i.ContainerInterfaceConfig.PodNamespace,
InterfaceName: i.InterfaceName,
IP: i.GetIPv4Addr().String(),
IP: getPodIPsStr(i.IPs),
MAC: i.MAC.String(),
PortUUID: i.OVSPortConfig.PortUUID,
OFPort: i.OVSPortConfig.OFPort,
ContainerID: i.ContainerInterfaceConfig.ContainerID,
}
}

func getPodIPsStr(ips []net.IP) string {
ipStrs := make([]string, len(ips))
for i := range ips {
ipStrs[i] = ips[i].String()
}
return strings.Join(ipStrs, ", ")
}

// HandleFunc returns the function which can handle queries issued by the pod-interface command,
func HandleFunc(aq querier.AgentQuerier) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/agent/querier/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ func (aq agentQuerier) GetAgentInfo(agentInfo *v1beta1.AntreaAgentInfo, partial
if aq.nodeConfig.PodIPv4CIDR != nil {
agentInfo.NodeSubnet = append(agentInfo.NodeSubnet, aq.nodeConfig.PodIPv4CIDR.String())
}
if aq.nodeConfig.PodIPv6CIDR != nil {
agentInfo.NodeSubnet = append(agentInfo.NodeSubnet, aq.nodeConfig.PodIPv6CIDR.String())
}
agentInfo.OVSInfo.BridgeName = aq.nodeConfig.OVSBridge
agentInfo.APIPort = aq.apiPort
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/agent/querier/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,14 @@ func TestAgentQuerierGetAgentInfo(t *testing.T) {
OVSBridge: "br-int",
NodeIPAddr: getIPNet("10.10.0.10"),
PodIPv4CIDR: getIPNet("20.20.20.0/24"),
PodIPv6CIDR: getIPNet("2001:ab03:cd04:55ef::/64"),
},
apiPort: 10350,
partial: false,
expectedAgentInfo: &v1beta1.AntreaAgentInfo{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
NodeRef: corev1.ObjectReference{Kind: "Node", Name: "foo"},
NodeSubnet: []string{"20.20.20.0/24"},
NodeSubnet: []string{"20.20.20.0/24", "2001:ab03:cd04:55ef::/64"},
OVSInfo: v1beta1.OVSInfo{
Version: ovsVersion,
BridgeName: "br-int",
Expand Down
22 changes: 22 additions & 0 deletions pkg/agent/util/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package util
import (
"crypto/sha1" // #nosec G505: not used for security purposes
"encoding/hex"
"errors"
"fmt"
"io"
"net"
Expand All @@ -26,6 +27,9 @@ const (
interfaceNameLength = 15
interfacePrefixLength = 8
interfaceKeyLength = interfaceNameLength - (interfacePrefixLength + 1)

FamilyIPv4 uint8 = 4
FamilyIPv6 uint8 = 6
)

func generateInterfaceName(key string, name string, useHead bool) string {
Expand Down Expand Up @@ -136,3 +140,21 @@ func ContainIPv6Addr(ips []net.IP) bool {
}
return false
}

func GetIPWithFamily(ips []net.IP, addrFamily uint8) (net.IP, error) {
if addrFamily == FamilyIPv6 {
for _, ip := range ips {
if ip.To4() == nil {
return ip, nil
}
}
return nil, errors.New("no IP found with the specified AddressFamily")
} else {
for _, ip := range ips {
if ip.To4() != nil {
return ip, nil
}
}
return nil, errors.New("no IP found with the specified AddressFamily")
}
}
Loading

0 comments on commit b4d1470

Please sign in to comment.