Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Aug 28, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
wenyingd marked this conversation as resolved.
Show resolved Hide resolved
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 the specified AddressFamily")
wenyingd marked this conversation as resolved.
Show resolved Hide resolved
} else {
for _, ip := range ips {
if ip.To4() != nil {
return ip, nil
}
}
return nil, errors.New("no IP found with the specified AddressFamily")
wenyingd marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading