diff options
-rw-r--r-- | ovn/lib/actions.c | 83 | ||||
-rw-r--r-- | ovn/northd/ovn-northd.8.xml | 131 | ||||
-rw-r--r-- | ovn/northd/ovn-northd.c | 187 | ||||
-rw-r--r-- | ovn/ovn-nb.ovsschema | 19 | ||||
-rw-r--r-- | ovn/ovn-nb.xml | 65 | ||||
-rw-r--r-- | ovn/ovn-sb.xml | 41 | ||||
-rw-r--r-- | ovn/utilities/ovn-nbctl.c | 5 | ||||
-rw-r--r-- | tests/ovn.at | 17 |
8 files changed, 524 insertions, 24 deletions
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 5f0bf1966..4a486a002 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -442,6 +442,85 @@ emit_ct(struct action_context *ctx, bool recirc_next, bool commit) add_prerequisite(ctx, "ip"); } +static void +parse_ct_nat(struct action_context *ctx, bool snat) +{ + const size_t ct_offset = ctx->ofpacts->size; + ofpbuf_pull(ctx->ofpacts, ct_offset); + + struct ofpact_conntrack *ct = ofpact_put_CT(ctx->ofpacts); + + if (ctx->ap->cur_ltable < ctx->ap->n_tables) { + ct->recirc_table = ctx->ap->first_ptable + ctx->ap->cur_ltable + 1; + } else { + action_error(ctx, + "\"ct_[sd]nat\" action not allowed in last table."); + return; + } + + if (snat) { + ct->zone_src.field = mf_from_id(MFF_LOG_SNAT_ZONE); + } else { + ct->zone_src.field = mf_from_id(MFF_LOG_DNAT_ZONE); + } + ct->zone_src.ofs = 0; + ct->zone_src.n_bits = 16; + ct->flags = 0; + ct->alg = 0; + + add_prerequisite(ctx, "ip"); + + struct ofpact_nat *nat; + size_t nat_offset; + nat_offset = ctx->ofpacts->size; + ofpbuf_pull(ctx->ofpacts, nat_offset); + + nat = ofpact_put_NAT(ctx->ofpacts); + nat->flags = 0; + nat->range_af = AF_UNSPEC; + + int commit = 0; + if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { + ovs_be32 ip; + if (ctx->lexer->token.type == LEX_T_INTEGER + && ctx->lexer->token.format == LEX_F_IPV4) { + ip = ctx->lexer->token.value.ipv4; + } else { + action_syntax_error(ctx, "invalid ip"); + return; + } + + nat->range_af = AF_INET; + nat->range.addr.ipv4.min = ip; + if (snat) { + nat->flags |= NX_NAT_F_SRC; + } else { + nat->flags |= NX_NAT_F_DST; + } + commit = NX_CT_F_COMMIT; + lexer_get(ctx->lexer); + if (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { + action_syntax_error(ctx, "expecting `)'"); + return; + } + } + + ctx->ofpacts->header = ofpbuf_push_uninit(ctx->ofpacts, nat_offset); + ct = ctx->ofpacts->header; + ct->flags |= commit; + + /* XXX: For performance reasons, we try to prevent additional + * recirculations. So far, ct_snat which is used in a gateway router + * does not need a recirculation. ct_snat(IP) does need a recirculation. + * Should we consider a method to let the actions specify whether a action + * needs recirculation if there more use cases?. */ + if (!commit && snat) { + ct->recirc_table = NX_CT_RECIRC_NONE; + } + ofpact_finish(ctx->ofpacts, &ct->ofpact); + ofpbuf_push_uninit(ctx->ofpacts, ct_offset); +} + static bool parse_action(struct action_context *ctx) { @@ -469,6 +548,10 @@ parse_action(struct action_context *ctx) emit_ct(ctx, true, false); } else if (lexer_match_id(ctx->lexer, "ct_commit")) { emit_ct(ctx, false, true); + } else if (lexer_match_id(ctx->lexer, "ct_dnat")) { + parse_ct_nat(ctx, false); + } else if (lexer_match_id(ctx->lexer, "ct_snat")) { + parse_ct_nat(ctx, true); } else if (lexer_match_id(ctx->lexer, "arp")) { parse_arp_action(ctx); } else if (lexer_match_id(ctx->lexer, "get_arp")) { diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 0e5912588..22edba9c4 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -517,11 +517,40 @@ next; <li> <p> - Reply to ARP requests. These flows reply to ARP requests for the - router's own IP address. For each router port <var>P</var> that owns - IP address <var>A</var> and Ethernet address <var>E</var>, a - priority-90 flow matches <code>inport == <var>P</var> && - arp.op == 1 && arp.tpa == <var>A</var></code> (ARP request) + Reply to ARP requests. + </p> + + <p> + These flows reply to ARP requests for the router's own IP address. + For each router port <var>P</var> that owns IP address <var>A</var> + and Ethernet address <var>E</var>, a priority-90 flow matches + <code>inport == <var>P</var> && arp.op == 1 && + arp.tpa == <var>A</var></code> (ARP request) with the following + actions: + </p> + + <pre> +eth.dst = eth.src; +eth.src = <var>E</var>; +arp.op = 2; /* ARP reply. */ +arp.tha = arp.sha; +arp.sha = <var>E</var>; +arp.tpa = arp.spa; +arp.spa = <var>A</var>; +outport = <var>P</var>; +inport = ""; /* Allow sending out inport. */ +output; + </pre> + </li> + + <li> + <p> + These flows reply to ARP requests for the virtual IP addresses + configured in the router for DNAT. For a configured DNAT IP address + <var>A</var>, for each router port <var>P</var> with Ethernet + address <var>E</var>, a priority-90 flow matches + <code>inport == <var>P</var> && arp.op == 1 && + arp.tpa == <var>A</var></code> (ARP request) with the following actions: </p> @@ -663,7 +692,62 @@ icmp4 { </li> </ul> - <h3>Ingress Table 2: IP Routing</h3> + <h3>Ingress Table 2: UNSNAT</h3> + + <p> + This is for already established connections' reverse traffic. + i.e., SNAT has already been done in egress pipeline and now the + packet has entered the ingress pipeline as part of a reply. It is + unSNATted here. + </p> + + <ul> + <li> + <p> + For each configuration in the OVN Northbound database, that asks + to change the source IP address of a packet from <var>A</var> to + <var>B</var>, a priority-100 flow matches <code>ip && + ip4.dst == <var>B</var></code> with an action + <code>ct_snat; next;</code>. + </p> + + <p> + A priority-0 logical flow with match <code>1</code> has actions + <code>next;</code>. + </p> + </li> + </ul> + + <h3>Ingress Table 3: DNAT</h3> + + <p> + Packets enter the pipeline with destination IP address that needs to + be DNATted from a virtual IP address to a real IP address. Packets + in the reverse direction needs to be unDNATed. + </p> + <ul> + <li> + <p> + For each configuration in the OVN Northbound database, that asks + to change the destination IP address of a packet from <var>A</var> to + <var>B</var>, a priority-100 flow matches <code>ip && + ip4.dst == <var>A</var></code> with an action <code>inport = ""; + ct_dnat(<var>B</var>);</code>. + </p> + + <p> + For all IP packets of a Gateway router, a priority-50 flow with an + action <code>inport = ""; ct_dnat;</code>. + </p> + + <p> + A priority-0 logical flow with match <code>1</code> has actions + <code>next;</code>. + </p> + </li> + </ul> + + <h3>Ingress Table 4: IP Routing</h3> <p> A packet that arrives at this table is an IP packet that should be routed @@ -672,7 +756,7 @@ icmp4 { <code>ip4.dst</code>, the packet's final destination, unchanged) and advances to the next table for ARP resolution. It also sets <code>reg1</code> to the IP address owned by the selected router port - (which is used later in table 4 as the IP source address for an ARP + (which is used later in table 6 as the IP source address for an ARP request, if needed). </p> @@ -743,7 +827,7 @@ icmp4 { </li> </ul> - <h3>Ingress Table 3: ARP Resolution</h3> + <h3>Ingress Table 5: ARP Resolution</h3> <p> Any packet that reaches this table is an IP packet whose next-hop IP @@ -798,7 +882,7 @@ icmp4 { </li> </ul> - <h3>Ingress Table 4: ARP Request</h3> + <h3>Ingress Table 6: ARP Request</h3> <p> In the common case where the Ethernet destination has been resolved, this @@ -823,7 +907,7 @@ arp { </pre> <p> - (Ingress table 2 initialized <code>reg1</code> with the IP address + (Ingress table 4 initialized <code>reg1</code> with the IP address owned by <code>outport</code>.) </p> @@ -838,7 +922,32 @@ arp { </li> </ul> - <h3>Egress Table 0: Delivery</h3> + <h3>Egress Table 0: SNAT</h3> + + <p> + Packets that are configured to be SNATed get their source IP address + changed based on the configuration in the OVN Northbound database. + </p> + <ul> + <li> + <p> + For each configuration in the OVN Northbound database, that asks + to change the source IP address of a packet from an IP address of + <var>A</var> or to change the source IP address of a packet that + belongs to network <var>A</var> to <var>B</var>, a flow matches + <code>ip && ip4.src == <var>A</var></code> with an action + <code>ct_snat(<var>B</var>);</code>. The priority of the flow + is calculated based on the mask of <var>A</var>, with matches + having larger masks getting higher priorities. + </p> + <p> + A priority-0 logical flow with match <code>1</code> has actions + <code>next;</code>. + </p> + </li> + </ul> + + <h3>Egress Table 1: Delivery</h3> <p> Packets that reach this table are ready for delivery. It contains diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index d53fca9f4..17713eca3 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -105,12 +105,15 @@ enum ovn_stage { /* Logical router ingress stages. */ \ PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 2, "lr_in_ip_routing") \ - PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 3, "lr_in_arp_resolve") \ - PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 4, "lr_in_arp_request") \ + PIPELINE_STAGE(ROUTER, IN, UNSNAT, 2, "lr_in_unsnat") \ + PIPELINE_STAGE(ROUTER, IN, DNAT, 3, "lr_in_dnat") \ + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 4, "lr_in_ip_routing") \ + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 5, "lr_in_arp_resolve") \ + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 6, "lr_in_arp_request") \ \ /* Logical router egress stages. */ \ - PIPELINE_STAGE(ROUTER, OUT, DELIVERY, 0, "lr_out_delivery") + PIPELINE_STAGE(ROUTER, OUT, SNAT, 0, "lr_out_snat") \ + PIPELINE_STAGE(ROUTER, OUT, DELIVERY, 1, "lr_out_delivery") #define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME) \ S_##DP_TYPE##_##PIPELINE##_##STAGE \ @@ -1998,6 +2001,51 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, free(match); free(actions); + /* ARP handling for external IP addresses. + * + * DNAT IP addresses are external IP addresses that need ARP + * handling. */ + for (int i = 0; i < op->od->nbr->n_nat; i++) { + const struct nbrec_nat *nat; + + nat = op->od->nbr->nat[i]; + + if(!strcmp(nat->type, "snat")) { + continue; + } + + ovs_be32 ip; + if (!ip_parse(nat->external_ip, &ip) || !ip) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ip address %s in dnat configuration " + "for router %s", nat->external_ip, op->key); + continue; + } + + match = xasprintf( + "inport == %s && arp.tpa == "IP_FMT" && arp.op == 1", + op->json_key, IP_ARGS(ip)); + actions = xasprintf( + "eth.dst = eth.src; " + "eth.src = "ETH_ADDR_FMT"; " + "arp.op = 2; /* ARP reply */ " + "arp.tha = arp.sha; " + "arp.sha = "ETH_ADDR_FMT"; " + "arp.tpa = arp.spa; " + "arp.spa = "IP_FMT"; " + "outport = %s; " + "inport = \"\"; /* Allow sending out inport. */ " + "output;", + ETH_ADDR_ARGS(op->mac), + ETH_ADDR_ARGS(op->mac), + IP_ARGS(ip), + op->json_key); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, + match, actions); + free(match); + free(actions); + } + /* Drop IP traffic to this router. */ match = xasprintf("ip4.dst == "IP_FMT, IP_ARGS(op->ip)); ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60, @@ -2005,6 +2053,135 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, free(match); } + /* NAT in Gateway routers. */ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbr) { + continue; + } + + /* Packets are allowed by default. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); + + /* NAT rules are only valid on Gateway routers. */ + if (!smap_get(&od->nbr->options, "chassis")) { + continue; + } + + for (int i = 0; i < od->nbr->n_nat; i++) { + const struct nbrec_nat *nat; + + nat = od->nbr->nat[i]; + + ovs_be32 ip, mask; + + char *error = ip_parse_masked(nat->external_ip, &ip, &mask); + if (error || mask != OVS_BE32_MAX) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad external ip %s for nat", + nat->external_ip); + free(error); + continue; + } + + /* Check the validity of nat->logical_ip. 'logical_ip' can + * be a subnet when the type is "snat". */ + error = ip_parse_masked(nat->logical_ip, &ip, &mask); + if (!strcmp(nat->type, "snat")) { + if (error) { + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " + "in router "UUID_FMT"", + nat->logical_ip, UUID_ARGS(&od->key)); + free(error); + continue; + } + } else { + if (error || mask != OVS_BE32_MAX) { + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " + ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); + free(error); + continue; + } + } + + + char *match, *actions; + + /* Ingress UNSNAT table: It is for already established connections' + * reverse traffic. i.e., SNAT has already been done in egress + * pipeline and now the packet has entered the ingress pipeline as + * part of a reply. We undo the SNAT here. + * + * Undoing SNAT has to happen before DNAT processing. This is + * because when the packet was DNATed in ingress pipeline, it did + * not know about the possibility of eventual additional SNAT in + * egress pipeline. */ + if (!strcmp(nat->type, "snat") + || !strcmp(nat->type, "dnat_and_snat")) { + match = xasprintf("ip && ip4.dst == %s", nat->external_ip); + ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100, + match, "ct_snat; next;"); + free(match); + } + + /* Ingress DNAT table: Packets enter the pipeline with destination + * IP address that needs to be DNATted from a external IP address + * to a logical IP address. */ + if (!strcmp(nat->type, "dnat") + || !strcmp(nat->type, "dnat_and_snat")) { + /* Packet when it goes from the initiator to destination. + * We need to zero the inport because the router can + * send the packet back through the same interface. */ + match = xasprintf("ip && ip4.dst == %s", nat->external_ip); + actions = xasprintf("inport = \"\"; ct_dnat(%s);", + nat->logical_ip); + ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100, + match, actions); + free(match); + free(actions); + } + + /* Egress SNAT table: Packets enter the egress pipeline with + * source ip address that needs to be SNATted to a external ip + * address. */ + if (!strcmp(nat->type, "snat") + || !strcmp(nat->type, "dnat_and_snat")) { + match = xasprintf("ip && ip4.src == %s", nat->logical_ip); + actions = xasprintf("ct_snat(%s);", nat->external_ip); + + /* The priority here is calculated such that the + * nat->logical_ip with the longest mask gets a higher + * priority. */ + ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, + count_1bits(ntohl(mask)) + 1, match, actions); + free(match); + free(actions); + } + } + + /* Re-circulate every packet through the DNAT zone. + * This helps with two things. + * + * 1. Any packet that needs to be unDNATed in the reverse + * direction gets unDNATed. Ideally this could be done in + * the egress pipeline. But since the gateway router + * does not have any feature that depends on the source + * ip address being external IP address for IP routing, + * we can do it here, saving a future re-circulation. + * + * 2. Any packet that was sent through SNAT zone in the + * previous table automatically gets re-circulated to get + * back the new destination IP address that is needed for + * routing in the openflow pipeline. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, + "ip", "inport = \"\"; ct_dnat;"); + } + /* Logical router ingress table 2: IP Routing. * * A packet that arrives at this table is an IP packet that should be @@ -2206,7 +2383,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;"); } - /* Logical router egress table 0: Delivery (priority 100). + /* Logical router egress table 1: Delivery (priority 100). * * Priority 100 rules deliver packets to enabled logical ports. */ HMAP_FOR_EACH (op, key_node, ports) { diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index 95eb4f7ee..58f04b2f5 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "3.0.0", - "cksum": "1808140260 5339", + "version": "3.1.0", + "cksum": "1426508118 6135", "tables": { "Logical_Switch": { "columns": { @@ -78,6 +78,11 @@ "max": "unlimited"}}, "default_gw": {"type": {"key": "string", "min": 0, "max": 1}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, + "nat": {"type": {"key": {"type": "uuid", + "refTable": "NAT", + "refType": "strong"}, + "min": 0, + "max": "unlimited"}}, "options": { "type": {"key": "string", "value": "string", @@ -104,6 +109,16 @@ "ip_prefix": {"type": "string"}, "nexthop": {"type": "string"}, "output_port": {"type": {"key": "string", "min": 0, "max": 1}}}, + "isRoot": false}, + "NAT": { + "columns": { + "external_ip": {"type": "string"}, + "logical_ip": {"type": "string"}, + "type": {"type": {"key": {"type": "string", + "enum": ["set", ["dnat", + "snat", + "dnat_and_snat" + ]]}}}}, "isRoot": false} } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index d0dc597ff..6355c440d 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -631,18 +631,31 @@ router has all ingress and egress traffic dropped. </column> + <column name="nat"> + One or more NAT rules for the router. NAT rules only work on the + Gateway routers. + </column> + <group title="Options"> <p> Additional options for the logical router. </p> <column name="options" key="chassis"> - If set, indicates that the logical router in question is - a Gateway router (which is centralized) and resides in the set - chassis. The same value is also used by <code>ovn-controller</code> - to uniquely identify the chassis in the OVN deployment and - comes from <code>external_ids:system-id</code> in the - <code>Open_vSwitch</code> table of Open_vSwitch database. + <p> + If set, indicates that the logical router in question is a Gateway + router (which is centralized) and resides in the set chassis. The + same value is also used by <code>ovn-controller</code> to + uniquely identify the chassis in the OVN deployment and + comes from <code>external_ids:system-id</code> in the + <code>Open_vSwitch</code> table of Open_vSwitch database. + </p> + + <p> + The Gateway router can only be connected to a distributed router + via a switch if SNAT and DNAT are to be configured in the Gateway + router. + </p> </column> </group> @@ -766,4 +779,44 @@ </column> </table> + <table name="NAT" title="NAT rules for a Gateway router."> + <p> + Each record represents a NAT rule in a Gateway router. + </p> + + <column name="type"> + <p>Type of the NAT rule.</p> + <ul> + <li> + When <ref column="type"/> is <code>dnat</code>, the externally + visible IP address <ref column="external_ip"/> is DNATted to the IP + address <ref column="logical_ip"/> in the logical space. + </li> + <li> + When <ref column="type"/> is <code>snat</code>, IP packets + with their source IP address that either matches the IP address + in <ref column="logical_ip"/> or is in the network provided by + <ref column="logical_ip"/> is SNATed into the IP address in + <ref column="external_ip"/>. + </li> + <li> + When <ref column="type"/> is <code>dnat_and_snat</code>, the + externally visible IP address <ref column="external_ip"/> is + DNATted to the IP address <ref column="logical_ip"/> in the + logical space. In addition, IP packets with the source IP + address that matches <ref column="logical_ip"/> is SNATed into + the IP address in <ref column="external_ip"/>. + </li> + </ul> + </column> + + <column name="external_ip"> + An IPv4 address. + </column> + + <column name="logical_ip"> + An IPv4 network (e.g 192.168.1.0/24) or an IPv4 address. + </column> + </table> + </database> diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index d877f767c..e9353f336 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -951,6 +951,47 @@ </p> </dd> + <dt><code>ct_dnat;</code></dt> + <dt><code>ct_dnat(<var>IP</var>);</code></dt> + <dd> + <p> + <code>ct_dnat</code> sends the packet through the DNAT zone in + connection tracking table to unDNAT any packet that was DNATed in + the opposite direction. The packet is then automatically sent to + to the next tables as if followed by <code>next;</code> action. + The next tables will see the changes in the packet caused by + the connection tracker. + </p> + <p> + <code>ct_dnat(<var>IP</var>)</code> sends the packet through the + DNAT zone to change the destination IP address of the packet to + the one provided inside the parenthesis and commits the connection. + The packet is then automatically sent to the next tables as if + followed by <code>next;</code> action. The next tables will see + the changes in the packet caused by the connection tracker. + </p> + </dd> + + <dt><code>ct_snat;</code></dt> + <dt><code>ct_snat(<var>IP</var>);</code></dt> + <dd> + <p> + <code>ct_snat</code> sends the packet through the SNAT zone to + unSNAT any packet that was SNATed in the opposite direction. If + the packet needs to be sent to the next tables, then it should be + followed by a <code>next;</code> action. The next tables will not + see the changes in the packet caused by the connection tracker. + </p> + <p> + <code>ct_snat(<var>IP</var>)</code> sends the packet through the + SNAT zone to change the source IP address of the packet to + the one provided inside the parenthesis and commits the connection. + The packet is then automatically sent to the next tables as if + followed by <code>next;</code> action. The next tables will see the + changes in the packet caused by the connection tracker. + </p> + </dd> + <dt><code>arp { <var>action</var>; </code>...<code> };</code></dt> <dd> <p> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index a3bd619d6..7789cddd0 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -1861,6 +1861,11 @@ static const struct ctl_table_class tables[] = { NULL}, {NULL, NULL, NULL}}}, + {&nbrec_table_nat, + {{&nbrec_table_nat, NULL, + NULL}, + {NULL, NULL, NULL}}}, + {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}} }; diff --git a/tests/ovn.at b/tests/ovn.at index ad209741c..a52def492 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -507,6 +507,23 @@ ip.ttl => Syntax error at end of input expecting `--'. ct_next; => actions=ct(table=27,zone=NXM_NX_REG5[0..15]), prereqs=ip ct_commit; => actions=ct(commit,zone=NXM_NX_REG5[0..15]), prereqs=ip +# dnat +ct_dnat; => actions=ct(table=27,zone=NXM_NX_REG3[0..15],nat), prereqs=ip +ct_dnat(192.168.1.2); => actions=ct(commit,table=27,zone=NXM_NX_REG3[0..15],nat(dst=192.168.1.2)), prereqs=ip +ct_dnat(192.168.1.2, 192.168.1.3); => Syntax error at `,' expecting `)'. +ct_dnat(foo); => Syntax error at `foo' invalid ip. +ct_dnat(foo, bar); => Syntax error at `foo' invalid ip. +ct_dnat(); => Syntax error at `)' invalid ip. + +# snat +ct_snat; => actions=ct(zone=NXM_NX_REG4[0..15],nat), prereqs=ip +ct_snat(192.168.1.2); => actions=ct(commit,table=27,zone=NXM_NX_REG4[0..15],nat(src=192.168.1.2)), prereqs=ip +ct_snat(192.168.1.2, 192.168.1.3); => Syntax error at `,' expecting `)'. +ct_snat(foo); => Syntax error at `foo' invalid ip. +ct_snat(foo, bar); => Syntax error at `foo' invalid ip. +ct_snat(); => Syntax error at `)' invalid ip. + + # arp arp { eth.dst = ff:ff:ff:ff:ff:ff; output; }; => actions=controller(userdata=00.00.00.00.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=ip4 |