bbaaef
From 0ed8c91b86b0d4bc01b4795155b25e85df1c6a9d Mon Sep 17 00:00:00 2001
bbaaef
From: Russell Bryant <russell@ovn.org>
bbaaef
Date: Fri, 25 Oct 2019 16:47:21 -0400
bbaaef
Subject: [PATCH 4/5] northd: Add lflows for IPv6 NAT.
bbaaef
bbaaef
Signed-off-by: Russell Bryant <russell@ovn.org>
bbaaef
Acked-by: Numan Siddique <numans@ovn.org>
bbaaef
---
bbaaef
 ovn/northd/ovn-northd.8.xml | 233 ++++++++++++----------
bbaaef
 ovn/northd/ovn-northd.c     | 384 +++++++++++++++++++++++++++---------
bbaaef
 2 files changed, 418 insertions(+), 199 deletions(-)
bbaaef
bbaaef
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
bbaaef
index b9ae28e14..38e6ecec5 100644
bbaaef
--- a/ovn/northd/ovn-northd.8.xml
bbaaef
+++ b/ovn/northd/ovn-northd.8.xml
bbaaef
@@ -1498,11 +1498,55 @@ output;
bbaaef
 
bbaaef
       
  • bbaaef
             

    bbaaef
    -          These flows reply to ARP requests for the virtual IP addresses
    bbaaef
    -          configured in the router for DNAT or load balancing.  For a
    bbaaef
    -          configured DNAT IP address or a load balancer IPv4 VIP A,
    bbaaef
    -          for each router port P with Ethernet
    bbaaef
    -          address E, a priority-90 flow matches
    bbaaef
    +          Reply to IPv6 Neighbor Solicitations.  These flows reply to
    bbaaef
    +          Neighbor Solicitation requests for the router's own IPv6
    bbaaef
    +          address and populate the logical router's mac binding table.
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        

    bbaaef
    +          For each router port P that
    bbaaef
    +          owns IPv6 address A, solicited node address S,
    bbaaef
    +          and Ethernet address E, a priority-90 flow matches
    bbaaef
    +          inport == P &&
    bbaaef
    +          nd_ns && ip6.dst == {A, E} &&
    bbaaef
    +          nd.target == A with the following actions:
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +nd_na_router {
    bbaaef
    +    eth.src = E;
    bbaaef
    +    ip6.src = A;
    bbaaef
    +    nd.target = A;
    bbaaef
    +    nd.tll = E;
    bbaaef
    +    outport = inport;
    bbaaef
    +    flags.loopback = 1;
    bbaaef
    +    output;
    bbaaef
    +};
    bbaaef
    +        
    bbaaef
    +
    bbaaef
    +        

    bbaaef
    +          For the gateway port on a distributed logical router (where
    bbaaef
    +          one of the logical router ports specifies a
    bbaaef
    +          redirect-chassis), the above flows replying to
    bbaaef
    +          IPv6 Neighbor Solicitations are only programmed on the
    bbaaef
    +          gateway port instance on the redirect-chassis.
    bbaaef
    +          This behavior avoids generation of multiple replies from
    bbaaef
    +          different chassis, and allows upstream MAC learning to point
    bbaaef
    +          to the redirect-chassis.
    bbaaef
    +        

    bbaaef
    +      
    bbaaef
    +
    bbaaef
    +      
  • bbaaef
    +        

    bbaaef
    +          These flows reply to ARP requests or IPv6 neighbor solicitation
    bbaaef
    +          for the virtual IP addresses configured in the router for DNAT
    bbaaef
    +          or load balancing.
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        

    bbaaef
    +          IPv4: For a configured DNAT IP address or a load balancer
    bbaaef
    +          IPv4 VIP A, for each router port P with
    bbaaef
    +          Ethernet address E, a priority-90 flow matches
    bbaaef
               inport == P && arp.op == 1 &&
    bbaaef
               arp.tpa == A (ARP request)
    bbaaef
               with the following actions:
    bbaaef
    @@ -1521,6 +1565,30 @@ flags.loopback = 1;
    bbaaef
     output;
    bbaaef
             
    bbaaef
     
    bbaaef
    +        

    bbaaef
    +          IPv6: For a configured DNAT IP address or a load balancer
    bbaaef
    +          IPv6 VIP A, solicited node address S,
    bbaaef
    +          for each router port P with
    bbaaef
    +          Ethernet address E, a priority-90 flow matches
    bbaaef
    +          inport == P && nd_ns &&
    bbaaef
    +          ip6.dst == {A, S} &&
    bbaaef
    +          nd.target == A
    bbaaef
    +          with the following actions:
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +eth.dst = eth.src;
    bbaaef
    +nd_na {
    bbaaef
    +    eth.src = E;
    bbaaef
    +    nd.tll = E;
    bbaaef
    +    ip6.src = A;
    bbaaef
    +    nd.target = A;
    bbaaef
    +    outport = P;
    bbaaef
    +    flags.loopback = 1;
    bbaaef
    +    output;
    bbaaef
    +}
    bbaaef
    +        
    bbaaef
    +
    bbaaef
             

    bbaaef
               For the gateway port on a distributed logical router with NAT
    bbaaef
               (where one of the logical router ports specifies a
    bbaaef
    @@ -1557,6 +1625,15 @@ eth.src = external_mac;
    bbaaef
     arp.sha = external_mac;
    bbaaef
                 
    bbaaef
     
    bbaaef
    +            

    bbaaef
    +              or in the case of IPv6 neighbor solicition:
    bbaaef
    +            

    bbaaef
    +
    bbaaef
    +            
    bbaaef
    +eth.src = external_mac;
    bbaaef
    +nd.tll = external_mac;
    bbaaef
    +            
    bbaaef
    +
    bbaaef
                 

    bbaaef
                   This behavior avoids generation of multiple ARP responses
    bbaaef
                   from different chassis, and allows upstream MAC learning to
    bbaaef
    @@ -1566,68 +1643,6 @@ arp.sha = external_mac;
    bbaaef
             
    bbaaef
           
    bbaaef
     
    bbaaef
    -      
  • bbaaef
    -        

    bbaaef
    -          Reply to IPv6 Neighbor Solicitations.  These flows reply to
    bbaaef
    -          Neighbor Solicitation requests for the router's own IPv6
    bbaaef
    -          address and load balancing IPv6 VIPs and populate the logical
    bbaaef
    -          router's mac binding table.
    bbaaef
    -        

    bbaaef
    -
    bbaaef
    -        

    bbaaef
    -          For each router port P that
    bbaaef
    -          owns IPv6 address A, solicited node address S,
    bbaaef
    -          and Ethernet address E, a priority-90 flow matches
    bbaaef
    -          inport == P &&
    bbaaef
    -          nd_ns && ip6.dst == {A, E} &&
    bbaaef
    -          nd.target == A with the following actions:
    bbaaef
    -        

    bbaaef
    -
    bbaaef
    -        
    bbaaef
    -nd_na_router {
    bbaaef
    -    eth.src = E;
    bbaaef
    -    ip6.src = A;
    bbaaef
    -    nd.target = A;
    bbaaef
    -    nd.tll = E;
    bbaaef
    -    outport = inport;
    bbaaef
    -    flags.loopback = 1;
    bbaaef
    -    output;
    bbaaef
    -};
    bbaaef
    -        
    bbaaef
    -
    bbaaef
    -        

    bbaaef
    -          For each router port P that has load balancing VIP
    bbaaef
    -          A, solicited node address S, and Ethernet
    bbaaef
    -          address E, a priority-90 flow matches
    bbaaef
    -          inport == P &&
    bbaaef
    -          nd_ns && ip6.dst == {A, E} &&
    bbaaef
    -          nd.target == A with the following actions:
    bbaaef
    -        

    bbaaef
    -
    bbaaef
    -        
    bbaaef
    -nd_na {
    bbaaef
    -    eth.src = E;
    bbaaef
    -    ip6.src = A;
    bbaaef
    -    nd.target = A;
    bbaaef
    -    nd.tll = E;
    bbaaef
    -    outport = inport;
    bbaaef
    -    flags.loopback = 1;
    bbaaef
    -    output;
    bbaaef
    -};
    bbaaef
    -        
    bbaaef
    -
    bbaaef
    -        

    bbaaef
    -          For the gateway port on a distributed logical router (where
    bbaaef
    -          one of the logical router ports specifies a
    bbaaef
    -          redirect-chassis), the above flows replying to
    bbaaef
    -          IPv6 Neighbor Solicitations are only programmed on the
    bbaaef
    -          gateway port instance on the redirect-chassis.
    bbaaef
    -          This behavior avoids generation of multiple replies from
    bbaaef
    -          different chassis, and allows upstream MAC learning to point
    bbaaef
    -          to the redirect-chassis.
    bbaaef
    -        

    bbaaef
    -      
    bbaaef
    -
    bbaaef
           
  • bbaaef
             Priority-85 flows which drops the ARP and IPv6 Neighbor Discovery
    bbaaef
             packets.
    bbaaef
    @@ -1681,10 +1696,11 @@ nd_na {
    bbaaef
             handled by one of the flows above, which amounts to ICMP (other than
    bbaaef
             echo requests) and fragments with nonzero offsets.  For each IP address
    bbaaef
             A owned by the router, a priority-60 flow matches
    bbaaef
    -        ip4.dst == A and drops the traffic.  An
    bbaaef
    -        exception is made and the above flow is not added if the router
    bbaaef
    -        port's own IP address is used to SNAT packets passing through that
    bbaaef
    -        router.
    bbaaef
    +        ip4.dst == A or
    bbaaef
    +        ip6.dst == A
    bbaaef
    +        and drops the traffic.  An exception is made and the above flow
    bbaaef
    +        is not added if the router port's own IP address is used to SNAT
    bbaaef
    +        packets passing through that router.
    bbaaef
           
    bbaaef
         
    bbaaef
     
    bbaaef
    @@ -1778,23 +1794,26 @@ icmp6 {
    bbaaef
             

    bbaaef
               If the Gateway router has been configured to force SNAT any
    bbaaef
               previously DNATted packets to B, a priority-110 flow
    bbaaef
    -          matches ip && ip4.dst == B with
    bbaaef
    -          an action ct_snat; .
    bbaaef
    +          matches ip && ip4.dst == B or
    bbaaef
    +          ip && ip6.dst == B
    bbaaef
    +          with an action ct_snat; .
    bbaaef
             

    bbaaef
     
    bbaaef
             

    bbaaef
               If the Gateway router has been configured to force SNAT any
    bbaaef
               previously load-balanced packets to B, a priority-100 flow
    bbaaef
    -          matches ip && ip4.dst == B with
    bbaaef
    -          an action ct_snat; .
    bbaaef
    +          matches ip && ip4.dst == B or
    bbaaef
    +          ip && ip6.dst == B
    bbaaef
    +          with an action ct_snat; .
    bbaaef
             

    bbaaef
     
    bbaaef
             

    bbaaef
               For each NAT configuration in the OVN Northbound database, that asks
    bbaaef
               to change the source IP address of a packet from A to
    bbaaef
    -          B, a priority-90 flow matches ip &&
    bbaaef
    -          ip4.dst == B with an action
    bbaaef
    -          ct_snat; .
    bbaaef
    +          B, a priority-90 flow matches
    bbaaef
    +          ip && ip4.dst == B or
    bbaaef
    +          ip && ip6.dst == B
    bbaaef
    +          with an action ct_snat; .
    bbaaef
             

    bbaaef
     
    bbaaef
             

    bbaaef
    @@ -1812,7 +1831,9 @@ icmp6 {
    bbaaef
               For each configuration in the OVN Northbound database, that asks
    bbaaef
               to change the source IP address of a packet from A to
    bbaaef
               B, a priority-100 flow matches ip &&
    bbaaef
    -          ip4.dst == B && inport == GW,
    bbaaef
    +          ip4.dst == B && inport == GW or
    bbaaef
    +          ip &&
    bbaaef
    +          ip6.dst == B && inport == GW
    bbaaef
               where GW is the logical router gateway port, with an
    bbaaef
               action ct_snat;.
    bbaaef
             

    bbaaef
    @@ -1826,10 +1847,12 @@ icmp6 {
    bbaaef
             

    bbaaef
               For each configuration in the OVN Northbound database, that asks
    bbaaef
               to change the source IP address of a packet from A to
    bbaaef
    -          B, a priority-50 flow matches ip &&
    bbaaef
    -          ip4.dst == B with an action
    bbaaef
    +          B, a priority-50 flow matches
    bbaaef
    +          ip && ip4.dst == B or
    bbaaef
    +          ip && ip6.dst == B
    bbaaef
    +          with an action
    bbaaef
               REGBIT_NAT_REDIRECT = 1; next;.  This flow is for
    bbaaef
    -          east/west traffic to a NAT destination IPv4 address.  By
    bbaaef
    +          east/west traffic to a NAT destination IPv4/IPv6 address.  By
    bbaaef
               setting the REGBIT_NAT_REDIRECT flag, in the
    bbaaef
               ingress table Gateway Redirect this will trigger a
    bbaaef
               redirect to the instance of the gateway port on the
    bbaaef
    @@ -1875,25 +1898,27 @@ icmp6 {
    bbaaef
             For all the configured load balancing rules for a Gateway router or
    bbaaef
             Router with gateway port in OVN_Northbound database that
    bbaaef
             includes a L4 port PORT of protocol P and IPv4
    bbaaef
    -        address VIP, a priority-120 flow that matches on
    bbaaef
    +        or IPv6 address VIP, a priority-120 flow that matches on
    bbaaef
             ct.new && ip && ip4.dst == VIP
    bbaaef
             && P && P.dst == PORT
    bbaaef
    -         with an action of ct_lb(args),
    bbaaef
    -        where args contains comma separated IPv4 addresses (and
    bbaaef
    -        optional port numbers) to load balance to.  If the router is configured
    bbaaef
    -        to force SNAT any load-balanced packets, the above action will be
    bbaaef
    -        replaced by flags.force_snat_for_lb = 1;
    bbaaef
    +         (ip6.dst == VIP in the IPv6 case)
    bbaaef
    +        with an action of ct_lb(args),
    bbaaef
    +        where args contains comma separated IPv4 or IPv6 addresses
    bbaaef
    +        (and optional port numbers) to load balance to.  If the router is
    bbaaef
    +        configured to force SNAT any load-balanced packets, the above action
    bbaaef
    +        will be replaced by flags.force_snat_for_lb = 1;
    bbaaef
             ct_lb(args);.
    bbaaef
           
    bbaaef
     
    bbaaef
           
  • bbaaef
             For all the configured load balancing rules for a router in
    bbaaef
             OVN_Northbound database that includes a L4 port
    bbaaef
    -        PORT of protocol P and IPv4 address
    bbaaef
    +        PORT of protocol P and IPv4 or IPv6 address
    bbaaef
             VIP, a priority-120 flow that matches on
    bbaaef
             ct.est && ip && ip4.dst == VIP
    bbaaef
             && P && P.dst == PORT
    bbaaef
    -         with an action of ct_dnat;. If the router is
    bbaaef
    +         (ip6.dst == VIP in the IPv6 case)
    bbaaef
    +        with an action of ct_dnat;. If the router is
    bbaaef
             configured to force SNAT any load-balanced packets, the above action
    bbaaef
             will be replaced by flags.force_snat_for_lb = 1; ct_dnat;.
    bbaaef
           
    bbaaef
    @@ -1903,11 +1928,13 @@ icmp6 {
    bbaaef
             OVN_Northbound database that includes just an IP address
    bbaaef
             VIP to match on, a priority-110 flow that matches on
    bbaaef
             ct.new && ip && ip4.dst ==
    bbaaef
    -        VIP with an action of
    bbaaef
    +        VIP (ip6.dst == VIP in the
    bbaaef
    +        IPv6 case) with an action of
    bbaaef
             ct_lb(args), where args contains
    bbaaef
    -        comma separated IPv4 addresses.  If the router is configured to force
    bbaaef
    -        SNAT any load-balanced packets, the above action will be replaced by
    bbaaef
    -        flags.force_snat_for_lb = 1; ct_lb(args);.
    bbaaef
    +        comma separated IPv4 or IPv6 addresses.  If the router is configured
    bbaaef
    +        to force SNAT any load-balanced packets, the above action will be
    bbaaef
    +        replaced by flags.force_snat_for_lb = 1;
    bbaaef
    +        ct_lb(args);.
    bbaaef
           
    bbaaef
     
    bbaaef
           
  • bbaaef
    @@ -1915,7 +1942,8 @@ icmp6 {
    bbaaef
             OVN_Northbound database that includes just an IP address
    bbaaef
             VIP to match on, a priority-110 flow that matches on
    bbaaef
             ct.est && ip && ip4.dst ==
    bbaaef
    -        VIP with an action of ct_dnat;.
    bbaaef
    +        VIP (or ip6.dst == VIP)
    bbaaef
    +        with an action of ct_dnat;.
    bbaaef
             If the router is configured to force SNAT any load-balanced
    bbaaef
             packets, the above action will be replaced by
    bbaaef
             flags.force_snat_for_lb = 1; ct_dnat;.
    bbaaef
    @@ -1929,7 +1957,8 @@ icmp6 {
    bbaaef
             For each configuration in the OVN Northbound database, that asks
    bbaaef
             to change the destination IP address of a packet from A to
    bbaaef
             B, a priority-100 flow matches ip &&
    bbaaef
    -        ip4.dst == A with an action
    bbaaef
    +        ip4.dst == A or ip &&
    bbaaef
    +        ip6.dst == A with an action
    bbaaef
             flags.loopback = 1; ct_dnat(B);.  If the
    bbaaef
             Gateway router is configured to force SNAT any DNATed packet,
    bbaaef
             the above action will be replaced by
    bbaaef
    @@ -1966,7 +1995,8 @@ icmp6 {
    bbaaef
               B, a priority-100 flow matches ip &&
    bbaaef
               ip4.dst == B && inport == GW,
    bbaaef
               where GW is the logical router gateway port, with an
    bbaaef
    -          action ct_dnat(B);.
    bbaaef
    +          action ct_dnat(B);.  The match will
    bbaaef
    +          include ip6.dst == B in the IPv6 case.
    bbaaef
             

    bbaaef
     
    bbaaef
             

    bbaaef
    @@ -1979,9 +2009,10 @@ icmp6 {
    bbaaef
               For each configuration in the OVN Northbound database, that asks
    bbaaef
               to change the destination IP address of a packet from A to
    bbaaef
               B, a priority-50 flow matches ip &&
    bbaaef
    -          ip4.dst == B with an action
    bbaaef
    +          ip4.dst == B or ip &&
    bbaaef
    +          ip6.dst == B with an action
    bbaaef
               REGBIT_NAT_REDIRECT = 1; next;.  This flow is for
    bbaaef
    -          east/west traffic to a NAT destination IPv4 address.  By
    bbaaef
    +          east/west traffic to a NAT destination IPv4/IPv6 address.  By
    bbaaef
               setting the REGBIT_NAT_REDIRECT flag, in the
    bbaaef
               ingress table Gateway Redirect this will trigger a
    bbaaef
               redirect to the instance of the gateway port on the
    bbaaef
    @@ -2136,8 +2167,8 @@ logical_ip{0,1} = LIP{0,1};
    bbaaef
             
    bbaaef
     eth.dst = MAC0;
    bbaaef
     eth.src = MAC1;
    bbaaef
    -reg0 = ip4.dst;
    bbaaef
    -reg1 = EIP1;
    bbaaef
    +reg0 = ip4.dst; /* xxreg0 = ip6.dst; in the IPv6 case */
    bbaaef
    +reg1 = EIP1; /* xxreg1 in the IPv6 case */
    bbaaef
     outport = redirect-chassis-port;
    bbaaef
     REGBIT_DISTRIBUTED_NAT = 1; next;.
    bbaaef
             
    bbaaef
    diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
    bbaaef
    index 7cfeb60be..84b2a9ff1 100644
    bbaaef
    --- a/ovn/northd/ovn-northd.c
    bbaaef
    +++ b/ovn/northd/ovn-northd.c
    bbaaef
    @@ -66,6 +66,15 @@ struct northd_context {
    bbaaef
         struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
    bbaaef
     };
    bbaaef
     
    bbaaef
    +/* An IPv4 or IPv6 address */
    bbaaef
    +struct v46_ip {
    bbaaef
    +    int family;
    bbaaef
    +    union {
    bbaaef
    +        ovs_be32 ipv4;
    bbaaef
    +        struct in6_addr ipv6;
    bbaaef
    +    };
    bbaaef
    +};
    bbaaef
    +
    bbaaef
     static const char *ovnnb_db;
    bbaaef
     static const char *ovnsb_db;
    bbaaef
     static const char *unixctl_path;
    bbaaef
    @@ -2266,6 +2275,15 @@ get_nat_addresses(const struct ovn_port *op, size_t *n)
    bbaaef
                         break;
    bbaaef
                     }
    bbaaef
                 }
    bbaaef
    +            if (!is_router_ip) {
    bbaaef
    +                for (size_t j = 0; j < op->lrp_networks.n_ipv6_addrs; j++) {
    bbaaef
    +                    if (!strcmp(nat->external_ip,
    bbaaef
    +                                op->lrp_networks.ipv6_addrs[j].addr_s)) {
    bbaaef
    +                        is_router_ip = true;
    bbaaef
    +                        break;
    bbaaef
    +                    }
    bbaaef
    +                }
    bbaaef
    +            }
    bbaaef
     
    bbaaef
                 if (!is_router_ip) {
    bbaaef
                     ds_put_format(&c_addresses, " %s", nat->external_ip);
    bbaaef
    @@ -6013,9 +6031,28 @@ add_distributed_nat_routes(struct hmap *lflows, const struct ovn_port *op)
    bbaaef
                 continue;
    bbaaef
             }
    bbaaef
     
    bbaaef
    +        /* Determine if we need to create IPv4 or IPv6 flows */
    bbaaef
    +        ovs_be32 ip;
    bbaaef
    +        struct in6_addr ipv6;
    bbaaef
    +        int family = AF_INET;
    bbaaef
    +        if (!ip_parse(nat->external_ip, &ip) || !ip) {
    bbaaef
    +            family = AF_INET6;
    bbaaef
    +            if (!ipv6_parse(nat->external_ip, &ipv6)) {
    bbaaef
    +                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    +                VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration "
    bbaaef
    +                             "for router %s", nat->external_ip, op->key);
    bbaaef
    +                /* We'll create IPv6 flows anyway, but the address
    bbaaef
    +                 * is probably bogus ... */
    bbaaef
    +            }
    bbaaef
    +        }
    bbaaef
    +
    bbaaef
             ds_put_format(&match, "inport == %s && "
    bbaaef
    -                      "ip4.src == %s && ip4.dst == %s",
    bbaaef
    -                       op->json_key, nat->logical_ip, nat->external_ip);
    bbaaef
    +                      "ip%s.src == %s && ip%s.dst == %s",
    bbaaef
    +                       op->json_key,
    bbaaef
    +                       family == AF_INET ? "4" : "6",
    bbaaef
    +                       nat->logical_ip,
    bbaaef
    +                       family == AF_INET ? "4" : "6",
    bbaaef
    +                       nat->external_ip);
    bbaaef
             ds_put_format(&actions, "outport = %s; eth.dst = %s; "
    bbaaef
                           REGBIT_DISTRIBUTED_NAT" = 1; "
    bbaaef
                           REGBIT_NAT_REDIRECT" = 0; next;",
    bbaaef
    @@ -6033,17 +6070,38 @@ add_distributed_nat_routes(struct hmap *lflows, const struct ovn_port *op)
    bbaaef
                     !nat2->external_mac || !nat2->external_ip)
    bbaaef
                     continue;
    bbaaef
     
    bbaaef
    +            family = AF_INET;
    bbaaef
    +            if (!ip_parse(nat2->external_ip, &ip) || !ip) {
    bbaaef
    +                family = AF_INET6;
    bbaaef
    +                if (!ipv6_parse(nat2->external_ip, &ipv6)) {
    bbaaef
    +                    static struct vlog_rate_limit rl =
    bbaaef
    +                        VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    +                    VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration "
    bbaaef
    +                                 "for router %s", nat2->external_ip, op->key);
    bbaaef
    +                    /* We'll create IPv6 flows anyway, but the address
    bbaaef
    +                     * is probably bogus ... */
    bbaaef
    +                }
    bbaaef
    +            }
    bbaaef
    +
    bbaaef
                 ds_put_format(&match, "inport == %s && "
    bbaaef
    -                          "ip4.src == %s && ip4.dst == %s",
    bbaaef
    -                          op->json_key, nat->logical_ip, nat2->external_ip);
    bbaaef
    +                          "ip%s.src == %s && ip%s.dst == %s",
    bbaaef
    +                          op->json_key,
    bbaaef
    +                          family == AF_INET ? "4" : "6",
    bbaaef
    +                          nat->logical_ip,
    bbaaef
    +                          family == AF_INET ? "4" : "6",
    bbaaef
    +                          nat2->external_ip);
    bbaaef
                 ds_put_format(&actions, "outport = %s; "
    bbaaef
                               "eth.src = %s; eth.dst = %s; "
    bbaaef
    -                          "reg0 = ip4.dst; reg1 = %s; "
    bbaaef
    +                          "%sreg0 = ip%s.dst; %sreg1 = %s; "
    bbaaef
                               REGBIT_DISTRIBUTED_NAT" = 1; "
    bbaaef
                               REGBIT_NAT_REDIRECT" = 0; next;",
    bbaaef
                               op->od->l3dgw_port->json_key,
    bbaaef
                               op->od->l3dgw_port->lrp_networks.ea_s,
    bbaaef
    -                          nat2->external_mac, nat->external_ip);
    bbaaef
    +                          nat2->external_mac,
    bbaaef
    +                          family == AF_INET ? "" : "xx",
    bbaaef
    +                          family == AF_INET ? "4" : "6",
    bbaaef
    +                          family == AF_INET ? "" : "xx",
    bbaaef
    +                          nat->external_ip);
    bbaaef
                 ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, 400,
    bbaaef
                               ds_cstr(&match), ds_cstr(&actions));
    bbaaef
                 ds_clear(&match);
    bbaaef
    @@ -6280,7 +6338,8 @@ op_put_v6_networks(struct ds *ds, const struct ovn_port *op)
    bbaaef
     }
    bbaaef
     
    bbaaef
     static const char *
    bbaaef
    -get_force_snat_ip(struct ovn_datapath *od, const char *key_type, ovs_be32 *ip)
    bbaaef
    +get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
    bbaaef
    +                  struct v46_ip *ip)
    bbaaef
     {
    bbaaef
         char *key = xasprintf("%s_force_snat_ip", key_type);
    bbaaef
         const char *ip_address = smap_get(&od->nbr->options, key);
    bbaaef
    @@ -6288,19 +6347,27 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, ovs_be32 *ip)
    bbaaef
     
    bbaaef
         if (ip_address) {
    bbaaef
             ovs_be32 mask;
    bbaaef
    -        char *error = ip_parse_masked(ip_address, ip, &mask);
    bbaaef
    +        ip->family = AF_INET;
    bbaaef
    +        char *error = ip_parse_masked(ip_address, &ip->ipv4, &mask);
    bbaaef
             if (error || mask != OVS_BE32_MAX) {
    bbaaef
    -            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    -            VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"",
    bbaaef
    -                         ip_address, UUID_ARGS(&od->key));
    bbaaef
                 free(error);
    bbaaef
    -            *ip = 0;
    bbaaef
    -            return NULL;
    bbaaef
    +            struct in6_addr mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
    bbaaef
    +            ip->family = AF_INET6;
    bbaaef
    +            error = ipv6_parse_masked(ip_address, &ip->ipv6, &mask_v6);
    bbaaef
    +            if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
    bbaaef
    +                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    +                VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"",
    bbaaef
    +                             ip_address, UUID_ARGS(&od->key));
    bbaaef
    +                memset(ip, 0, sizeof *ip);
    bbaaef
    +                ip->family = AF_UNSPEC;
    bbaaef
    +                return NULL;
    bbaaef
    +            }
    bbaaef
             }
    bbaaef
             return ip_address;
    bbaaef
         }
    bbaaef
     
    bbaaef
    -    *ip = 0;
    bbaaef
    +    memset(ip, 0, sizeof *ip);
    bbaaef
    +    ip->family = AF_UNSPEC;
    bbaaef
         return NULL;
    bbaaef
     }
    bbaaef
     
    bbaaef
    @@ -6866,11 +6933,11 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
             /* A gateway router can have 2 SNAT IP addresses to force DNATed and
    bbaaef
              * LBed traffic respectively to be SNATed.  In addition, there can be
    bbaaef
              * a number of SNAT rules in the NAT table. */
    bbaaef
    -        ovs_be32 *snat_ips = xmalloc(sizeof *snat_ips *
    bbaaef
    -                                     (op->od->nbr->n_nat + 2));
    bbaaef
    +        struct v46_ip *snat_ips = xmalloc(sizeof *snat_ips
    bbaaef
    +                                          * (op->od->nbr->n_nat + 2));
    bbaaef
             size_t n_snat_ips = 0;
    bbaaef
     
    bbaaef
    -        ovs_be32 snat_ip;
    bbaaef
    +        struct v46_ip snat_ip;
    bbaaef
             const char *dnat_force_snat_ip = get_force_snat_ip(op->od, "dnat",
    bbaaef
                                                                &snat_ip);
    bbaaef
             if (dnat_force_snat_ip) {
    bbaaef
    @@ -6889,44 +6956,85 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                 nat = op->od->nbr->nat[i];
    bbaaef
     
    bbaaef
                 ovs_be32 ip;
    bbaaef
    +            struct in6_addr ipv6;
    bbaaef
    +            bool is_v6 = false;
    bbaaef
                 if (!ip_parse(nat->external_ip, &ip) || !ip) {
    bbaaef
    -                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    -                VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration "
    bbaaef
    -                             "for router %s", nat->external_ip, op->key);
    bbaaef
    -                continue;
    bbaaef
    +                if (!ipv6_parse(nat->external_ip, &ipv6)) {
    bbaaef
    +                    static struct vlog_rate_limit rl =
    bbaaef
    +                        VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    +                    VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration "
    bbaaef
    +                                 "for router %s", nat->external_ip, op->key);
    bbaaef
    +                    continue;
    bbaaef
    +                }
    bbaaef
    +                is_v6 = true;
    bbaaef
                 }
    bbaaef
     
    bbaaef
                 if (!strcmp(nat->type, "snat")) {
    bbaaef
    -                snat_ips[n_snat_ips++] = ip;
    bbaaef
    +                if (is_v6) {
    bbaaef
    +                    snat_ips[n_snat_ips].family = AF_INET6;
    bbaaef
    +                    snat_ips[n_snat_ips++].ipv6 = ipv6;
    bbaaef
    +                } else {
    bbaaef
    +                    snat_ips[n_snat_ips].family = AF_INET;
    bbaaef
    +                    snat_ips[n_snat_ips++].ipv4 = ip;
    bbaaef
    +                }
    bbaaef
                     continue;
    bbaaef
                 }
    bbaaef
     
    bbaaef
    -            /* ARP handling for external IP addresses.
    bbaaef
    +            /* ARP / ND handling for external IP addresses.
    bbaaef
                  *
    bbaaef
                  * DNAT IP addresses are external IP addresses that need ARP
    bbaaef
                  * handling. */
    bbaaef
    +            char addr_s[INET6_ADDRSTRLEN + 1];
    bbaaef
                 ds_clear(&match);
    bbaaef
    -            ds_put_format(&match,
    bbaaef
    -                          "inport == %s && arp.tpa == "IP_FMT" && arp.op == 1",
    bbaaef
    -                          op->json_key, IP_ARGS(ip));
    bbaaef
    -
    bbaaef
                 ds_clear(&actions);
    bbaaef
    -            ds_put_format(&actions,
    bbaaef
    -                "eth.dst = eth.src; "
    bbaaef
    -                "arp.op = 2; /* ARP reply */ "
    bbaaef
    -                "arp.tha = arp.sha; ");
    bbaaef
    +            if (is_v6) {
    bbaaef
    +                /* For ND solicitations, we need to listen for both the
    bbaaef
    +                 * unicast IPv6 address and its all-nodes multicast address,
    bbaaef
    +                 * but always respond with the unicast IPv6 address. */
    bbaaef
    +                char sn_addr_s[INET6_ADDRSTRLEN + 1];
    bbaaef
    +                struct in6_addr sn_addr;
    bbaaef
    +                in6_addr_solicited_node(&sn_addr, &ipv6);
    bbaaef
    +                ipv6_string_mapped(sn_addr_s, &sn_addr);
    bbaaef
    +                ipv6_string_mapped(addr_s, &ipv6);
    bbaaef
    +
    bbaaef
    +                ds_put_format(&match, "inport == %s && "
    bbaaef
    +                        "nd_ns && ip6.dst == {%s, %s} && nd.target == %s",
    bbaaef
    +                        op->json_key, addr_s, sn_addr_s, addr_s);
    bbaaef
     
    bbaaef
    +                ds_put_format(&actions,
    bbaaef
    +                    "eth.dst = eth.src; "
    bbaaef
    +                    "nd_na { ");
    bbaaef
    +            } else {
    bbaaef
    +                ds_put_format(&match,
    bbaaef
    +                              "inport == %s "
    bbaaef
    +                              "&& arp.tpa == "IP_FMT" && arp.op == 1",
    bbaaef
    +                              op->json_key, IP_ARGS(ip));
    bbaaef
    +
    bbaaef
    +                ds_put_format(&actions,
    bbaaef
    +                    "eth.dst = eth.src; "
    bbaaef
    +                    "arp.op = 2; /* ARP reply */ "
    bbaaef
    +                    "arp.tha = arp.sha; ");
    bbaaef
    +            }
    bbaaef
                 if (op->od->l3dgw_port && op == op->od->l3dgw_port) {
    bbaaef
                     struct eth_addr mac;
    bbaaef
                     if (nat->external_mac &&
    bbaaef
                         eth_addr_from_string(nat->external_mac, &mac)
    bbaaef
                         && nat->logical_port) {
    bbaaef
                         /* distributed NAT case, use nat->external_mac */
    bbaaef
    -                    ds_put_format(&actions,
    bbaaef
    -                        "eth.src = "ETH_ADDR_FMT"; "
    bbaaef
    -                        "arp.sha = "ETH_ADDR_FMT"; ",
    bbaaef
    -                        ETH_ADDR_ARGS(mac),
    bbaaef
    -                        ETH_ADDR_ARGS(mac));
    bbaaef
    +                    if (is_v6) {
    bbaaef
    +                        ds_put_format(&actions,
    bbaaef
    +                            "eth.src = "ETH_ADDR_FMT"; "
    bbaaef
    +                            "nd.tll = "ETH_ADDR_FMT"; ",
    bbaaef
    +                            ETH_ADDR_ARGS(mac),
    bbaaef
    +                            ETH_ADDR_ARGS(mac));
    bbaaef
    +
    bbaaef
    +                    } else {
    bbaaef
    +                        ds_put_format(&actions,
    bbaaef
    +                            "eth.src = "ETH_ADDR_FMT"; "
    bbaaef
    +                            "arp.sha = "ETH_ADDR_FMT"; ",
    bbaaef
    +                            ETH_ADDR_ARGS(mac),
    bbaaef
    +                            ETH_ADDR_ARGS(mac));
    bbaaef
    +                    }
    bbaaef
                         /* Traffic with eth.src = nat->external_mac should only be
    bbaaef
                          * sent from the chassis where nat->logical_port is
    bbaaef
                          * resident, so that upstream MAC learning points to the
    bbaaef
    @@ -6935,11 +7043,20 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                         ds_put_format(&match, " && is_chassis_resident(\"%s\")",
    bbaaef
                                       nat->logical_port);
    bbaaef
                     } else {
    bbaaef
    -                    ds_put_format(&actions,
    bbaaef
    -                        "eth.src = %s; "
    bbaaef
    -                        "arp.sha = %s; ",
    bbaaef
    -                        op->lrp_networks.ea_s,
    bbaaef
    -                        op->lrp_networks.ea_s);
    bbaaef
    +                    if (is_v6) {
    bbaaef
    +                        ds_put_format(&actions,
    bbaaef
    +                            "eth.src = %s; "
    bbaaef
    +                            "nd.tll = %s; ",
    bbaaef
    +                            op->lrp_networks.ea_s,
    bbaaef
    +                            op->lrp_networks.ea_s);
    bbaaef
    +
    bbaaef
    +                    } else {
    bbaaef
    +                        ds_put_format(&actions,
    bbaaef
    +                            "eth.src = %s; "
    bbaaef
    +                            "arp.sha = %s; ",
    bbaaef
    +                            op->lrp_networks.ea_s,
    bbaaef
    +                            op->lrp_networks.ea_s);
    bbaaef
    +                    }
    bbaaef
                         /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
    bbaaef
                          * should only be sent from the "redirect-chassis", so that
    bbaaef
                          * upstream MAC learning points to the "redirect-chassis".
    bbaaef
    @@ -6950,21 +7067,40 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                                           op->od->l3redirect_port->json_key);
    bbaaef
                         }
    bbaaef
                     }
    bbaaef
    +            } else {
    bbaaef
    +                if (is_v6) {
    bbaaef
    +                    ds_put_format(&actions,
    bbaaef
    +                        "eth.src = %s; "
    bbaaef
    +                        "nd.tll = %s; ",
    bbaaef
    +                        op->lrp_networks.ea_s,
    bbaaef
    +                        op->lrp_networks.ea_s);
    bbaaef
    +                } else {
    bbaaef
    +                    ds_put_format(&actions,
    bbaaef
    +                        "eth.src = %s; "
    bbaaef
    +                        "arp.sha = %s; ",
    bbaaef
    +                        op->lrp_networks.ea_s,
    bbaaef
    +                        op->lrp_networks.ea_s);
    bbaaef
    +                }
    bbaaef
    +            }
    bbaaef
    +            if (is_v6) {
    bbaaef
    +                ds_put_format(&actions,
    bbaaef
    +                    "ip6.src = %s; "
    bbaaef
    +                    "nd.target = %s; "
    bbaaef
    +                    "outport = %s; "
    bbaaef
    +                    "flags.loopback = 1; "
    bbaaef
    +                    "output; "
    bbaaef
    +                    "};",
    bbaaef
    +                    addr_s, addr_s, op->json_key);
    bbaaef
                 } else {
    bbaaef
                     ds_put_format(&actions,
    bbaaef
    -                    "eth.src = %s; "
    bbaaef
    -                    "arp.sha = %s; ",
    bbaaef
    -                    op->lrp_networks.ea_s,
    bbaaef
    -                    op->lrp_networks.ea_s);
    bbaaef
    +                    "arp.tpa = arp.spa; "
    bbaaef
    +                    "arp.spa = "IP_FMT"; "
    bbaaef
    +                    "outport = %s; "
    bbaaef
    +                    "flags.loopback = 1; "
    bbaaef
    +                    "output;",
    bbaaef
    +                    IP_ARGS(ip),
    bbaaef
    +                    op->json_key);
    bbaaef
                 }
    bbaaef
    -            ds_put_format(&actions,
    bbaaef
    -                "arp.tpa = arp.spa; "
    bbaaef
    -                "arp.spa = "IP_FMT"; "
    bbaaef
    -                "outport = %s; "
    bbaaef
    -                "flags.loopback = 1; "
    bbaaef
    -                "output;",
    bbaaef
    -                IP_ARGS(ip),
    bbaaef
    -                op->json_key);
    bbaaef
                 ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
    bbaaef
                               ds_cstr(&match), ds_cstr(&actions));
    bbaaef
             }
    bbaaef
    @@ -7021,7 +7157,36 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                 bool snat_ip_is_router_ip = false;
    bbaaef
                 for (int j = 0; j < n_snat_ips; j++) {
    bbaaef
                     /* Packets to SNAT IPs should not be dropped. */
    bbaaef
    -                if (op->lrp_networks.ipv4_addrs[i].addr == snat_ips[j]) {
    bbaaef
    +                if (snat_ips[j].family == AF_INET
    bbaaef
    +                    && op->lrp_networks.ipv4_addrs[i].addr
    bbaaef
    +                       == snat_ips[j].ipv4) {
    bbaaef
    +                        snat_ip_is_router_ip = true;
    bbaaef
    +                        break;
    bbaaef
    +                }
    bbaaef
    +            }
    bbaaef
    +            if (snat_ip_is_router_ip) {
    bbaaef
    +                continue;
    bbaaef
    +            }
    bbaaef
    +            ds_put_format(&match, "%s, ",
    bbaaef
    +                          op->lrp_networks.ipv4_addrs[i].addr_s);
    bbaaef
    +            has_drop_ips = true;
    bbaaef
    +        }
    bbaaef
    +        if (has_drop_ips) {
    bbaaef
    +            ds_chomp(&match, ' ');
    bbaaef
    +            ds_chomp(&match, ',');
    bbaaef
    +            ds_put_cstr(&match, "} || ip6.dst == {");
    bbaaef
    +        } else {
    bbaaef
    +            ds_clear(&match);
    bbaaef
    +            ds_put_cstr(&match, "ip6.dst == {");
    bbaaef
    +        }
    bbaaef
    +
    bbaaef
    +        for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
    bbaaef
    +            bool snat_ip_is_router_ip = false;
    bbaaef
    +            for (int j = 0; j < n_snat_ips; j++) {
    bbaaef
    +                /* Packets to SNAT IPs should not be dropped. */
    bbaaef
    +                if (snat_ips[j].family == AF_INET6
    bbaaef
    +                    && !memcmp(&op->lrp_networks.ipv6_addrs[i].addr,
    bbaaef
    +                               &snat_ips[j].ipv6, sizeof snat_ips[j].ipv6)) {
    bbaaef
                         snat_ip_is_router_ip = true;
    bbaaef
                         break;
    bbaaef
                     }
    bbaaef
    @@ -7030,9 +7195,10 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                     continue;
    bbaaef
                 }
    bbaaef
                 ds_put_format(&match, "%s, ",
    bbaaef
    -                          op->lrp_networks.ipv4_addrs[i].addr_s);
    bbaaef
    +                          op->lrp_networks.ipv6_addrs[i].addr_s);
    bbaaef
                 has_drop_ips = true;
    bbaaef
             }
    bbaaef
    +
    bbaaef
             ds_chomp(&match, ' ');
    bbaaef
             ds_chomp(&match, ',');
    bbaaef
             ds_put_cstr(&match, "}");
    bbaaef
    @@ -7059,14 +7225,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
             }
    bbaaef
     
    bbaaef
             if (op->lrp_networks.n_ipv6_addrs) {
    bbaaef
    -            /* L3 admission control: drop packets that originate from an
    bbaaef
    -             * IPv6 address owned by the router (priority 100). */
    bbaaef
    -            ds_clear(&match);
    bbaaef
    -            ds_put_cstr(&match, "ip6.src == ");
    bbaaef
    -            op_put_v6_networks(&match, op);
    bbaaef
    -            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
    bbaaef
    -                          ds_cstr(&match), "drop;");
    bbaaef
    -
    bbaaef
                 /* ICMPv6 echo reply.  These flows reply to echo requests
    bbaaef
                  * received for the router's IP address. */
    bbaaef
                 ds_clear(&match);
    bbaaef
    @@ -7083,13 +7241,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                             "next; ");
    bbaaef
                 ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
    bbaaef
                               ds_cstr(&match), ds_cstr(&actions));
    bbaaef
    -
    bbaaef
    -            /* Drop IPv6 traffic to this router. */
    bbaaef
    -            ds_clear(&match);
    bbaaef
    -            ds_put_cstr(&match, "ip6.dst == ");
    bbaaef
    -            op_put_v6_networks(&match, op);
    bbaaef
    -            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60,
    bbaaef
    -                          ds_cstr(&match), "drop;");
    bbaaef
             }
    bbaaef
     
    bbaaef
             /* ND reply.  These flows reply to ND solicitations for the
    bbaaef
    @@ -7231,11 +7382,11 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                 continue;
    bbaaef
             }
    bbaaef
     
    bbaaef
    -        ovs_be32 snat_ip;
    bbaaef
    +        struct v46_ip snat_ip, lb_snat_ip;
    bbaaef
             const char *dnat_force_snat_ip = get_force_snat_ip(od, "dnat",
    bbaaef
                                                                &snat_ip);
    bbaaef
             const char *lb_force_snat_ip = get_force_snat_ip(od, "lb",
    bbaaef
    -                                                         &snat_ip);
    bbaaef
    +                                                         &lb_snat_ip);
    bbaaef
     
    bbaaef
             for (int i = 0; i < od->nbr->n_nat; i++) {
    bbaaef
                 const struct nbrec_nat *nat;
    bbaaef
    @@ -7243,21 +7394,38 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                 nat = od->nbr->nat[i];
    bbaaef
     
    bbaaef
                 ovs_be32 ip, mask;
    bbaaef
    +            struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
    bbaaef
    +            bool is_v6 = false;
    bbaaef
     
    bbaaef
                 char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
    bbaaef
                 if (error || mask != OVS_BE32_MAX) {
    bbaaef
    -                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    -                VLOG_WARN_RL(&rl, "bad external ip %s for nat",
    bbaaef
    -                             nat->external_ip);
    bbaaef
                     free(error);
    bbaaef
    -                continue;
    bbaaef
    +                error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
    bbaaef
    +                if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
    bbaaef
    +                    /* Invalid for both IPv4 and IPv6 */
    bbaaef
    +                    static struct vlog_rate_limit rl =
    bbaaef
    +                        VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
    +                    VLOG_WARN_RL(&rl, "bad external ip %s for nat",
    bbaaef
    +                                 nat->external_ip);
    bbaaef
    +                    free(error);
    bbaaef
    +                    continue;
    bbaaef
    +                }
    bbaaef
    +                /* It was an invalid IPv4 address, but valid IPv6.
    bbaaef
    +                 * Treat the rest of the handling of this NAT rule
    bbaaef
    +                 * as IPv6. */
    bbaaef
    +                is_v6 = true;
    bbaaef
                 }
    bbaaef
     
    bbaaef
                 /* Check the validity of nat->logical_ip. 'logical_ip' can
    bbaaef
                  * be a subnet when the type is "snat". */
    bbaaef
    -            error = ip_parse_masked(nat->logical_ip, &ip, &mask);
    bbaaef
    +            if (is_v6) {
    bbaaef
    +                error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
    bbaaef
    +            } else {
    bbaaef
    +                error = ip_parse_masked(nat->logical_ip, &ip, &mask);
    bbaaef
    +            }
    bbaaef
                 if (!strcmp(nat->type, "snat")) {
    bbaaef
                     if (error) {
    bbaaef
    +                    /* Invalid for both IPv4 and IPv6 */
    bbaaef
                         static struct vlog_rate_limit rl =
    bbaaef
                             VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
                         VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
    bbaaef
    @@ -7267,7 +7435,10 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                         continue;
    bbaaef
                     }
    bbaaef
                 } else {
    bbaaef
    -                if (error || mask != OVS_BE32_MAX) {
    bbaaef
    +                if (error || (!is_v6 && mask != OVS_BE32_MAX)
    bbaaef
    +                    || (is_v6 && memcmp(&mask_v6, &v6_exact,
    bbaaef
    +                                        sizeof mask_v6))) {
    bbaaef
    +                    /* Invalid for both IPv4 and IPv6 */
    bbaaef
                         static struct vlog_rate_limit rl =
    bbaaef
                             VLOG_RATE_LIMIT_INIT(5, 1);
    bbaaef
                         VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
    bbaaef
    @@ -7308,7 +7479,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                     if (!od->l3dgw_port) {
    bbaaef
                         /* Gateway router. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.dst == %s",
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.dst == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->external_ip);
    bbaaef
                         ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90,
    bbaaef
                                       ds_cstr(&match), "ct_snat;");
    bbaaef
    @@ -7317,8 +7489,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
     
    bbaaef
                         /* Traffic received on l3dgw_port is subject to NAT. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.dst == %s"
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.dst == %s"
    bbaaef
                                               " && inport == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->external_ip,
    bbaaef
                                       od->l3dgw_port->json_key);
    bbaaef
                         if (!distributed && od->l3redirect_port) {
    bbaaef
    @@ -7334,7 +7507,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                          * redirected to the central instance of the l3dgw_port
    bbaaef
                          * for NAT processing. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.dst == %s",
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.dst == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->external_ip);
    bbaaef
                         ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 50,
    bbaaef
                                       ds_cstr(&match),
    bbaaef
    @@ -7353,7 +7527,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                          * We need to set flags.loopback because the router can
    bbaaef
                          * send the packet back through the same interface. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.dst == %s",
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.dst == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->external_ip);
    bbaaef
                         ds_clear(&actions);
    bbaaef
                         if (dnat_force_snat_ip) {
    bbaaef
    @@ -7372,8 +7547,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
     
    bbaaef
                         /* Traffic received on l3dgw_port is subject to NAT. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.dst == %s"
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.dst == %s"
    bbaaef
                                               " && inport == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->external_ip,
    bbaaef
                                       od->l3dgw_port->json_key);
    bbaaef
                         if (!distributed && od->l3redirect_port) {
    bbaaef
    @@ -7392,7 +7568,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                          * redirected to the central instance of the l3dgw_port
    bbaaef
                          * for NAT processing. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.dst == %s",
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.dst == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->external_ip);
    bbaaef
                         ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
    bbaaef
                                       ds_cstr(&match),
    bbaaef
    @@ -7411,8 +7588,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                 if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
    bbaaef
                     || !strcmp(nat->type, "dnat_and_snat"))) {
    bbaaef
                     ds_clear(&match);
    bbaaef
    -                ds_put_format(&match, "ip && ip4.src == %s"
    bbaaef
    +                ds_put_format(&match, "ip && ip%s.src == %s"
    bbaaef
                                           " && outport == %s",
    bbaaef
    +                              is_v6 ? "6" : "4",
    bbaaef
                                   nat->logical_ip,
    bbaaef
                                   od->l3dgw_port->json_key);
    bbaaef
                     if (!distributed && od->l3redirect_port) {
    bbaaef
    @@ -7439,7 +7617,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                     if (!od->l3dgw_port) {
    bbaaef
                         /* Gateway router. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.src == %s",
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.src == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->logical_ip);
    bbaaef
                         ds_clear(&actions);
    bbaaef
                         ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
    bbaaef
    @@ -7455,8 +7634,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
     
    bbaaef
                         /* Distributed router. */
    bbaaef
                         ds_clear(&match);
    bbaaef
    -                    ds_put_format(&match, "ip && ip4.src == %s"
    bbaaef
    +                    ds_put_format(&match, "ip && ip%s.src == %s"
    bbaaef
                                               " && outport == %s",
    bbaaef
    +                                  is_v6 ? "6" : "4",
    bbaaef
                                       nat->logical_ip,
    bbaaef
                                       od->l3dgw_port->json_key);
    bbaaef
                         if (!distributed && od->l3redirect_port) {
    bbaaef
    @@ -7505,7 +7685,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                  * can be applied in a distributed manner. */
    bbaaef
                 if (distributed) {
    bbaaef
                     ds_clear(&match);
    bbaaef
    -                ds_put_format(&match, "ip4.src == %s && outport == %s",
    bbaaef
    +                ds_put_format(&match, "ip%s.src == %s && outport == %s",
    bbaaef
    +                              is_v6 ? "6" : "4",
    bbaaef
                                   nat->logical_ip,
    bbaaef
                                   od->l3dgw_port->json_key);
    bbaaef
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 100,
    bbaaef
    @@ -7532,9 +7713,10 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
     
    bbaaef
                             ds_clear(&match);
    bbaaef
                             ds_put_format(&match, "is_chassis_resident(\"%s\") && "
    bbaaef
    -                                      "ip4.src == %s && ip4.dst == %s",
    bbaaef
    -                                      nat->logical_port, nat2->external_ip,
    bbaaef
    -                                      nat->external_ip);
    bbaaef
    +                                      "ip%s.src == %s && ip%s.dst == %s",
    bbaaef
    +                                      nat->logical_port,
    bbaaef
    +                                      is_v6 ? "6" : "4", nat2->external_ip,
    bbaaef
    +                                      is_v6 ? "6" : "4", nat->external_ip);
    bbaaef
                             ds_clear(&actions);
    bbaaef
                             ds_put_format(&actions,
    bbaaef
                                           "inport = outport; outport = \"\"; "
    bbaaef
    @@ -7546,8 +7728,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
     
    bbaaef
                             ds_clear(&match);
    bbaaef
                             ds_put_format(&match,
    bbaaef
    -                                      "ip4.src == %s && ip4.dst == %s",
    bbaaef
    -                                      nat2->external_ip, nat->external_ip);
    bbaaef
    +                                      "ip%s.src == %s && ip%s.dst == %s",
    bbaaef
    +                                      is_v6 ? "6" : "4", nat2->external_ip,
    bbaaef
    +                                      is_v6 ? "6" : "4", nat->external_ip);
    bbaaef
                             ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 200,
    bbaaef
                                           ds_cstr(&match), "next;");
    bbaaef
                             ds_clear(&match);
    bbaaef
    @@ -7555,7 +7738,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                     }
    bbaaef
     
    bbaaef
                     ds_clear(&match);
    bbaaef
    -                ds_put_format(&match, "ip4.dst == %s && outport == %s",
    bbaaef
    +                ds_put_format(&match, "ip%s.dst == %s && outport == %s",
    bbaaef
    +                              is_v6 ? "6" : "4",
    bbaaef
                                   nat->external_ip,
    bbaaef
                                   od->l3dgw_port->json_key);
    bbaaef
                     ds_clear(&actions);
    bbaaef
    @@ -7579,7 +7763,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                  * gateway router (as set in options:dnat_force_snat_ip) is seen,
    bbaaef
                  * UNSNAT it. */
    bbaaef
                 ds_clear(&match);
    bbaaef
    -            ds_put_format(&match, "ip && ip4.dst == %s", dnat_force_snat_ip);
    bbaaef
    +            ds_put_format(&match, "ip && ip%s.dst == %s",
    bbaaef
    +                          snat_ip.family == AF_INET ? "4" : "6",
    bbaaef
    +                          dnat_force_snat_ip);
    bbaaef
                 ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 110,
    bbaaef
                               ds_cstr(&match), "ct_snat;");
    bbaaef
     
    bbaaef
    @@ -7598,7 +7784,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                  * gateway router (as set in options:lb_force_snat_ip) is seen,
    bbaaef
                  * UNSNAT it. */
    bbaaef
                 ds_clear(&match);
    bbaaef
    -            ds_put_format(&match, "ip && ip4.dst == %s", lb_force_snat_ip);
    bbaaef
    +            ds_put_format(&match, "ip && ip%s.dst == %s",
    bbaaef
    +                          lb_snat_ip.family == AF_INET ? "4" : "6",
    bbaaef
    +                          lb_force_snat_ip);
    bbaaef
                 ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
    bbaaef
                               ds_cstr(&match), "ct_snat;");
    bbaaef
     
    bbaaef
    @@ -7700,7 +7888,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                         if (addr_family == AF_INET) {
    bbaaef
                             ds_put_format(&match, "ip && ip4.dst == %s",
    bbaaef
                                           ip_address);
    bbaaef
    -                    } else {
    bbaaef
    +                    } else if (addr_family == AF_INET6) {
    bbaaef
                             ds_put_format(&match, "ip && ip6.dst == %s",
    bbaaef
                                           ip_address);
    bbaaef
                         }
    bbaaef
    @@ -7720,7 +7908,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                     if (addr_family == AF_INET) {
    bbaaef
                         ds_put_format(&match, "ip && ip4.dst == %s",
    bbaaef
                                     ip_address);
    bbaaef
    -                } else {
    bbaaef
    +                } else if (addr_family == AF_INET6) {
    bbaaef
                         ds_put_format(&match, "ip && ip6.dst == %s",
    bbaaef
                                     ip_address);
    bbaaef
                     }
    bbaaef
    -- 
    bbaaef
    2.23.0
    bbaaef