Skip to content

Commit

Permalink
Add IPv6 multicast support.
Browse files Browse the repository at this point in the history
Enable MLD Snoop and MLD relay to allow multicast connectivity across
nodes. Extend the IPv4 multicast network policies to make them
applicable to IPv6.

Signed-off-by: Dumitru Ceara <dceara@redhat.com>
  • Loading branch information
dceara committed Dec 21, 2020
1 parent 3864f2b commit cbc7294
Show file tree
Hide file tree
Showing 4 changed files with 383 additions and 205 deletions.
49 changes: 30 additions & 19 deletions go-controller/pkg/ovn/master.go
Expand Up @@ -297,10 +297,6 @@ func (oc *Controller) StartClusterMaster(masterNodeName string) error {
"Disabling Multicast Support")
oc.multicastSupport = false
}
if !config.IPv4Mode {
klog.Warningf("Multicast support enabled, but can not be used with single-stack IPv6. Disabling Multicast Support")
oc.multicastSupport = false
}
}

if err := oc.SetupMaster(masterNodeName); err != nil {
Expand Down Expand Up @@ -664,12 +660,14 @@ func (oc *Controller) ensureNodeLogicalNetwork(nodeName string, hostSubnets []*n
"--", "set", "logical_switch", nodeName,
}

var v4Gateway net.IP
var v4Gateway, v6Gateway net.IP
for _, hostSubnet := range hostSubnets {
gwIfAddr := util.GetNodeGatewayIfAddr(hostSubnet)
lrpArgs = append(lrpArgs, gwIfAddr.String())

if utilnet.IsIPv6CIDR(hostSubnet) {
v6Gateway = gwIfAddr.IP

lsArgs = append(lsArgs,
"other-config:ipv6_prefix="+hostSubnet.IP.String(),
)
Expand Down Expand Up @@ -703,7 +701,7 @@ func (oc *Controller) ensureNodeLogicalNetwork(nodeName string, hostSubnets []*n
return err
}

// If supported, enable IGMP snooping and querier on the node.
// If supported, enable IGMP/MLD snooping and querier on the node.
if oc.multicastSupport {
stdout, stderr, err = util.RunOVNNbctl("set", "logical_switch",
nodeName, "other-config:mcast_snoop=\"true\"")
Expand All @@ -713,27 +711,40 @@ func (oc *Controller) ensureNodeLogicalNetwork(nodeName string, hostSubnets []*n
return err
}

// Configure querier only if we have an IPv4 address, otherwise
// disable querier.
if v4Gateway != nil {
stdout, stderr, err = util.RunOVNNbctl("set", "logical_switch",
nodeName, "other-config:mcast_querier=\"true\"",
"other-config:mcast_eth_src=\""+nodeLRPMAC.String()+"\"",
"other-config:mcast_ip4_src=\""+v4Gateway.String()+"\"")
if err != nil {
klog.Errorf("Failed to enable IGMP Querier on logical switch %v, stdout: %q, stderr: %q, error: %v",
nodeName, stdout, stderr, err)
return err
// Configure IGMP/MLD querier if the gateway IP address is known.
// Otherwise disable it.
if v4Gateway != nil || v6Gateway != nil {
if v4Gateway != nil {
stdout, stderr, err = util.RunOVNNbctl("set", "logical_switch",
nodeName, "other-config:mcast_querier=\"true\"",
"other-config:mcast_eth_src=\""+nodeLRPMAC.String()+"\"",
"other-config:mcast_ip4_src=\""+v4Gateway.String()+"\"")
if err != nil {
klog.Errorf("Failed to enable IGMP Querier on logical switch %v, stdout: %q, stderr: %q, error: %v",
nodeName, stdout, stderr, err)
return err
}
}
if v6Gateway != nil {
stdout, stderr, err = util.RunOVNNbctl("set", "logical_switch",
nodeName, "other-config:mcast_querier=\"true\"",
"other-config:mcast_eth_src=\""+nodeLRPMAC.String()+"\"",
"other-config:mcast_ip6_src=\""+v6Gateway.String()+"\"")
if err != nil {
klog.Errorf("Failed to enable MLD Querier on logical switch %v, stdout: %q, stderr: %q, error: %v",
nodeName, stdout, stderr, err)
return err
}
}
} else {
stdout, stderr, err = util.RunOVNNbctl("set", "logical_switch",
nodeName, "other-config:mcast_querier=\"false\"")
if err != nil {
klog.Errorf("Failed to disable IGMP Querier on logical switch %v, stdout: %q, stderr: %q, error: %v",
klog.Errorf("Failed to disable IGMP/MLD Querier on logical switch %v, stdout: %q, stderr: %q, error: %v",
nodeName, stdout, stderr, err)
return err
}
klog.Infof("Disabled IGMP Querier on logical switch %v (No IPv4 Source IP available)",
klog.Infof("Disabled IGMP/MLD Querier on logical switch %v (No IPv4/IPv6 Source IP available)",
nodeName)
}
}
Expand Down
8 changes: 4 additions & 4 deletions go-controller/pkg/ovn/master_test.go
Expand Up @@ -143,10 +143,10 @@ func defaultFakeExec(nodeSubnet, nodeName string, sctpSupport bool) (*ovntest.Fa
"ovn-nbctl --timeout=15 create port_group name=clusterPortGroup external-ids:name=clusterPortGroup",
"ovn-nbctl --timeout=15 --data=bare --no-heading --columns=_uuid find port_group name=clusterRtrPortGroup",
"ovn-nbctl --timeout=15 create port_group name=clusterRtrPortGroup external-ids:name=clusterRtrPortGroup",
"ovn-nbctl --timeout=15 --data=bare --no-heading --columns=_uuid find ACL match=\"ip4.mcast\" action=drop external-ids:default-deny-policy-type=Egress",
"ovn-nbctl --timeout=15 --id=@acl create acl priority=1011 direction=from-lport match=\"ip4.mcast\" action=drop external-ids:default-deny-policy-type=Egress -- add port_group acls @acl",
"ovn-nbctl --timeout=15 --data=bare --no-heading --columns=_uuid find ACL match=\"ip4.mcast\" action=drop external-ids:default-deny-policy-type=Ingress",
"ovn-nbctl --timeout=15 --id=@acl create acl priority=1011 direction=to-lport match=\"ip4.mcast\" action=drop external-ids:default-deny-policy-type=Ingress -- add port_group acls @acl",
"ovn-nbctl --timeout=15 --data=bare --no-heading --columns=_uuid find ACL match=\"(ip4.mcast || mldv1 || mldv2 || " + ipv6DynamicMulticastMatch + ")\" action=drop external-ids:default-deny-policy-type=Egress",
"ovn-nbctl --timeout=15 --id=@acl create acl priority=1011 direction=from-lport match=\"(ip4.mcast || mldv1 || mldv2 || " + ipv6DynamicMulticastMatch + ")\" action=drop external-ids:default-deny-policy-type=Egress -- add port_group acls @acl",
"ovn-nbctl --timeout=15 --data=bare --no-heading --columns=_uuid find ACL match=\"(ip4.mcast || mldv1 || mldv2 || " + ipv6DynamicMulticastMatch + ")\" action=drop external-ids:default-deny-policy-type=Ingress",
"ovn-nbctl --timeout=15 --id=@acl create acl priority=1011 direction=to-lport match=\"(ip4.mcast || mldv1 || mldv2 || " + ipv6DynamicMulticastMatch + ")\" action=drop external-ids:default-deny-policy-type=Ingress -- add port_group acls @acl",
})
fexec.AddFakeCmd(&ovntest.ExpectedCmd{
Cmd: "ovn-nbctl --timeout=15 --data=bare --no-heading --columns=_uuid find load_balancer external_ids:k8s-cluster-lb-tcp=yes",
Expand Down
73 changes: 61 additions & 12 deletions go-controller/pkg/ovn/policy.go
Expand Up @@ -5,6 +5,7 @@ import (
"net"
"sync"

"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
kapi "k8s.io/api/core/v1"
Expand Down Expand Up @@ -48,6 +49,9 @@ const (
toLport = "to-lport"
fromLport = "from-lport"
noneMatch = "None"
// IPv6 multicast traffic destined to dynamic groups must have the "T" bit
// set to 1: https://tools.ietf.org/html/rfc3307#section-4.3
ipv6DynamicMulticastMatch = "(ip6.dst[120..127] == 0xff && ip6.dst[116] == 1)"
// Default deny acl rule priority
defaultDenyPriority = "1000"
// Default allow acl rule priority
Expand Down Expand Up @@ -239,11 +243,54 @@ func (oc *Controller) createDefaultDenyPortGroup(policyType knet.PolicyType) err
return nil
}

func getACLMatchAF(ipv4Match, ipv6Match string) string {
if config.IPv4Mode && config.IPv6Mode {
return "(" + ipv4Match + " || " + ipv6Match + ")"
} else if config.IPv4Mode {
return ipv4Match
} else {
return ipv6Match
}
}

// Creates the match string used for ACLs matching on multicast traffic.
func getMulticastACLMatch() string {
return "(ip4.mcast || mldv1 || mldv2 || " + ipv6DynamicMulticastMatch + ")"
}

func getMulticastACLIgrMatchV4(addrSetName string) string {
return "ip4.src == $" + addrSetName + " && ip4.mcast"
}

func getMulticastACLIgrMatchV6(addrSetName string) string {
return "ip6.src == $" + addrSetName + " && " + ipv6DynamicMulticastMatch
}

// Creates the match string used for ACLs allowing incoming multicast into a
// namespace, that is, from IPs that are in the namespace's address set.
func getMulticastACLMatch(nsInfo *namespaceInfo) string {
ipv4HashedAS, _ := nsInfo.addressSet.GetASHashNames()
return "ip4.src == $" + ipv4HashedAS + " && ip4.mcast"
func getMulticastACLIgrMatch(nsInfo *namespaceInfo) string {
var ipv4Match, ipv6Match string
addrSetNameV4, addrSetNameV6 := nsInfo.addressSet.GetASHashNames()
if config.IPv4Mode {
ipv4Match = getMulticastACLIgrMatchV4(addrSetNameV4)
}
if config.IPv6Mode {
ipv6Match = getMulticastACLIgrMatchV6(addrSetNameV6)
}
return getACLMatchAF(ipv4Match, ipv6Match)
}

// Creates the match string used for ACLs allowing outgoing multicast from a
// namespace.
func getMulticastACLEgrMatch() string {
var ipv4Match, ipv6Match string
if config.IPv4Mode {
ipv4Match = "ip4.mcast"
}
if config.IPv6Mode {
ipv6Match = "(mldv1 || mldv2 || " + ipv6DynamicMulticastMatch + ")"
}
return getACLMatchAF(ipv4Match, ipv6Match)
}

// Creates a policy to allow multicast traffic within 'ns':
Expand All @@ -260,15 +307,17 @@ func (oc *Controller) createMulticastAllowPolicy(ns string, nsInfo *namespaceInf
}

portGroupName := hashedPortGroup(ns)
match := getACLMatch(portGroupName, "ip4.mcast", knet.PolicyTypeEgress)
match := getACLMatch(portGroupName, getMulticastACLEgrMatch(),
knet.PolicyTypeEgress)
err = addACLPortGroup(nsInfo.portGroupUUID, fromLport,
defaultMcastAllowPriority, match, "allow", knet.PolicyTypeEgress)
if err != nil {
return fmt.Errorf("failed to create allow egress multicast ACL for %s (%v)",
ns, err)
}

match = getACLMatch(portGroupName, getMulticastACLMatch(nsInfo), knet.PolicyTypeIngress)
match = getACLMatch(portGroupName, getMulticastACLIgrMatch(nsInfo),
knet.PolicyTypeIngress)
err = addACLPortGroup(nsInfo.portGroupUUID, toLport,
defaultMcastAllowPriority, match, "allow", knet.PolicyTypeIngress)
if err != nil {
Expand All @@ -295,16 +344,15 @@ func (oc *Controller) createMulticastAllowPolicy(ns string, nsInfo *namespaceInf

func deleteMulticastACLs(ns, portGroupHash string, nsInfo *namespaceInfo) error {
err := deleteACLPortGroup(portGroupHash, fromLport,
defaultMcastAllowPriority, "ip4.mcast", "allow",
defaultMcastAllowPriority, getMulticastACLEgrMatch(), "allow",
knet.PolicyTypeEgress)
if err != nil {
return fmt.Errorf("failed to delete allow egress multicast ACL for %s (%v)",
ns, err)
}

err = deleteACLPortGroup(portGroupHash, toLport,
defaultMcastAllowPriority, getMulticastACLMatch(nsInfo), "allow",
knet.PolicyTypeIngress)
err = deleteACLPortGroup(portGroupHash, toLport, defaultMcastAllowPriority,
getMulticastACLIgrMatch(nsInfo), "allow", knet.PolicyTypeIngress)
if err != nil {
return fmt.Errorf("failed to delete allow ingress multicast ACL for %s (%v)",
ns, err)
Expand Down Expand Up @@ -336,7 +384,7 @@ func (oc *Controller) createDefaultDenyMulticastPolicy() error {
// By default deny any egress multicast traffic from any pod. This drops
// IP multicast membership reports therefore denying any multicast traffic
// to be forwarded to pods.
match := "match=\"ip4.mcast\""
match := "match=\"" + getMulticastACLMatch() + "\""
err := addACLPortGroup(oc.clusterPortGroupUUID, fromLport,
defaultMcastDenyPriority, match, "drop", knet.PolicyTypeEgress)
if err != nil {
Expand All @@ -361,14 +409,15 @@ func (oc *Controller) createDefaultDenyMulticastPolicy() error {
// - one ACL allowing multicast traffic to cluster router ports.
// Caller must hold the namespace's namespaceInfo object lock.
func (oc *Controller) createDefaultAllowMulticastPolicy() error {
match := getACLMatch(clusterRtrPortGroupName, "ip4.mcast", knet.PolicyTypeEgress)
mcastMatch := getMulticastACLMatch()
match := getACLMatch(clusterRtrPortGroupName, mcastMatch, knet.PolicyTypeEgress)
err := addACLPortGroup(oc.clusterRtrPortGroupUUID, fromLport,
defaultRoutedMcastAllowPriority, match, "allow", knet.PolicyTypeEgress)
if err != nil {
return fmt.Errorf("failed to create default deny multicast egress ACL: %v", err)
}

match = getACLMatch(clusterRtrPortGroupName, "ip4.mcast", knet.PolicyTypeIngress)
match = getACLMatch(clusterRtrPortGroupName, mcastMatch, knet.PolicyTypeIngress)
err = addACLPortGroup(oc.clusterRtrPortGroupUUID, toLport,
defaultRoutedMcastAllowPriority, match, "allow", knet.PolicyTypeIngress)
if err != nil {
Expand Down

0 comments on commit cbc7294

Please sign in to comment.