From 7dc88496081cccc0b22e3e341d88d2e4867323df Mon Sep 17 00:00:00 2001 From: Numan Siddique Date: Mon, 22 Feb 2016 15:59:37 +0530 Subject: [PATCH] ovn-northd: Allow lport 'addresses' to store multiple ips in each set If a logical port has two ipv4 addresses and one ipv6 address it will be stored as ["MAC IPv41 IPv42 IPv61"] instead of ["MAC IPv41", "MAC IPv42", "MAC IPv61"]. Signed-off-by: Numan Siddique [blp@ovn.org made changes to comments and ovn.at] Signed-off-by: Ben Pfaff --- lib/packets.c | 123 +++++++++++++++++++++++++--------- lib/packets.h | 10 +++ ovn/northd/ovn-northd.c | 145 +++++++++++++++++++++++++++++++++++----- ovn/ovn-nb.xml | 56 +++++++++++----- tests/ovn.at | 24 ++++++- 5 files changed, 296 insertions(+), 62 deletions(-) diff --git a/lib/packets.c b/lib/packets.c index d82341d71..daca1b3dd 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -425,29 +425,29 @@ ip_parse(const char *s, ovs_be32 *ip) } /* Parses string 's', which must be an IP address with an optional netmask or - * CIDR prefix length. Stores the IP address into '*ip' and the netmask into - * '*mask'. (If 's' does not contain a netmask, 255.255.255.255 is - * assumed.) + * CIDR prefix length. Stores the IP address into '*ip', netmask into '*mask', + * (255.255.255.255, if 's' lacks a netmask), and number of scanned characters + * into '*n'. * * Returns NULL if successful, otherwise an error message that the caller must * free(). */ char * OVS_WARN_UNUSED_RESULT -ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask) +ip_parse_masked_len(const char *s, int *n, ovs_be32 *ip, + ovs_be32 *mask) { int prefix; - int n; - if (ovs_scan(s, IP_SCAN_FMT"/"IP_SCAN_FMT"%n", - IP_SCAN_ARGS(ip), IP_SCAN_ARGS(mask), &n) && !s[n]) { + if (ovs_scan_len(s, n, IP_SCAN_FMT"/"IP_SCAN_FMT, + IP_SCAN_ARGS(ip), IP_SCAN_ARGS(mask))) { /* OK. */ - } else if (ovs_scan(s, IP_SCAN_FMT"/%d%n", IP_SCAN_ARGS(ip), &prefix, &n) - && !s[n]) { + } else if (ovs_scan_len(s, n, IP_SCAN_FMT"/%d", + IP_SCAN_ARGS(ip), &prefix)) { if (prefix <= 0 || prefix > 32) { return xasprintf("%s: network prefix bits not between 0 and " "32", s); } *mask = be32_prefix_mask(prefix); - } else if (ip_parse(s, ip)) { + } else if (ovs_scan_len(s, n, IP_SCAN_FMT, IP_SCAN_ARGS(ip))) { *mask = OVS_BE32_MAX; } else { return xasprintf("%s: invalid IP address", s); @@ -455,15 +455,33 @@ ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask) return NULL; } -/* Similar to ip_parse_masked(), but the mask, if present, must be a CIDR mask - * and is returned as a prefix length in '*plen'. */ +/* This function is similar to ip_parse_masked_len(), but doesn't return the + * number of scanned characters and expects 's' to end after the ip/(optional) + * mask. + * + * Returns NULL if successful, otherwise an error message that the caller must + * free(). */ char * OVS_WARN_UNUSED_RESULT -ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen) +ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask) +{ + int n = 0; + + char *error = ip_parse_masked_len(s, &n, ip, mask); + if (!error && s[n]) { + return xasprintf("%s: invalid IP address", s); + } + return error; +} + +/* Similar to ip_parse_masked_len(), but the mask, if present, must be a CIDR + * mask and is returned as a prefix len in '*plen'. */ +char * OVS_WARN_UNUSED_RESULT +ip_parse_cidr_len(const char *s, int *n, ovs_be32 *ip, unsigned int *plen) { ovs_be32 mask; char *error; - error = ip_parse_masked(s, ip, &mask); + error = ip_parse_masked_len(s, n, ip, &mask); if (error) { return error; } @@ -475,6 +493,21 @@ ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen) return NULL; } +/* Similar to ip_parse_cidr_len(), but doesn't return the number of scanned + * characters and expects 's' to be NULL terminated at the end of the + * ip/(optional) cidr. */ +char * OVS_WARN_UNUSED_RESULT +ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen) +{ + int n = 0; + + char *error = ip_parse_cidr_len(s, &n, ip, plen); + if (!error && s[n]) { + return xasprintf("%s: invalid IP address", s); + } + return error; +} + /* Parses string 's', which must be an IPv6 address. Stores the IPv6 address * into '*ip'. Returns true if successful, otherwise false. */ bool @@ -485,49 +518,65 @@ ipv6_parse(const char *s, struct in6_addr *ip) /* Parses string 's', which must be an IPv6 address with an optional netmask or * CIDR prefix length. Stores the IPv6 address into '*ip' and the netmask into - * '*mask'. (If 's' does not contain a netmask, all-one-bits is assumed.) + * '*mask' (if 's' does not contain a netmask, all-one-bits is assumed), and + * number of scanned characters into '*n'. * * Returns NULL if successful, otherwise an error message that the caller must * free(). */ char * OVS_WARN_UNUSED_RESULT -ipv6_parse_masked(const char *s, struct in6_addr *ip, struct in6_addr *mask) +ipv6_parse_masked_len(const char *s, int *n, struct in6_addr *ip, + struct in6_addr *mask) { char ipv6_s[IPV6_SCAN_LEN + 1]; int prefix; - int n; - if (ovs_scan(s, IPV6_SCAN_FMT"%n", ipv6_s, &n) && ipv6_parse(ipv6_s, ip)) { - s += n; - if (!*s) { - *mask = in6addr_exact; - } else if (ovs_scan(s, "/%d%n", &prefix, &n) && !s[n]) { + if (ovs_scan_len(s, n, " "IPV6_SCAN_FMT, ipv6_s) + && ipv6_parse(ipv6_s, ip)) { + if (ovs_scan_len(s, n, "/%d", &prefix)) { if (prefix <= 0 || prefix > 128) { return xasprintf("%s: IPv6 network prefix bits not between 0 " "and 128", s); } *mask = ipv6_create_mask(prefix); - } else if (ovs_scan(s, "/"IPV6_SCAN_FMT"%n", ipv6_s, &n) - && !s[n] - && ipv6_parse(ipv6_s, mask)) { + } else if (ovs_scan_len(s, n, "/"IPV6_SCAN_FMT, ipv6_s)) { + if (!ipv6_parse(ipv6_s, mask)) { + return xasprintf("%s: Invalid IPv6 mask", s); + } /* OK. */ } else { - return xasprintf("%s: syntax error expecting IPv6 prefix length " - "or mask", s); + /* OK. No mask. */ + *mask = in6addr_exact; } return NULL; } return xasprintf("%s: invalid IPv6 address", s); } -/* Similar to ipv6_parse_masked(), but the mask, if present, must be a CIDR +/* This function is similar to ipv6_parse_masked_len(), but doesn't return the + * number of scanned characters and expects 's' to end following the + * ipv6/(optional) mask. */ +char * OVS_WARN_UNUSED_RESULT +ipv6_parse_masked(const char *s, struct in6_addr *ip, struct in6_addr *mask) +{ + int n = 0; + + char *error = ipv6_parse_masked_len(s, &n, ip, mask); + if (!error && s[n]) { + return xasprintf("%s: invalid IPv6 address", s); + } + return error; +} + +/* Similar to ipv6_parse_masked_len(), but the mask, if present, must be a CIDR * mask and is returned as a prefix length in '*plen'. */ char * OVS_WARN_UNUSED_RESULT -ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen) +ipv6_parse_cidr_len(const char *s, int *n, struct in6_addr *ip, + unsigned int *plen) { struct in6_addr mask; char *error; - error = ipv6_parse_masked(s, ip, &mask); + error = ipv6_parse_masked_len(s, n, ip, &mask); if (error) { return error; } @@ -539,6 +588,20 @@ ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen) return NULL; } +/* Similar to ipv6_parse_cidr_len(), but doesn't return the number of scanned + * characters and expects 's' to end after the ipv6/(optional) cidr. */ +char * OVS_WARN_UNUSED_RESULT +ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen) +{ + int n = 0; + + char *error = ipv6_parse_cidr_len(s, &n, ip, plen); + if (!error && s[n]) { + return xasprintf("%s: invalid IPv6 address", s); + } + return error; +} + /* Stores the string representation of the IPv6 address 'addr' into the * character array 'addr_str', which must be at least INET6_ADDRSTRLEN * bytes long. */ diff --git a/lib/packets.h b/lib/packets.h index f1445deea..bf12937b1 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -594,6 +594,11 @@ char *ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask) OVS_WARN_UNUSED_RESULT; char *ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen) OVS_WARN_UNUSED_RESULT; +char *ip_parse_masked_len(const char *s, int *n, ovs_be32 *ip, ovs_be32 *mask) + OVS_WARN_UNUSED_RESULT; +char *ip_parse_cidr_len(const char *s, int *n, ovs_be32 *ip, + unsigned int *plen) + OVS_WARN_UNUSED_RESULT; #define IP_VER(ip_ihl_ver) ((ip_ihl_ver) >> 4) #define IP_IHL(ip_ihl_ver) ((ip_ihl_ver) & 15) @@ -1041,6 +1046,11 @@ char *ipv6_parse_masked(const char *s, struct in6_addr *ipv6, struct in6_addr *mask); char *ipv6_parse_cidr(const char *s, struct in6_addr *ip, unsigned int *plen) OVS_WARN_UNUSED_RESULT; +char *ipv6_parse_masked_len(const char *s, int *n, struct in6_addr *ipv6, + struct in6_addr *mask); +char *ipv6_parse_cidr_len(const char *s, int *n, struct in6_addr *ip, + unsigned int *plen) + OVS_WARN_UNUSED_RESULT; void *eth_compose(struct dp_packet *, const struct eth_addr eth_dst, const struct eth_addr eth_src, uint16_t eth_type, diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index e6271cff8..b2b1a45cc 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -914,6 +914,112 @@ ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow) } } +struct ipv4_netaddr { + ovs_be32 addr; + unsigned int plen; +}; + +struct ipv6_netaddr { + struct in6_addr addr; + unsigned int plen; +}; + +struct lport_addresses { + struct eth_addr ea; + size_t n_ipv4_addrs; + struct ipv4_netaddr *ipv4_addrs; + size_t n_ipv6_addrs; + struct ipv6_netaddr *ipv6_addrs; +}; + +/* + * Extracts the mac, ipv4 and ipv6 addresses from the input param 'address' + * which should be of the format 'MAC [IP1 IP2 ..]" where IPn should be + * a valid IPv4 or IPv6 address and stores them in the 'ipv4_addrs' and + * 'ipv6_addrs' fields of input param 'laddrs'. + * The caller has to free the 'ipv4_addrs' and 'ipv6_addrs' fields. + * If input param 'store_ipv6' is true only then extracted ipv6 addresses + * are stored in 'ipv6_addrs' fields. + * Return true if at least 'MAC' is found in 'address', false otherwise. + * Eg 1. + * If 'address' = '00:00:00:00:00:01 10.0.0.4 fe80::ea2a:eaff:fe28:3390/64 + * 30.0.0.3/23' and 'store_ipv6' = true + * then returns true with laddrs->n_ipv4_addrs = 2, naddrs->n_ipv6_addrs = 1. + * + * Eg. 2 + * If 'address' = '00:00:00:00:00:01 10.0.0.4 fe80::ea2a:eaff:fe28:3390/64 + * 30.0.0.3/23' and 'store_ipv6' = false + * then returns true with laddrs->n_ipv4_addrs = 2, naddrs->n_ipv6_addrs = 0. + * + * Eg 3. If 'address' = '00:00:00:00:00:01 10.0.0.4 addr 30.0.0.4', then + * returns true with laddrs->n_ipv4_addrs = 1 and laddrs->n_ipv6_addrs = 0. + */ +static bool +extract_lport_addresses(char *address, struct lport_addresses *laddrs, + bool store_ipv6) +{ + char *buf = address; + int buf_index = 0; + char *buf_end = buf + strlen(address); + if (!ovs_scan_len(buf, &buf_index, ETH_ADDR_SCAN_FMT, + ETH_ADDR_SCAN_ARGS(laddrs->ea))) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_INFO_RL(&rl, "invalid syntax '%s' in address. No MAC address" + " found", address); + return false; + } + + ovs_be32 ip4; + struct in6_addr ip6; + unsigned int plen; + char *error; + + laddrs->n_ipv4_addrs = 0; + laddrs->n_ipv6_addrs = 0; + laddrs->ipv4_addrs = NULL; + laddrs->ipv6_addrs = NULL; + + /* Loop through the buffer and extract the IPv4/IPv6 addresses + * and store in the 'laddrs'. Break the loop if invalid data is found. + */ + buf += buf_index; + while (buf < buf_end) { + buf_index = 0; + error = ip_parse_cidr_len(buf, &buf_index, &ip4, &plen); + if (!error) { + laddrs->n_ipv4_addrs++; + laddrs->ipv4_addrs = xrealloc( + laddrs->ipv4_addrs, + sizeof (struct ipv4_netaddr) * laddrs->n_ipv4_addrs); + laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1].addr = ip4; + laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1].plen = plen; + buf += buf_index; + continue; + } + free(error); + error = ipv6_parse_cidr_len(buf, &buf_index, &ip6, &plen); + if (!error && store_ipv6) { + laddrs->n_ipv6_addrs++; + laddrs->ipv6_addrs = xrealloc( + laddrs->ipv6_addrs, + sizeof(struct ipv6_netaddr) * laddrs->n_ipv6_addrs); + memcpy(&laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1].addr, &ip6, + sizeof(struct in6_addr)); + laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1].plen = plen; + } + + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", address); + free(error); + break; + } + buf += buf_index; + } + + return true; +} + /* Appends port security constraints on L2 address field 'eth_addr_field' * (e.g. "eth.src" or "eth.dst") to 'match'. 'port_security', with * 'n_port_security' elements, is the collection of port_security constraints @@ -1194,14 +1300,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } for (size_t i = 0; i < op->nbs->n_addresses; i++) { - struct eth_addr ea; - ovs_be32 ip; - - if (ovs_scan(op->nbs->addresses[i], - ETH_ADDR_SCAN_FMT" "IP_SCAN_FMT, - ETH_ADDR_SCAN_ARGS(ea), IP_SCAN_ARGS(&ip))) { + struct lport_addresses laddrs; + if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs, + false)) { + continue; + } + for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) { char *match = xasprintf( - "arp.tpa == "IP_FMT" && arp.op == 1", IP_ARGS(ip)); + "arp.tpa == "IP_FMT" && arp.op == 1", + IP_ARGS(laddrs.ipv4_addrs[j].addr)); char *actions = xasprintf( "eth.dst = eth.src; " "eth.src = "ETH_ADDR_FMT"; " @@ -1213,14 +1320,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, "outport = inport; " "inport = \"\"; /* Allow sending out inport. */ " "output;", - ETH_ADDR_ARGS(ea), - ETH_ADDR_ARGS(ea), - IP_ARGS(ip)); + ETH_ADDR_ARGS(laddrs.ea), + ETH_ADDR_ARGS(laddrs.ea), + IP_ARGS(laddrs.ipv4_addrs[j].addr)); ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 150, match, actions); free(match); free(actions); } + + free(laddrs.ipv4_addrs); } } @@ -1541,12 +1650,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, /* XXX ARP for neighboring router */ } else if (op->od->n_router_ports) { for (size_t i = 0; i < op->nbs->n_addresses; i++) { - struct eth_addr ea; - ovs_be32 ip; + struct lport_addresses laddrs; + if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs, + false)) { + continue; + } - if (ovs_scan(op->nbs->addresses[i], - ETH_ADDR_SCAN_FMT" "IP_SCAN_FMT, - ETH_ADDR_SCAN_ARGS(ea), IP_SCAN_ARGS(&ip))) { + for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) { + ovs_be32 ip = laddrs.ipv4_addrs[k].addr; for (size_t j = 0; j < op->od->n_router_ports; j++) { /* Get the Logical_Router_Port that the Logical_Port is * connected to, as 'peer'. */ @@ -1574,7 +1685,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, "outport = %s; " "output;", ETH_ADDR_ARGS(peer->mac), - ETH_ADDR_ARGS(ea), + ETH_ADDR_ARGS(laddrs.ea), peer->json_key); ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP, 200, match, actions); @@ -1583,6 +1694,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, break; } } + + free(laddrs.ipv4_addrs); } } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index c3b493424..dbd7bbbbd 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -271,12 +271,12 @@

-
xx:xx:xx:xx:xx:xx
+
Ethernet address followed by zero or more IPv4 or IPv6 addresses (or both)

- An Ethernet address owned by the logical port. Like a physical - Ethernet NIC, a logical port ordinarily has a single fixed - Ethernet address. + An Ethernet address defined is owned by the logical port. + Like a physical Ethernet NIC, a logical port ordinarily has + a single fixed Ethernet address.

@@ -286,27 +286,53 @@ if a MAC learning process had learned that MAC address on the port.

-
-
xx:xx:xx:xx:xx:xx a.b.c.d
-

- This form has all the effects of the previous form. It also - indicates that the logical port owns the given IPv4 address. + If IPv4 or IPv6 address(es) (or both) are defined, it indicates + that the logical port owns the given IP addresses.

- The OVN logical switch uses this information to synthesize - responses to ARP requests without traversing the physical - network. The OVN logical router connected to the logical switch, - if any, uses this information to avoid issuing ARP requests for - logical switch ports. + If IPv4 address(es) are defined, the OVN logical switch uses this + information to synthesize responses to ARP requests without + traversing the physical network. The OVN logical router connected + to the logical switch, if any, uses this information to avoid + issuing ARP requests for logical switch ports.

Note that the order here is important. The Ethernet address must - be listed before the IP address. + be listed before the IP address(es) if defined. +

+ +

+ Examples:

+ +
+
80:fa:5b:06:72:b7
+
+ This indicates that the logical port owns the above mac address. +
+ +
80:fa:5b:06:72:b7 10.0.0.4 20.0.0.4
+
+ This indicates that the logical port owns the mac address and two + IPv4 addresses. +
+ +
80:fa:5b:06:72:b7 fdaa:15f2:72cf:0:f816:3eff:fe20:3f41
+
+ This indicates that the logical port owns the mac address and + 1 IPv6 address. +
+ +
80:fa:5b:06:72:b7 10.0.0.4 fdaa:15f2:72cf:0:f816:3eff:fe20:3f41
+
+ This indicates that the logical port owns the mac address and + 1 IPv4 address and 1 IPv6 address. +
+
unknown
diff --git a/tests/ovn.at b/tests/ovn.at index d0200e4e8..0e717320f 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -551,7 +551,12 @@ for i in 1 2 3; do if test $j = 1; then ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown else - ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" + if test $j = 3; then + ip_addrs="192.168.0.$i$j fe80::ea2a:eaff:fe28:$i$j/64 192.169.0.$i$j" + else + ip_addrs="192.168.0.$i$j" + fi + ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs" ovn-nbctl lport-set-port-security lp$i$j f0:00:00:00:00:$i$j fi done @@ -719,6 +724,12 @@ for is in 1 2 3; do tip_unknown=`ip_to_hex 11 11 11 11` test_arp $s f000000000$s $sip $tip f000000000$d #9 test_arp $s f000000000$s $sip $tip_unknown #10 + + if test $jd = 3; then + # lport[123]3 has an additional ip 192.169.0.[123]3. + tip=`ip_to_hex 192 169 0 $id$jd` + test_arp $s f000000000$s $sip $tip f000000000$d #9 + fi done done @@ -743,6 +754,17 @@ for is in 1 2 3; do done done +# set address for lp13 with invalid characters. +# lp13 should be configured with only 192.168.0.13. +ovn-nbctl lport-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13" +sip=`ip_to_hex 192 168 0 11` +tip=`ip_to_hex 192 168 0 13` +test_arp 11 f00000000011 $sip $tip f00000000013 + +tip=`ip_to_hex 192 169 0 13` +#arp request for 192.169.0.13 should be flooded +test_arp 11 f00000000011 $sip $tip + # Allow some time for packet forwarding. # XXX This can be improved. sleep 1 -- 2.20.1