Re: [ovs-dev] [PATCH v4] ovn: DNAT and SNAT on a gateway router.

2016-06-21 Thread Guru Shetty
On 21 June 2016 at 10:29, Flaviof  wrote:

> On Tue, Jun 21, 2016 at 10:46 AM, Guru Shetty  wrote:
>
> >
> >
> > On 20 June 2016 at 19:36, Flaviof  wrote:
> >
> >> On Mon, Jun 13, 2016 at 6:45 AM, Gurucharan Shetty 
> wrote:
> >>
> >> > For traffic from physical space to virtual space we need DNAT.
> >> > The DNAT happens in the gateway router and reaches the logical
> >> > port. The return traffic should be unDNATed.
> >> >
> >> > Traffic originating in virtual space heading to physical space
> >> > should be SNATed. The return traffic is unSNATted.
> >> >
> >> > East-west traffic with the public destination IP address needs
> >> > a DNAT. This traffic is punted to the l3 gateway where DNAT
> >> > takes place. This traffic is also SNATed and eventually loops back to
> >> > its destination. The SNAT is needed because we need the reverse
> traffic
> >> > to go back to the l3 gateway and not short-circuit directly to the
> >> source.
> >> >
> >> > This commit introduces 4 new logical actions.
> >> > 1. ct_snat: To send the packet through SNAT zone to unSNAT packets.
> >> > 2. ct_snat(IP): To SNAT to the provided IP address.
> >> > 3. ct_dnat: To send the packet throgh DNAT zone to unDNAT packets.
> >> > 4. ct_dnat(IP): To DNAT to the provided IP.
> >> >
> >> > This commit only provides the ability to do IP based NAT. This will
> >> > eventually be enhanced to do PORT based NAT too.
> >> >
> >> > Command hints:
> >> >
> >> > Consider a distributed router "R1" that has switch foo (
> 192.168.1.0/24)
> >> > with a lport foo1 (192.168.1.2) and bar (192.168.2.0/24) with lport
> >> bar1
> >> > (192.168.2.2) connected to it. You connect "R1" to
> >> > a gateway router "R2" via a switch "join" in (20.0.0.0/24) network.
> >> >
> >> > R2 has a switch "alice" (172.16.1.0/24) connected to it (to simulate
> >> > external network).
> >> >
> >> > case: Add pure DNAT (north-south)
> >> >
> >> > Add a DNAT rule in R2:
> >> > ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
> >> > external_ip=30.0.0.2 -- add logical_router R2 nat @nat
> >> >
> >> > Now alice1 should be able to ping 192.168.1.2 via 30.0.0.2.
> >> >
> >> > case2 : Add pure SNAT (south-north)
> >> >
> >> > Add a SNAT rule in R2:
> >> >
> >> > ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
> >> > external_ip=30.0.0.1 -- add logical_router R2 nat @nat
> >> >
> >> > (You need a static route in R1 to send packets destined to outside
> >> > world to go through R2. The logical_ip can be a subnet.)
> >> >
> >> > When bar1 pings alice1, alice1 receives traffic from 30.0.0.1
> >> >
> >> > case3 : SNAT and DNAT (east-west traffic)
> >> >
> >> > When bar1 pings 30.0.0.2, the traffic jumps to the gateway router
> >> > and loops back to foo1 with a source ip address of 30.0.0.1
> >> >
> >> >
> >> So, is 30.0.0.0/x network an external network that R2 has a port too?
> >>
> >
> > The example above does not have that. In the above example 30.0.0.0/x is
> > being treated as virtual address. But in a real setup (non-simulated),
> you
> > are right. R2 will be connected to a 30.0.0.0/x network and will have a
> > port in it. It will also have a static route (0.0.0.0/0) or a
> > default_gateway to point to the physical router IP address as its next
> hop.
> > (I have not tested it as I do not have a real setup at hand, but based on
> > the simulation, it should ideally work.)
> >
> >
> >> What is the next hop that R2 would use to reach a destination beyond
> >> that subnet?
> >>
> > Answered above.
> >
>
> Ack!
>
>
> >
> >>
> >> I think this may be clear when a test is added to ovn.at, which uses
> foo,
> >> bar, join, alice
> >>
> > The unit tests do not have the ability to do conntrack NAT right now. I
> > think we should add one once Daniele introduces NAT to usespace
> conntrack.
> > But the unit test "ovn -- 2 HVs, 2 LRs connected via LS, gateway router"
> > does something very similar (it has foo - R1 - join - R2 - alice).
> >
>
> Right, I saw that test and it makes perfect sense. Adding the 'bar' logical
> switch, net 30.0.0.x and the nat rules are the few lines that it currently
> does not have.
>
>
> >
> >>
> >> Based on the code and my little test setup, there seems to be a high
> cost
> >> for DNAT entries in that an ARP response rule will be added per DNAT x
> all
> >> router ports.
> >
> > The intention was to add only on the router where DNAT entry is defined
> > and not on all router ports of all routers. Is it not true? (If so, this
> is
> > a bug. ). The for loop which adds this entry, only looks at that
> datapath's
> > NAT entries.
> >
> > On the gateway router itself, there would be typically two DNAT entries.
> > One of them connected to internal network (for east-west) and another one
> > at external port (facing physical router).
> >
> >
> Understood.
>
>
> >
> >
> >> In the example used by the commit message, ingress table 1 of
> >> the logical router will have arp response entries for inports alice and
> >> R2_j

Re: [ovs-dev] [PATCH v4] ovn: DNAT and SNAT on a gateway router.

2016-06-21 Thread Flaviof
On Tue, Jun 21, 2016 at 10:46 AM, Guru Shetty  wrote:

>
>
> On 20 June 2016 at 19:36, Flaviof  wrote:
>
>> On Mon, Jun 13, 2016 at 6:45 AM, Gurucharan Shetty  wrote:
>>
>> > For traffic from physical space to virtual space we need DNAT.
>> > The DNAT happens in the gateway router and reaches the logical
>> > port. The return traffic should be unDNATed.
>> >
>> > Traffic originating in virtual space heading to physical space
>> > should be SNATed. The return traffic is unSNATted.
>> >
>> > East-west traffic with the public destination IP address needs
>> > a DNAT. This traffic is punted to the l3 gateway where DNAT
>> > takes place. This traffic is also SNATed and eventually loops back to
>> > its destination. The SNAT is needed because we need the reverse traffic
>> > to go back to the l3 gateway and not short-circuit directly to the
>> source.
>> >
>> > This commit introduces 4 new logical actions.
>> > 1. ct_snat: To send the packet through SNAT zone to unSNAT packets.
>> > 2. ct_snat(IP): To SNAT to the provided IP address.
>> > 3. ct_dnat: To send the packet throgh DNAT zone to unDNAT packets.
>> > 4. ct_dnat(IP): To DNAT to the provided IP.
>> >
>> > This commit only provides the ability to do IP based NAT. This will
>> > eventually be enhanced to do PORT based NAT too.
>> >
>> > Command hints:
>> >
>> > Consider a distributed router "R1" that has switch foo (192.168.1.0/24)
>> > with a lport foo1 (192.168.1.2) and bar (192.168.2.0/24) with lport
>> bar1
>> > (192.168.2.2) connected to it. You connect "R1" to
>> > a gateway router "R2" via a switch "join" in (20.0.0.0/24) network.
>> >
>> > R2 has a switch "alice" (172.16.1.0/24) connected to it (to simulate
>> > external network).
>> >
>> > case: Add pure DNAT (north-south)
>> >
>> > Add a DNAT rule in R2:
>> > ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
>> > external_ip=30.0.0.2 -- add logical_router R2 nat @nat
>> >
>> > Now alice1 should be able to ping 192.168.1.2 via 30.0.0.2.
>> >
>> > case2 : Add pure SNAT (south-north)
>> >
>> > Add a SNAT rule in R2:
>> >
>> > ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
>> > external_ip=30.0.0.1 -- add logical_router R2 nat @nat
>> >
>> > (You need a static route in R1 to send packets destined to outside
>> > world to go through R2. The logical_ip can be a subnet.)
>> >
>> > When bar1 pings alice1, alice1 receives traffic from 30.0.0.1
>> >
>> > case3 : SNAT and DNAT (east-west traffic)
>> >
>> > When bar1 pings 30.0.0.2, the traffic jumps to the gateway router
>> > and loops back to foo1 with a source ip address of 30.0.0.1
>> >
>> >
>> So, is 30.0.0.0/x network an external network that R2 has a port too?
>>
>
> The example above does not have that. In the above example 30.0.0.0/x is
> being treated as virtual address. But in a real setup (non-simulated), you
> are right. R2 will be connected to a 30.0.0.0/x network and will have a
> port in it. It will also have a static route (0.0.0.0/0) or a
> default_gateway to point to the physical router IP address as its next hop.
> (I have not tested it as I do not have a real setup at hand, but based on
> the simulation, it should ideally work.)
>
>
>> What is the next hop that R2 would use to reach a destination beyond
>> that subnet?
>>
> Answered above.
>

Ack!


>
>>
>> I think this may be clear when a test is added to ovn.at, which uses foo,
>> bar, join, alice
>>
> The unit tests do not have the ability to do conntrack NAT right now. I
> think we should add one once Daniele introduces NAT to usespace conntrack.
> But the unit test "ovn -- 2 HVs, 2 LRs connected via LS, gateway router"
> does something very similar (it has foo - R1 - join - R2 - alice).
>

Right, I saw that test and it makes perfect sense. Adding the 'bar' logical
switch, net 30.0.0.x and the nat rules are the few lines that it currently
does not have.


>
>>
>> Based on the code and my little test setup, there seems to be a high cost
>> for DNAT entries in that an ARP response rule will be added per DNAT x all
>> router ports.
>
> The intention was to add only on the router where DNAT entry is defined
> and not on all router ports of all routers. Is it not true? (If so, this is
> a bug. ). The for loop which adds this entry, only looks at that datapath's
> NAT entries.
>
> On the gateway router itself, there would be typically two DNAT entries.
> One of them connected to internal network (for east-west) and another one
> at external port (facing physical router).
>
>
Understood.


>
>
>> In the example used by the commit message, ingress table 1 of
>> the logical router will have arp response entries for inports alice and
>> R2_join.
>>
> Right. That is because as explained above, I need to do DNAT for both
> east-west as well as north-south. (It is very possible that I did not
> understand your concern)
>

Nah, you set me straight. If there were multiple internal subnets I imagine
we will need a DNAT
rule for each, since the response nee

Re: [ovs-dev] [PATCH v4] ovn: DNAT and SNAT on a gateway router.

2016-06-21 Thread Guru Shetty
On 20 June 2016 at 19:36, Flaviof  wrote:

> On Mon, Jun 13, 2016 at 6:45 AM, Gurucharan Shetty  wrote:
>
> > For traffic from physical space to virtual space we need DNAT.
> > The DNAT happens in the gateway router and reaches the logical
> > port. The return traffic should be unDNATed.
> >
> > Traffic originating in virtual space heading to physical space
> > should be SNATed. The return traffic is unSNATted.
> >
> > East-west traffic with the public destination IP address needs
> > a DNAT. This traffic is punted to the l3 gateway where DNAT
> > takes place. This traffic is also SNATed and eventually loops back to
> > its destination. The SNAT is needed because we need the reverse traffic
> > to go back to the l3 gateway and not short-circuit directly to the
> source.
> >
> > This commit introduces 4 new logical actions.
> > 1. ct_snat: To send the packet through SNAT zone to unSNAT packets.
> > 2. ct_snat(IP): To SNAT to the provided IP address.
> > 3. ct_dnat: To send the packet throgh DNAT zone to unDNAT packets.
> > 4. ct_dnat(IP): To DNAT to the provided IP.
> >
> > This commit only provides the ability to do IP based NAT. This will
> > eventually be enhanced to do PORT based NAT too.
> >
> > Command hints:
> >
> > Consider a distributed router "R1" that has switch foo (192.168.1.0/24)
> > with a lport foo1 (192.168.1.2) and bar (192.168.2.0/24) with lport bar1
> > (192.168.2.2) connected to it. You connect "R1" to
> > a gateway router "R2" via a switch "join" in (20.0.0.0/24) network.
> >
> > R2 has a switch "alice" (172.16.1.0/24) connected to it (to simulate
> > external network).
> >
> > case: Add pure DNAT (north-south)
> >
> > Add a DNAT rule in R2:
> > ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
> > external_ip=30.0.0.2 -- add logical_router R2 nat @nat
> >
> > Now alice1 should be able to ping 192.168.1.2 via 30.0.0.2.
> >
> > case2 : Add pure SNAT (south-north)
> >
> > Add a SNAT rule in R2:
> >
> > ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
> > external_ip=30.0.0.1 -- add logical_router R2 nat @nat
> >
> > (You need a static route in R1 to send packets destined to outside
> > world to go through R2. The logical_ip can be a subnet.)
> >
> > When bar1 pings alice1, alice1 receives traffic from 30.0.0.1
> >
> > case3 : SNAT and DNAT (east-west traffic)
> >
> > When bar1 pings 30.0.0.2, the traffic jumps to the gateway router
> > and loops back to foo1 with a source ip address of 30.0.0.1
> >
> >
> So, is 30.0.0.0/x network an external network that R2 has a port too?
>

The example above does not have that. In the above example 30.0.0.0/x is
being treated as virtual address. But in a real setup (non-simulated), you
are right. R2 will be connected to a 30.0.0.0/x network and will have a
port in it. It will also have a static route (0.0.0.0/0) or a
default_gateway to point to the physical router IP address as its next hop.
(I have not tested it as I do not have a real setup at hand, but based on
the simulation, it should ideally work.)


> What is the next hop that R2 would use to reach a destination beyond
> that subnet?
>
Answered above.


>
> I think this may be clear when a test is added to ovn.at, which uses foo,
> bar, join, alice
>
The unit tests do not have the ability to do conntrack NAT right now. I
think we should add one once Daniele introduces NAT to usespace conntrack.
But the unit test "ovn -- 2 HVs, 2 LRs connected via LS, gateway router"
does something very similar (it has foo - R1 - join - R2 - alice).


>
> Based on the code and my little test setup, there seems to be a high cost
> for DNAT entries in that an ARP response rule will be added per DNAT x all
> router ports.

The intention was to add only on the router where DNAT entry is defined and
not on all router ports of all routers. Is it not true? (If so, this is a
bug. ). The for loop which adds this entry, only looks at that datapath's
NAT entries.

On the gateway router itself, there would be typically two DNAT entries.
One of them connected to internal network (for east-west) and another one
at external port (facing physical router).



> In the example used by the commit message, ingress table 1 of
> the logical router will have arp response entries for inports alice and
> R2_join.
>
Right. That is because as explained above, I need to do DNAT for both
east-west as well as north-south. (It is very possible that I did not
understand your concern)


>
>
> Table 3: do we really intend to apply the actions 'inport = ""; ct_dnat;'
> to all ip packets that do not have an explicit dnat mapping?
>
Yes. This is a little tricky. I have tried to explain the rationale in a
comment above. The general idea is that in a gateway router, there will be
atleast one DNAT or SNAT entry. Otherwise, why have a gateway router? Also,
a re-circulation is considered to be very expensive. What we want is to
minimize re-circulations. With the code above, we have a minimum of
one-recirculation

Re: [ovs-dev] [PATCH v4] ovn: DNAT and SNAT on a gateway router.

2016-06-20 Thread Flaviof
On Mon, Jun 13, 2016 at 6:45 AM, Gurucharan Shetty  wrote:

> For traffic from physical space to virtual space we need DNAT.
> The DNAT happens in the gateway router and reaches the logical
> port. The return traffic should be unDNATed.
>
> Traffic originating in virtual space heading to physical space
> should be SNATed. The return traffic is unSNATted.
>
> East-west traffic with the public destination IP address needs
> a DNAT. This traffic is punted to the l3 gateway where DNAT
> takes place. This traffic is also SNATed and eventually loops back to
> its destination. The SNAT is needed because we need the reverse traffic
> to go back to the l3 gateway and not short-circuit directly to the source.
>
> This commit introduces 4 new logical actions.
> 1. ct_snat: To send the packet through SNAT zone to unSNAT packets.
> 2. ct_snat(IP): To SNAT to the provided IP address.
> 3. ct_dnat: To send the packet throgh DNAT zone to unDNAT packets.
> 4. ct_dnat(IP): To DNAT to the provided IP.
>
> This commit only provides the ability to do IP based NAT. This will
> eventually be enhanced to do PORT based NAT too.
>
> Command hints:
>
> Consider a distributed router "R1" that has switch foo (192.168.1.0/24)
> with a lport foo1 (192.168.1.2) and bar (192.168.2.0/24) with lport bar1
> (192.168.2.2) connected to it. You connect "R1" to
> a gateway router "R2" via a switch "join" in (20.0.0.0/24) network.
>
> R2 has a switch "alice" (172.16.1.0/24) connected to it (to simulate
> external network).
>
> case: Add pure DNAT (north-south)
>
> Add a DNAT rule in R2:
> ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
> external_ip=30.0.0.2 -- add logical_router R2 nat @nat
>
> Now alice1 should be able to ping 192.168.1.2 via 30.0.0.2.
>
> case2 : Add pure SNAT (south-north)
>
> Add a SNAT rule in R2:
>
> ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
> external_ip=30.0.0.1 -- add logical_router R2 nat @nat
>
> (You need a static route in R1 to send packets destined to outside
> world to go through R2. The logical_ip can be a subnet.)
>
> When bar1 pings alice1, alice1 receives traffic from 30.0.0.1
>
> case3 : SNAT and DNAT (east-west traffic)
>
> When bar1 pings 30.0.0.2, the traffic jumps to the gateway router
> and loops back to foo1 with a source ip address of 30.0.0.1
>
>
So, is 30.0.0.0/x network an external network that R2 has a port too?
What is the next hop that R2 would use to reach a destination beyond
that subnet?

I think this may be clear when a test is added to ovn.at, which uses foo,
bar, join, alice

Based on the code and my little test setup, there seems to be a high cost
for DNAT entries in that an ARP response rule will be added per DNAT x all
router ports. In the example used by the commit message, ingress table 1 of
the logical router will have arp response entries for inports alice and
R2_join.

Is that expected? This may be very okay, since it only takes place on the
gateway router (i.e. logical router associated to a chassis)?

Side note: After adding DNAT, I tried removing and re-adding 'alice' and
'rp-alice'. As expected, the ARP reply rules we properly removed when the
ports were deleted, and properly re-added when the ports were re-created.
Nice!

Table 3: do we really intend to apply the actions 'inport = ""; ct_dnat;'
to all ip packets that do not have an explicit dnat mapping?

SNAT: do we need ARP reply rules for the SNAT addresses, similar to the
ones added for DNAT?

SNAT: looking at the openflow table I see n mentioning of the address added
to support SNAT. Ist that because that is all handled by connect_tracker
and there is nothing to be done via openflow? Or maybe part of another
patchset?

Thanks,

-- flaviof




> Signed-off-by: Gurucharan Shetty 
> ---
>  ovn/lib/actions.c   |  83 
>  ovn/northd/ovn-northd.8.xml | 131 ---
>  ovn/northd/ovn-northd.c | 187
> ++--
>  ovn/ovn-nb.ovsschema|  19 -
>  ovn/ovn-nb.xml  |  65 +--
>  ovn/ovn-sb.xml  |  41 ++
>  ovn/utilities/ovn-nbctl.c   |   5 ++
>  tests/ovn.at|  17 
>  8 files changed, 524 insertions(+), 24 deletions(-)
>
> diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
> index 5f0bf19..4a486a0 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,
> +

Re: [ovs-dev] [PATCH v4] ovn: DNAT and SNAT on a gateway router.

2016-06-16 Thread Guru Shetty
On 9 June 2016 at 00:37, Gurucharan Shetty  wrote:

> For traffic from physical space to virtual space we need DNAT.
> The DNAT happens in the gateway router and reaches the logical
> port. The return traffic should be unDNATed.
>
> Traffic originating in virtual space heading to physical space
> should be SNATed. The return traffic is unSNATted.
>
> East-west traffic with the public destination IP address needs
> a DNAT. This traffic is punted to the l3 gateway where DNAT
> takes place. This traffic is also SNATed and eventually loops back to
> its destination. The SNAT is needed because we need the reverse traffic
> to go back to the l3 gateway and not short-circuit directly to the source.
>
> This commit introduces 4 new logical actions.
> 1. ct_snat: To send the packet through SNAT zone to unSNAT packets.
> 2. ct_snat(IP): To SNAT to the provided IP address.
> 3. ct_dnat: To send the packet throgh DNAT zone to unDNAT packets.
> 4. ct_dnat(IP): To DNAT to the provided IP.
>
> This commit only provides the ability to do IP based NAT. This will
> eventually be enhanced to do PORT based NAT too.
>
> Command hints:
>
> Consider a distributed router "R1" that has switch foo (192.168.1.0/24)
> with a lport foo1 (192.168.1.2) and bar (192.168.2.0/24) with lport bar1
> (192.168.2.2) connected to it. You connect "R1" to
> a gateway router "R2" via a switch "join" in (20.0.0.0/24) network.
>
> R2 has a switch "alice" (172.16.1.0/24) connected to it (to simulate
> external network).
>
> case: Add pure DNAT (north-south)
>
> Add a DNAT rule in R2:
> ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
> external_ip=30.0.0.2 -- add logical_router R2 nat @nat
>
> Now alice1 should be able to ping 192.168.1.2 via 30.0.0.2.
>
> case2 : Add pure SNAT (south-north)
>
> Add a SNAT rule in R2:
>
> ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
> external_ip=30.0.0.1 -- add logical_router R2 nat @nat
>
> (You need a static route in R1 to send packets destined to outside
> world to go through R2. The logical_ip can be a subnet.)
>
> When bar1 pings alice1, alice1 receives traffic from 30.0.0.1
>
> One could combine case1 and case2 with nat type="dnat_and_snat"
> if the IP addresses are the same.
>
> case3 : SNAT and DNAT (east-west traffic)
>
> When bar1 pings 30.0.0.2, the traffic jumps to the gateway router
> and loops back to foo1 with a source ip address of 30.0.0.1
>
> Signed-off-by: Gurucharan Shetty 
> ---
> v3->v4:
> 1. Added unit tests and updated documentation based on blp's comments.
> 2. Changed schema to make it easier for OpenStack users.
>

This patch no longer applies on the tip of the master branch because of a
merge conflict. So for easier reference, I rebased it and pushed it here:
https://github.com/shettyg/ovs/tree/gateway


>
> ---
>  ovn/lib/actions.c   |  83 
>  ovn/northd/ovn-northd.8.xml | 131 ---
>  ovn/northd/ovn-northd.c | 187
> ++--
>  ovn/ovn-nb.ovsschema|  19 -
>  ovn/ovn-nb.xml  |  65 +--
>  ovn/ovn-sb.xml  |  41 ++
>  ovn/utilities/ovn-nbctl.c   |   5 ++
>  tests/ovn.at|  17 
>  8 files changed, 524 insertions(+), 24 deletions(-)
>
> diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
> index 5f0bf19..4a486a0 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 {
> +act