diff mbox series

[ovs-dev,v2,08/10] netdev-offload-tc: Add conntrack nat support

Message ID 20191127125516.5135-9-roid@mellanox.com
State Superseded
Headers show
Series Add support for offloading CT datapath rules to TC | expand

Commit Message

Roi Dayan Nov. 27, 2019, 12:55 p.m. UTC
From: Paul Blakey <paulb@mellanox.com>

Changelog:
V1->V2:
    Missing ntohs/htons on nat port range.

Signed-off-by: Paul Blakey <paulb@mellanox.com>
---
 lib/netdev-offload-tc.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/tc.c                |  91 ++++++++++++++++++++++++++++++++++++++++++
 lib/tc.h                |  24 +++++++++++
 3 files changed, 219 insertions(+)

Comments

Marcelo Leitner Nov. 27, 2019, 3:27 p.m. UTC | #1
On Wed, Nov 27, 2019 at 02:55:14PM +0200, Roi Dayan wrote:
> From: Paul Blakey <paulb@mellanox.com>
> 
> Changelog:
> V1->V2:
>     Missing ntohs/htons on nat port range.
> 
> Signed-off-by: Paul Blakey <paulb@mellanox.com>
> ---
>  lib/netdev-offload-tc.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>  lib/tc.c                |  91 ++++++++++++++++++++++++++++++++++++++++++
>  lib/tc.h                |  24 +++++++++++
>  3 files changed, 219 insertions(+)
> 
> diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
> index 2b4ecfb9ca48..3a9b9e144311 100644
> --- a/lib/netdev-offload-tc.c
> +++ b/lib/netdev-offload-tc.c
> @@ -814,6 +814,40 @@ parse_tc_flower_to_match(struct tc_flower *flower,
>                      ct_label->mask = action->ct.label_mask;
>                  }
>  
> +                if (action->ct.nat_type) {
> +                    size_t nat_offset = nl_msg_start_nested(buf,
> +                                                            OVS_CT_ATTR_NAT);
> +
> +                    if (action->ct.nat_type == TC_NAT_SRC) {
> +                        nl_msg_put_flag(buf, OVS_NAT_ATTR_SRC);
> +                    } else if (action->ct.nat_type == TC_NAT_DST) {
> +                        nl_msg_put_flag(buf, OVS_NAT_ATTR_DST);
> +                    }
> +
> +                    if (action->ct.range.ip_family == AF_INET) {
> +                        nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MIN,
                                                        ^^^^^^^^^^^
> +                                        action->ct.range.min_addr.ipv4);
                                                            ^^^^^^^^

Sorry, more bikeshedding here.
OVS seems to use _MIN and _MAX as suffix, and tc act ct also uses it
that way: TCA_CT_NAT_IPV4_MIN, etc.

I'm wondering, should we do the same for tc_action members here?


> +                        nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MAX,
> +                                        action->ct.range.max_addr.ipv4);
> +                    } else if (action->ct.range.ip_family == AF_INET6) {
> +                        nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MIN,
> +                                            &action->ct.range.min_addr.ipv6);
> +                        nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MAX,
> +                                            &action->ct.range.max_addr.ipv6);
> +                    }
> +
> +                    if (action->ct.range.min_port) {
> +                        nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MIN,
> +                                       ntohs(action->ct.range.min_port));
> +                        if (action->ct.range.max_port) {
> +                            nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MAX,
> +                                           ntohs(action->ct.range.max_port));
> +                        }
> +                    }
> +
> +                    nl_msg_end_nested(buf, nat_offset);
> +                }
> +
>                  nl_msg_end_nested(buf, ct_offset);
>              }
>              break;
> @@ -906,6 +940,66 @@ parse_mpls_set_action(struct tc_flower *flower, struct tc_action *action,
>  }
>  
>  static int
> +parse_put_flow_nat_action(struct tc_action *action,
> +                          const struct nlattr *nat,
> +                          size_t nat_len)
> +{
> +    const struct nlattr *nat_attr;
> +    size_t nat_left;
> +
> +    action->ct.nat_type = TC_NAT_RESTORE;
> +    NL_ATTR_FOR_EACH_UNSAFE (nat_attr, nat_left, nat, nat_len) {
> +        switch (nl_attr_type(nat_attr)) {
> +            case OVS_NAT_ATTR_SRC: {
> +                action->ct.nat_type = TC_NAT_SRC;
> +            };
> +            break;
> +            case OVS_NAT_ATTR_DST: {
> +                action->ct.nat_type = TC_NAT_DST;
> +            };
> +            break;
> +            case OVS_NAT_ATTR_IP_MIN: {
> +                if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) {
> +                    ovs_be32 addr = nl_attr_get_be32(nat_attr);
> +
> +                    action->ct.range.min_addr.ipv4 = addr;
> +                    action->ct.range.ip_family = AF_INET;
> +                } else {
> +                    struct in6_addr addr = nl_attr_get_in6_addr(nat_attr);
> +
> +                    action->ct.range.min_addr.ipv6 = addr;
> +                    action->ct.range.ip_family = AF_INET6;
> +                }
> +            };
> +            break;
> +            case OVS_NAT_ATTR_IP_MAX: {
> +                if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) {
> +                    ovs_be32 addr = nl_attr_get_be32(nat_attr);
> +
> +                    action->ct.range.max_addr.ipv4 = addr;
> +                    action->ct.range.ip_family = AF_INET;
> +                } else {
> +                    struct in6_addr addr = nl_attr_get_in6_addr(nat_attr);
> +
> +                    action->ct.range.max_addr.ipv6 = addr;
> +                    action->ct.range.ip_family = AF_INET6;
> +                }
> +            };
> +            break;
> +            case OVS_NAT_ATTR_PROTO_MIN: {
> +                action->ct.range.min_port = htons(nl_attr_get_u16(nat_attr));
> +            };
> +            break;
> +            case OVS_NAT_ATTR_PROTO_MAX: {
> +                action->ct.range.max_port = htons(nl_attr_get_u16(nat_attr));
> +            };
> +            break;
> +        }
> +    }
> +    return 0;
> +}
> +
> +static int
>  parse_put_flow_ct_action(struct tc_flower *flower,
>                           struct tc_action *action,
>                           const struct nlattr *ct,
> @@ -925,6 +1019,16 @@ parse_put_flow_ct_action(struct tc_flower *flower,
>                      action->ct.zone = nl_attr_get_u16(ct_attr);
>                  }
>                  break;
> +                case OVS_CT_ATTR_NAT: {
> +                    const struct nlattr *nat = nl_attr_get(ct_attr);
> +                    const size_t nat_len = nl_attr_get_size(ct_attr);
> +
> +                    err = parse_put_flow_nat_action(action, nat, nat_len);
> +                    if (err) {
> +                        return err;
> +                    }
> +                }
> +                break;
>                  case OVS_CT_ATTR_MARK: {
>                      const struct {
>                          uint32_t key;
> diff --git a/lib/tc.c b/lib/tc.c
> index 1062326d4b86..83ee0f6473aa 100644
> --- a/lib/tc.c
> +++ b/lib/tc.c
> @@ -1282,6 +1282,18 @@ static const struct nl_policy ct_policy[] = {
>                           .optional = true, },
>      [TCA_CT_LABELS_MASK] = { .type = NL_A_UNSPEC,
>                                .optional = true, },
> +    [TCA_CT_NAT_IPV4_MIN] = { .type = NL_A_U32,
> +                              .optional = true, },
> +    [TCA_CT_NAT_IPV4_MAX] = { .type = NL_A_U32,
> +                              .optional = true, },
> +    [TCA_CT_NAT_IPV6_MIN] = { .min_len = sizeof(struct in6_addr),
> +                               .optional = true, },
> +    [TCA_CT_NAT_IPV6_MAX] = { .min_len = sizeof(struct in6_addr),
> +                              .optional = true, },
> +    [TCA_CT_NAT_PORT_MIN] = { .type = NL_A_U16,
> +                              .optional = true, },
> +    [TCA_CT_NAT_PORT_MAX] = { .type = NL_A_U16,
> +                              .optional = true, },
>  };
>  
>  static int
> @@ -1325,6 +1337,47 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
>          action->ct.label_mask = label_mask ?
>                                  nl_attr_get_u128(label_mask) : OVS_U128_ZERO;
>  
> +        if (ct_action & TCA_CT_ACT_NAT) {
> +            struct nlattr *ipv4_min = ct_attrs[TCA_CT_NAT_IPV4_MIN];
> +            struct nlattr *ipv4_max = ct_attrs[TCA_CT_NAT_IPV4_MAX];
> +            struct nlattr *ipv6_min = ct_attrs[TCA_CT_NAT_IPV6_MIN];
> +            struct nlattr *ipv6_max = ct_attrs[TCA_CT_NAT_IPV6_MAX];
> +            struct nlattr *min_port = ct_attrs[TCA_CT_NAT_PORT_MIN];
> +            struct nlattr *max_port = ct_attrs[TCA_CT_NAT_PORT_MAX];

Ditto here.

> +
> +            action->ct.nat_type = TC_NAT_RESTORE;
> +            if (ct_action & TCA_CT_ACT_NAT_SRC) {
> +                action->ct.nat_type = TC_NAT_SRC;
> +            } else if (ct_action & TCA_CT_ACT_NAT_DST) {
> +                action->ct.nat_type = TC_NAT_DST;
> +            }
> +
> +            if (ipv4_min) {
> +                action->ct.range.ip_family = AF_INET;
> +                action->ct.range.min_addr.ipv4 = nl_attr_get_be32(ipv4_min);
> +                if (ipv4_max) {
> +                    ovs_be32 port = nl_attr_get_be32(ipv4_max);
> +
> +                    action->ct.range.max_addr.ipv4 = port;
> +                }
> +            } else if (ipv6_min) {
> +                action->ct.range.ip_family = AF_INET6;
> +                action->ct.range.min_addr.ipv6
> +                    = nl_attr_get_in6_addr(ipv6_min);
> +                if (ipv6_max) {
> +                    struct in6_addr addr = nl_attr_get_in6_addr(ipv6_max);
> +
> +                    action->ct.range.max_addr.ipv6 = addr;
> +                }
> +            }
> +
> +            if (min_port) {
> +                action->ct.range.min_port = nl_attr_get_be16(min_port);
> +                if (max_port) {
> +                    action->ct.range.max_port = nl_attr_get_be16(max_port);
> +                }
> +            }
> +        }
>      }
>      action->type = TC_ACT_CT;
>  
> @@ -2056,6 +2109,44 @@ nl_msg_put_act_ct(struct ofpbuf *request, struct tc_action *action)
>                      ct_action |= TCA_CT_ACT_FORCE;
>                  }
>              }
> +
> +            if (action->ct.nat_type) {
> +                ct_action |= TCA_CT_ACT_NAT;
> +
> +                if (action->ct.nat_type == TC_NAT_SRC) {
> +                    ct_action |= TCA_CT_ACT_NAT_SRC;
> +                } else if (action->ct.nat_type == TC_NAT_DST) {
> +                    ct_action |= TCA_CT_ACT_NAT_DST;
> +                }
> +
> +                if (action->ct.range.ip_family == AF_INET) {
> +                    nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MIN,
> +                                    action->ct.range.min_addr.ipv4);
> +                    if (action->ct.range.max_addr.ipv4) {
> +                        nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MAX,
> +                                    action->ct.range.max_addr.ipv4);
> +                    }
> +                } else if (action->ct.range.ip_family == AF_INET6) {
> +                    size_t ipv6_sz = sizeof(action->ct.range.max_addr.ipv6);
> +
> +                    nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MIN,
> +                                        &action->ct.range.min_addr.ipv6);
> +                    if (!is_all_zeros(&action->ct.range.max_addr.ipv6,
> +                                      ipv6_sz)) {
> +                        nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MAX,
> +                                            &action->ct.range.max_addr.ipv6);
> +                    }
> +                }
> +
> +                if (action->ct.range.min_port) {
> +                    nl_msg_put_be16(request, TCA_CT_NAT_PORT_MIN,
> +                                    action->ct.range.min_port);
> +                    if (action->ct.range.max_port) {
> +                        nl_msg_put_be16(request, TCA_CT_NAT_PORT_MAX,
> +                                        action->ct.range.max_port);
> +                    }
> +                }
> +            }
>          } else {
>              ct_action = TCA_CT_ACT_CLEAR;
>          }
> diff --git a/lib/tc.h b/lib/tc.h
> index e853aeb77468..15e58f88acb9 100644
> --- a/lib/tc.h
> +++ b/lib/tc.h
> @@ -165,6 +165,13 @@ enum tc_action_type {
>      TC_ACT_CT,
>  };
>  
> +enum nat_type {
> +    TC_NO_NAT = 0,
> +    TC_NAT_SRC,
> +    TC_NAT_DST,
> +    TC_NAT_RESTORE,
> +};
> +
>  struct tc_action {
>      union {
>          int chain;
> @@ -213,6 +220,23 @@ struct tc_action {
>              uint32_t mark_mask;
>              ovs_u128 label;
>              ovs_u128 label_mask;
> +            uint8_t nat_type;
> +            struct {
> +                uint8_t ip_family;
> +
> +                union {
> +                    struct in6_addr ipv6;
> +                    ovs_be32 ipv4;
> +                } min_addr;
> +
> +                union {
> +                    struct in6_addr ipv6;
> +                    ovs_be32 ipv4;
> +                } max_addr;
> +
> +                ovs_be16 min_port;
> +                ovs_be16 max_port;
> +            } range;
>              bool clear;
>              bool force;
>              bool commit;
> -- 
> 2.8.4
>
Paul Blakey Dec. 1, 2019, 7:57 a.m. UTC | #2
On 11/27/2019 5:27 PM, Marcelo Ricardo Leitner wrote:
> On Wed, Nov 27, 2019 at 02:55:14PM +0200, Roi Dayan wrote:
>> From: Paul Blakey <paulb@mellanox.com>
>>
>> Changelog:
>> V1->V2:
>>      Missing ntohs/htons on nat port range.
>>
>> Signed-off-by: Paul Blakey <paulb@mellanox.com>
>> ---
>>   lib/netdev-offload-tc.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>>   lib/tc.c                |  91 ++++++++++++++++++++++++++++++++++++++++++
>>   lib/tc.h                |  24 +++++++++++
>>   3 files changed, 219 insertions(+)
>>
>> diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
>> index 2b4ecfb9ca48..3a9b9e144311 100644
>> --- a/lib/netdev-offload-tc.c
>> +++ b/lib/netdev-offload-tc.c
>> @@ -814,6 +814,40 @@ parse_tc_flower_to_match(struct tc_flower *flower,
>>                       ct_label->mask = action->ct.label_mask;
>>                   }
>>   
>> +                if (action->ct.nat_type) {
>> +                    size_t nat_offset = nl_msg_start_nested(buf,
>> +                                                            OVS_CT_ATTR_NAT);
>> +
>> +                    if (action->ct.nat_type == TC_NAT_SRC) {
>> +                        nl_msg_put_flag(buf, OVS_NAT_ATTR_SRC);
>> +                    } else if (action->ct.nat_type == TC_NAT_DST) {
>> +                        nl_msg_put_flag(buf, OVS_NAT_ATTR_DST);
>> +                    }
>> +
>> +                    if (action->ct.range.ip_family == AF_INET) {
>> +                        nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MIN,
>                                                          ^^^^^^^^^^^
>> +                                        action->ct.range.min_addr.ipv4);
>                                                              ^^^^^^^^
>
> Sorry, more bikeshedding here.
> OVS seems to use _MIN and _MAX as suffix, and tc act ct also uses it
> that way: TCA_CT_NAT_IPV4_MIN, etc.
>
> I'm wondering, should we do the same for tc_action members here?
>
Sure, will do.
diff mbox series

Patch

diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 2b4ecfb9ca48..3a9b9e144311 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -814,6 +814,40 @@  parse_tc_flower_to_match(struct tc_flower *flower,
                     ct_label->mask = action->ct.label_mask;
                 }
 
+                if (action->ct.nat_type) {
+                    size_t nat_offset = nl_msg_start_nested(buf,
+                                                            OVS_CT_ATTR_NAT);
+
+                    if (action->ct.nat_type == TC_NAT_SRC) {
+                        nl_msg_put_flag(buf, OVS_NAT_ATTR_SRC);
+                    } else if (action->ct.nat_type == TC_NAT_DST) {
+                        nl_msg_put_flag(buf, OVS_NAT_ATTR_DST);
+                    }
+
+                    if (action->ct.range.ip_family == AF_INET) {
+                        nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MIN,
+                                        action->ct.range.min_addr.ipv4);
+                        nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MAX,
+                                        action->ct.range.max_addr.ipv4);
+                    } else if (action->ct.range.ip_family == AF_INET6) {
+                        nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MIN,
+                                            &action->ct.range.min_addr.ipv6);
+                        nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MAX,
+                                            &action->ct.range.max_addr.ipv6);
+                    }
+
+                    if (action->ct.range.min_port) {
+                        nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MIN,
+                                       ntohs(action->ct.range.min_port));
+                        if (action->ct.range.max_port) {
+                            nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MAX,
+                                           ntohs(action->ct.range.max_port));
+                        }
+                    }
+
+                    nl_msg_end_nested(buf, nat_offset);
+                }
+
                 nl_msg_end_nested(buf, ct_offset);
             }
             break;
@@ -906,6 +940,66 @@  parse_mpls_set_action(struct tc_flower *flower, struct tc_action *action,
 }
 
 static int
+parse_put_flow_nat_action(struct tc_action *action,
+                          const struct nlattr *nat,
+                          size_t nat_len)
+{
+    const struct nlattr *nat_attr;
+    size_t nat_left;
+
+    action->ct.nat_type = TC_NAT_RESTORE;
+    NL_ATTR_FOR_EACH_UNSAFE (nat_attr, nat_left, nat, nat_len) {
+        switch (nl_attr_type(nat_attr)) {
+            case OVS_NAT_ATTR_SRC: {
+                action->ct.nat_type = TC_NAT_SRC;
+            };
+            break;
+            case OVS_NAT_ATTR_DST: {
+                action->ct.nat_type = TC_NAT_DST;
+            };
+            break;
+            case OVS_NAT_ATTR_IP_MIN: {
+                if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) {
+                    ovs_be32 addr = nl_attr_get_be32(nat_attr);
+
+                    action->ct.range.min_addr.ipv4 = addr;
+                    action->ct.range.ip_family = AF_INET;
+                } else {
+                    struct in6_addr addr = nl_attr_get_in6_addr(nat_attr);
+
+                    action->ct.range.min_addr.ipv6 = addr;
+                    action->ct.range.ip_family = AF_INET6;
+                }
+            };
+            break;
+            case OVS_NAT_ATTR_IP_MAX: {
+                if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) {
+                    ovs_be32 addr = nl_attr_get_be32(nat_attr);
+
+                    action->ct.range.max_addr.ipv4 = addr;
+                    action->ct.range.ip_family = AF_INET;
+                } else {
+                    struct in6_addr addr = nl_attr_get_in6_addr(nat_attr);
+
+                    action->ct.range.max_addr.ipv6 = addr;
+                    action->ct.range.ip_family = AF_INET6;
+                }
+            };
+            break;
+            case OVS_NAT_ATTR_PROTO_MIN: {
+                action->ct.range.min_port = htons(nl_attr_get_u16(nat_attr));
+            };
+            break;
+            case OVS_NAT_ATTR_PROTO_MAX: {
+                action->ct.range.max_port = htons(nl_attr_get_u16(nat_attr));
+            };
+            break;
+        }
+    }
+    return 0;
+}
+
+static int
 parse_put_flow_ct_action(struct tc_flower *flower,
                          struct tc_action *action,
                          const struct nlattr *ct,
@@ -925,6 +1019,16 @@  parse_put_flow_ct_action(struct tc_flower *flower,
                     action->ct.zone = nl_attr_get_u16(ct_attr);
                 }
                 break;
+                case OVS_CT_ATTR_NAT: {
+                    const struct nlattr *nat = nl_attr_get(ct_attr);
+                    const size_t nat_len = nl_attr_get_size(ct_attr);
+
+                    err = parse_put_flow_nat_action(action, nat, nat_len);
+                    if (err) {
+                        return err;
+                    }
+                }
+                break;
                 case OVS_CT_ATTR_MARK: {
                     const struct {
                         uint32_t key;
diff --git a/lib/tc.c b/lib/tc.c
index 1062326d4b86..83ee0f6473aa 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -1282,6 +1282,18 @@  static const struct nl_policy ct_policy[] = {
                          .optional = true, },
     [TCA_CT_LABELS_MASK] = { .type = NL_A_UNSPEC,
                               .optional = true, },
+    [TCA_CT_NAT_IPV4_MIN] = { .type = NL_A_U32,
+                              .optional = true, },
+    [TCA_CT_NAT_IPV4_MAX] = { .type = NL_A_U32,
+                              .optional = true, },
+    [TCA_CT_NAT_IPV6_MIN] = { .min_len = sizeof(struct in6_addr),
+                               .optional = true, },
+    [TCA_CT_NAT_IPV6_MAX] = { .min_len = sizeof(struct in6_addr),
+                              .optional = true, },
+    [TCA_CT_NAT_PORT_MIN] = { .type = NL_A_U16,
+                              .optional = true, },
+    [TCA_CT_NAT_PORT_MAX] = { .type = NL_A_U16,
+                              .optional = true, },
 };
 
 static int
@@ -1325,6 +1337,47 @@  nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
         action->ct.label_mask = label_mask ?
                                 nl_attr_get_u128(label_mask) : OVS_U128_ZERO;
 
+        if (ct_action & TCA_CT_ACT_NAT) {
+            struct nlattr *ipv4_min = ct_attrs[TCA_CT_NAT_IPV4_MIN];
+            struct nlattr *ipv4_max = ct_attrs[TCA_CT_NAT_IPV4_MAX];
+            struct nlattr *ipv6_min = ct_attrs[TCA_CT_NAT_IPV6_MIN];
+            struct nlattr *ipv6_max = ct_attrs[TCA_CT_NAT_IPV6_MAX];
+            struct nlattr *min_port = ct_attrs[TCA_CT_NAT_PORT_MIN];
+            struct nlattr *max_port = ct_attrs[TCA_CT_NAT_PORT_MAX];
+
+            action->ct.nat_type = TC_NAT_RESTORE;
+            if (ct_action & TCA_CT_ACT_NAT_SRC) {
+                action->ct.nat_type = TC_NAT_SRC;
+            } else if (ct_action & TCA_CT_ACT_NAT_DST) {
+                action->ct.nat_type = TC_NAT_DST;
+            }
+
+            if (ipv4_min) {
+                action->ct.range.ip_family = AF_INET;
+                action->ct.range.min_addr.ipv4 = nl_attr_get_be32(ipv4_min);
+                if (ipv4_max) {
+                    ovs_be32 port = nl_attr_get_be32(ipv4_max);
+
+                    action->ct.range.max_addr.ipv4 = port;
+                }
+            } else if (ipv6_min) {
+                action->ct.range.ip_family = AF_INET6;
+                action->ct.range.min_addr.ipv6
+                    = nl_attr_get_in6_addr(ipv6_min);
+                if (ipv6_max) {
+                    struct in6_addr addr = nl_attr_get_in6_addr(ipv6_max);
+
+                    action->ct.range.max_addr.ipv6 = addr;
+                }
+            }
+
+            if (min_port) {
+                action->ct.range.min_port = nl_attr_get_be16(min_port);
+                if (max_port) {
+                    action->ct.range.max_port = nl_attr_get_be16(max_port);
+                }
+            }
+        }
     }
     action->type = TC_ACT_CT;
 
@@ -2056,6 +2109,44 @@  nl_msg_put_act_ct(struct ofpbuf *request, struct tc_action *action)
                     ct_action |= TCA_CT_ACT_FORCE;
                 }
             }
+
+            if (action->ct.nat_type) {
+                ct_action |= TCA_CT_ACT_NAT;
+
+                if (action->ct.nat_type == TC_NAT_SRC) {
+                    ct_action |= TCA_CT_ACT_NAT_SRC;
+                } else if (action->ct.nat_type == TC_NAT_DST) {
+                    ct_action |= TCA_CT_ACT_NAT_DST;
+                }
+
+                if (action->ct.range.ip_family == AF_INET) {
+                    nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MIN,
+                                    action->ct.range.min_addr.ipv4);
+                    if (action->ct.range.max_addr.ipv4) {
+                        nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MAX,
+                                    action->ct.range.max_addr.ipv4);
+                    }
+                } else if (action->ct.range.ip_family == AF_INET6) {
+                    size_t ipv6_sz = sizeof(action->ct.range.max_addr.ipv6);
+
+                    nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MIN,
+                                        &action->ct.range.min_addr.ipv6);
+                    if (!is_all_zeros(&action->ct.range.max_addr.ipv6,
+                                      ipv6_sz)) {
+                        nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MAX,
+                                            &action->ct.range.max_addr.ipv6);
+                    }
+                }
+
+                if (action->ct.range.min_port) {
+                    nl_msg_put_be16(request, TCA_CT_NAT_PORT_MIN,
+                                    action->ct.range.min_port);
+                    if (action->ct.range.max_port) {
+                        nl_msg_put_be16(request, TCA_CT_NAT_PORT_MAX,
+                                        action->ct.range.max_port);
+                    }
+                }
+            }
         } else {
             ct_action = TCA_CT_ACT_CLEAR;
         }
diff --git a/lib/tc.h b/lib/tc.h
index e853aeb77468..15e58f88acb9 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -165,6 +165,13 @@  enum tc_action_type {
     TC_ACT_CT,
 };
 
+enum nat_type {
+    TC_NO_NAT = 0,
+    TC_NAT_SRC,
+    TC_NAT_DST,
+    TC_NAT_RESTORE,
+};
+
 struct tc_action {
     union {
         int chain;
@@ -213,6 +220,23 @@  struct tc_action {
             uint32_t mark_mask;
             ovs_u128 label;
             ovs_u128 label_mask;
+            uint8_t nat_type;
+            struct {
+                uint8_t ip_family;
+
+                union {
+                    struct in6_addr ipv6;
+                    ovs_be32 ipv4;
+                } min_addr;
+
+                union {
+                    struct in6_addr ipv6;
+                    ovs_be32 ipv4;
+                } max_addr;
+
+                ovs_be16 min_port;
+                ovs_be16 max_port;
+            } range;
             bool clear;
             bool force;
             bool commit;