Skip to content

Commit

Permalink
[IPv6] Add support for IPv6 address in antctl and agent's apiserver (a…
Browse files Browse the repository at this point in the history
…ntrea-io#1118)

* [IPv6] Add support for IPv6 address in antctl and agent's apiserver

1. Support using IPv6 address in OVS tracing.
2. Support displaying Node's and Pod's IPv6 address in agent apiserver.

Co-authored-by: Zhecheng Li <[email protected]>
  • Loading branch information
2 people authored and lzhecheng committed Nov 5, 2020
1 parent d53062b commit 6974b91
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 45 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
116 changes: 94 additions & 22 deletions pkg/agent/apiserver/handlers/ovstracing/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ import (
"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 +49,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 +81,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 +130,22 @@ 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
}

// Todo: move this function to pkg/agent/util/net.go if it is called by other code
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 All @@ -122,7 +164,7 @@ func prepareTracingRequest(aq querier.AgentQuerier, req *request) (*ovsctl.Traci
}
}
if !ok {
return nil, handlers.NewHandlerError(errors.New("Input port not found"), http.StatusNotFound)
return nil, handlers.NewHandlerError(errors.New("input port not found"), http.StatusNotFound)
}
} else {
// Input port is not specified. Allow "in_port" field in "Flow" to override
Expand All @@ -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 IPv6 AddressFamily")
} else {
for _, ip := range ips {
if ip.To4() != nil {
return ip, nil
}
}
return nil, errors.New("no IP found with IPv4 AddressFamily")
}
}
Loading

0 comments on commit 6974b91

Please sign in to comment.