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 20, 2020
1 parent d14389c commit 63dcc24
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 47 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
118 changes: 96 additions & 22 deletions pkg/agent/apiserver/handlers/ovstracing/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ 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"
Expand All @@ -47,13 +48,24 @@ type tracingPeer struct {
ip net.IP
}

func (p *tracingPeer) getAddressFamily() uint8 {
if p.ip == nil {
return 0
}
if p.ip.To4() != nil {
return ovsctl.FamilyIPv4
}
return ovsctl.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 +80,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 := 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,11 +129,43 @@ 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 getIPWithFamily(podIPs, addrFamily)
}

func getIPWithFamily(ips []net.IP, addrFamily uint8) (net.IP, error) {
if addrFamily == ovsctl.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")
}
}

func prepareTracingRequest(aq querier.AgentQuerier, req *request) (*ovsctl.TracingRequest, *handlers.HandlerError) {
traceReq := ovsctl.TracingRequest{Flow: req.flow, AllowOverrideInPort: false}
traceReq := ovsctl.TracingRequest{Flow: req.flow, AllowOverrideInPort: false, AddrFamily: req.addrFamily}

var inPort *interfacestore.InterfaceConfig
if req.inputPort != nil {
Expand All @@ -131,7 +189,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 +206,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 +268,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
// IPvaddress 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 +290,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 +299,19 @@ 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 = ovsctl.FamilyIPv6
} else if addrFamily == "4" {
request.addrFamily = ovsctl.FamilyIPv4
} else if strings.Contains(request.flow, "ipv6") {
request.addrFamily = ovsctl.FamilyIPv6
} else {
request.addrFamily = ovsctl.FamilyIPv4
}

if port != "" {
request.inputPort = parseTracingPeer(port)
// Input port cannot be specified with an IP.
Expand All @@ -258,12 +324,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
9 changes: 7 additions & 2 deletions pkg/antctl/antctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,19 +298,24 @@ var CommandList = &commandList{
},
{
name: "source",
usage: "Source of the packet. Can be an OVS port name, or a (local or remote) Pod (specified by <Namespace>/<name>), or an IP address. If specified, the source's IP addresss will be used as the tracing packet's source IP address, and the 'nw_src' field should not be added in the 'flow' argument.",
usage: "Source of the packet. Can be an OVS port name, or a (local or remote) Pod (specified by <Namespace>/<name>), or an IP address. If specified, the source's IP address will be used as the tracing packet's source IP address, and the 'nw_src'/'ipv6_src' field should not be added in the 'flow' argument.",
shorthand: "S",
},
{
name: "destination",
usage: "Destination of the packet. Can be an OVS port name, or a (local or remote) Pod or a Service (specified by <Namespace>/<name>), or an IP address. If specified, the destination's IP address (the ClusterIP for a Service) will be used as the tacing packet's destination IP address, and the 'nw_dst' field should not be added in the 'flow' argument.",
usage: "Destination of the packet. Can be an OVS port name, or a (local or remote) Pod or a Service (specified by <Namespace>/<name>), or an IP address. If specified, the destination's IP address (the ClusterIP for a Service) will be used as the tacing packet's destination IP address, and the 'nw_dst'/'ipv6_dst' field should not be added in the 'flow' argument.",
shorthand: "D",
},
{
name: "flow",
usage: "Specify the flow (packet headers) of the tracing packet. Check the flow syntax descriptions in ovs-ofctl(8) manpage.",
shorthand: "f",
},
{
name: "addressFamily",
usage: "Specify the address family fo the packet. Can be 4 (IPv4) or 6 (IPv6). If not specified, the addressFamily will be automatically figured out based on the 'flow'. If no IP address or address family is given in the 'flow', IPv4 is used by default.",
shorthand: "F",
},
},
outputType: single,
},
Expand Down
Loading

0 comments on commit 63dcc24

Please sign in to comment.