diff mbox series

[ovs-dev,ACL,Meters,7/7] ovn: Add rate-limiting for ACL logs.

Message ID 20180730064638.121021-7-jpettit@ovn.org
State Accepted
Headers show
Series [ovs-dev,ACL,Meters,1/7] ovn: Use C strings instead of ds for extended tables. | expand

Commit Message

Justin Pettit July 30, 2018, 6:46 a.m. UTC
Signed-off-by: Justin Pettit <jpettit@ovn.org>
---
 include/ovn/actions.h         |  1 +
 ovn/lib/actions.c             | 56 +++++++++++++++++++++------
 ovn/northd/ovn-northd.c       |  4 ++
 ovn/ovn-nb.ovsschema          |  5 ++-
 ovn/ovn-nb.xml                |  9 +++++
 ovn/utilities/ovn-nbctl.8.xml |  6 ++-
 ovn/utilities/ovn-nbctl.c     | 13 +++++--
 tests/ovn.at                  | 73 +++++++++++++++++++++++++++++++++++
 8 files changed, 149 insertions(+), 18 deletions(-)

Comments

0-day Robot July 30, 2018, 7:08 a.m. UTC | #1
Bleep bloop.  Greetings Justin Pettit, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line is 372 characters long (recommended limit is 79)
#239 FILE: ovn/utilities/ovn-nbctl.8.xml:88:
      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] [<code>--log</code>] [<code>--meter=</code><var>meter</var>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--may-exist</code>] <code>acl-add</code> <var>entity</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>

Lines checked: 386, Warnings: 1, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@bytheb.org

Thanks,
0-day Robot
Ben Pfaff July 30, 2018, 7:55 p.m. UTC | #2
On Sun, Jul 29, 2018 at 11:46:38PM -0700, Justin Pettit wrote:
> Signed-off-by: Justin Pettit <jpettit@ovn.org>

Besides the comments I gave on patch 6 (oops), I suggest the following
for more consistent formatting:

diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 6d4ed1e2c4ed..4f3cd48ce713 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -2157,7 +2157,7 @@ format_LOG(const struct ovnact_log *log, struct ds *s)
     ds_put_format(s, "severity=%s", log_severity_to_string(log->severity));
 
     if (log->meter) {
-        ds_put_format(s, ",meter=%s", log->meter);
+        ds_put_format(s, ", meter=%s", log->meter);
     }
 
     ds_put_cstr(s, ");");

Acked-by: Ben Pfaff <blp@ovn.org>
Justin Pettit July 31, 2018, 12:58 a.m. UTC | #3
> On Jul 30, 2018, at 12:55 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> On Sun, Jul 29, 2018 at 11:46:38PM -0700, Justin Pettit wrote:
>> Signed-off-by: Justin Pettit <jpettit@ovn.org>
> 
> Besides the comments I gave on patch 6 (oops), I suggest the following
> for more consistent formatting:
> 
> diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
> index 6d4ed1e2c4ed..4f3cd48ce713 100644
> --- a/ovn/lib/actions.c
> +++ b/ovn/lib/actions.c
> @@ -2157,7 +2157,7 @@ format_LOG(const struct ovnact_log *log, struct ds *s)
>     ds_put_format(s, "severity=%s", log_severity_to_string(log->severity));
> 
>     if (log->meter) {
> -        ds_put_format(s, ",meter=%s", log->meter);
> +        ds_put_format(s, ", meter=%s", log->meter);
>     }
> 
>     ds_put_cstr(s, ");");

Applied.

> Acked-by: Ben Pfaff <blp@ovn.org>

Thanks for the review!  I've pushed this series to master.

--Justin
Justin Pettit July 31, 2018, 6:12 a.m. UTC | #4
> On Jul 30, 2018, at 5:58 PM, Justin Pettit <jpettit@ovn.org> wrote:
> 
> Thanks for the review!  I've pushed this series to master.

I also just pushed this to branch-2.10.

The rate-limiting is implemented using meters.  Unfortunately, there's a bug in the current kernels that prevents meters from working properly.  I've sent a patch to upstream Linux, which should solve the problem:

	https://marc.info/?l=linux-netdev&m=153281677212826&w=2

Once that is merged, I'll commit it to OVS, and it should work with both Linux and userspace datapaths.

--Justin
Han Zhou Aug. 6, 2018, 11:27 p.m. UTC | #5
On Mon, Jul 30, 2018 at 8:12 PM, Justin Pettit <jpettit@ovn.org> wrote:
>
>
> > On Jul 30, 2018, at 5:58 PM, Justin Pettit <jpettit@ovn.org> wrote:
> >
> > Thanks for the review!  I've pushed this series to master.
>
> I also just pushed this to branch-2.10.
>
> The rate-limiting is implemented using meters.  Unfortunately, there's a
bug in the current kernels that prevents meters from working properly.
I've sent a patch to upstream Linux, which should solve the problem:
>
>         https://marc.info/?l=linux-netdev&m=153281677212826&w=2
>
> Once that is merged, I'll commit it to OVS, and it should work with both
Linux and userspace datapaths.
>
> --Justin
>
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Thanks Justin for the great work!!
Sorry that I didn't get time to review the series, just some quick
questions regarding the kernel bug you mentioned.

- What's the impact of having meter ID = 0? Does it mean the meters and ACL
rate limit feature can't be used at all, or is it just some limitations in
certain scenarios?

- For the bug fix, can we get it reviewed (and probably merged) in
datapath/compact first here in the OVS community without having to wait for
kernel upstream? What's the usual practice for kernel module patches?

Thanks,
Han
Justin Pettit Aug. 7, 2018, 9:03 a.m. UTC | #6
> On Aug 6, 2018, at 1:27 PM, Han Zhou <zhouhan@gmail.com> wrote:
> 
> Thanks Justin for the great work!!
> Sorry that I didn't get time to review the series, just some quick questions regarding the kernel bug you mentioned.

Yes, I think you were on vacation, and I was running up against my own, so it all went in pretty quickly.  I'm sorry I cut it so close to the 2.10 release date and couldn't have gotten some initial testing from you and your team.

> - What's the impact of having meter ID = 0? Does it mean the meters and ACL rate limit feature can't be used at all, or is it just some limitations in certain scenarios?

In theory, it would mean that a single meter could be put in the kernel (there is a layer of indirection in the mapping between the OpenFlow meter id and the one chosen for the datapath).  However, my current plan is to add a probe to OVS which just disables meters on those kernels with broken meters, since otherwise it will just lead to confusion.  I suspect the issue will be with kernels 4.15, 4.16, and 4.17.  (And hopefully some of those kernels will be fixed as they do bug fix releases.)

> - For the bug fix, can we get it reviewed (and probably merged) in datapath/compact first here in the OVS community without having to wait for kernel upstream? What's the usual practice for kernel module patches?

The patch was committed without comment from David Miller:

	https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=25432eba9

The backport patch is ready (I've appended it to the end of this message), but I've been on vacation and I wanted to get the probe patch in at the same time.  I plan to send both patches out Tuesday night.  However, if you want to apply the patch at the bottom and give ACL rate-limiting a try, I'd love to get some initial feedback.

Thanks,

--Justin


-=-=-=-=-=-=-=-=-=-

commit bc89eebb0e918d2e9a903d7e4a24ab1b5b522eab (HEAD -> meter-datapath)
Author: Justin Pettit <jpettit@ovn.org>
Date:   Thu Jul 26 22:28:11 2018 -0700

    datapath: Fix setting meter id for new entries.
    
    Upstream commit:
    
        openvswitch: meter: Fix setting meter id for new entries
    
        The meter code would create an entry for each new meter.  However, it
        would not set the meter id in the new entry, so every meter would appear
        to have a meter id of zero.  This commit properly sets the meter id when
        adding the entry.
    
        Fixes: 96fbc13d7e77 ("openvswitch: Add meter infrastructure")
        Signed-off-by: Justin Pettit <jpettit@ovn.org>
        Cc: Andy Zhou <azhou@ovn.org>
        Signed-off-by: David S. Miller <davem@davemloft.net>
    
    Signed-off-by: Justin Pettit <jpettit@ovn.org>

diff --git a/datapath/meter.c b/datapath/meter.c
index 1c2ed4626c2a..281d86937555 100644
--- a/datapath/meter.c
+++ b/datapath/meter.c
@@ -221,6 +221,7 @@ static struct dp_meter *dp_meter_create(struct nlattr **a)
        if (!meter)
                return ERR_PTR(-ENOMEM);
 
+       meter->id = nla_get_u32(a[OVS_METER_ATTR_ID]);
        meter->used = div_u64(ktime_get_ns(), 1000 * 1000);
        meter->kbps = a[OVS_METER_ATTR_KBPS] ? 1 : 0;
        meter->keep_stats = !a[OVS_METER_ATTR_CLEAR];
@@ -290,6 +291,10 @@ static int ovs_meter_cmd_set(struct sk_buff *skb, struct genl_info *info)
        u32 meter_id;
        bool failed;
 
+       if (!a[OVS_METER_ATTR_ID]) {
+               return -ENODEV;
+       }
+
        meter = dp_meter_create(a);
        if (IS_ERR_OR_NULL(meter))
                return PTR_ERR(meter);
@@ -308,11 +313,6 @@ static int ovs_meter_cmd_set(struct sk_buff *skb, struct genl_info *info)
                goto exit_unlock;
        }
 
-       if (!a[OVS_METER_ATTR_ID]) {
-               err = -ENODEV;
-               goto exit_unlock;
-       }
-
        meter_id = nla_get_u32(a[OVS_METER_ATTR_ID]);
 
        /* Cannot fail after this. */
Han Zhou Aug. 7, 2018, 3:51 p.m. UTC | #7
On Tue, Aug 7, 2018 at 2:03 AM, Justin Pettit <jpettit@ovn.org> wrote:
>
>
> > On Aug 6, 2018, at 1:27 PM, Han Zhou <zhouhan@gmail.com> wrote:
> >
> > Thanks Justin for the great work!!
> > Sorry that I didn't get time to review the series, just some quick
questions regarding the kernel bug you mentioned.
>
> Yes, I think you were on vacation, and I was running up against my own,
so it all went in pretty quickly.  I'm sorry I cut it so close to the 2.10
release date and couldn't have gotten some initial testing from you and
your team.
>
> > - What's the impact of having meter ID = 0? Does it mean the meters and
ACL rate limit feature can't be used at all, or is it just some limitations
in certain scenarios?
>
> In theory, it would mean that a single meter could be put in the kernel
(there is a layer of indirection in the mapping between the OpenFlow meter
id and the one chosen for the datapath).  However, my current plan is to
add a probe to OVS which just disables meters on those kernels with broken
meters, since otherwise it will just lead to confusion.  I suspect the
issue will be with kernels 4.15, 4.16, and 4.17.  (And hopefully some of
those kernels will be fixed as they do bug fix releases.)
>
> > - For the bug fix, can we get it reviewed (and probably merged) in
datapath/compact first here in the OVS community without having to wait for
kernel upstream? What's the usual practice for kernel module patches?
>
> The patch was committed without comment from David Miller:
>
>
https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=25432eba9
>
> The backport patch is ready (I've appended it to the end of this
message), but I've been on vacation and I wanted to get the probe patch in
at the same time.  I plan to send both patches out Tuesday night.  However,
if you want to apply the patch at the bottom and give ACL rate-limiting a
try, I'd love to get some initial feedback.
>
> Thanks,
>
> --Justin
>
>
> -=-=-=-=-=-=-=-=-=-
>
> commit bc89eebb0e918d2e9a903d7e4a24ab1b5b522eab (HEAD -> meter-datapath)
> Author: Justin Pettit <jpettit@ovn.org>
> Date:   Thu Jul 26 22:28:11 2018 -0700
>
>     datapath: Fix setting meter id for new entries.
>
>     Upstream commit:
>
>         openvswitch: meter: Fix setting meter id for new entries
>
>         The meter code would create an entry for each new meter.
However, it
>         would not set the meter id in the new entry, so every meter would
appear
>         to have a meter id of zero.  This commit properly sets the meter
id when
>         adding the entry.
>
>         Fixes: 96fbc13d7e77 ("openvswitch: Add meter infrastructure")
>         Signed-off-by: Justin Pettit <jpettit@ovn.org>
>         Cc: Andy Zhou <azhou@ovn.org>
>         Signed-off-by: David S. Miller <davem@davemloft.net>
>
>     Signed-off-by: Justin Pettit <jpettit@ovn.org>
>
> diff --git a/datapath/meter.c b/datapath/meter.c
> index 1c2ed4626c2a..281d86937555 100644
> --- a/datapath/meter.c
> +++ b/datapath/meter.c
> @@ -221,6 +221,7 @@ static struct dp_meter *dp_meter_create(struct nlattr
**a)
>         if (!meter)
>                 return ERR_PTR(-ENOMEM);
>
> +       meter->id = nla_get_u32(a[OVS_METER_ATTR_ID]);
>         meter->used = div_u64(ktime_get_ns(), 1000 * 1000);
>         meter->kbps = a[OVS_METER_ATTR_KBPS] ? 1 : 0;
>         meter->keep_stats = !a[OVS_METER_ATTR_CLEAR];
> @@ -290,6 +291,10 @@ static int ovs_meter_cmd_set(struct sk_buff *skb,
struct genl_info *info)
>         u32 meter_id;
>         bool failed;
>
> +       if (!a[OVS_METER_ATTR_ID]) {
> +               return -ENODEV;
> +       }
> +
>         meter = dp_meter_create(a);
>         if (IS_ERR_OR_NULL(meter))
>                 return PTR_ERR(meter);
> @@ -308,11 +313,6 @@ static int ovs_meter_cmd_set(struct sk_buff *skb,
struct genl_info *info)
>                 goto exit_unlock;
>         }
>
> -       if (!a[OVS_METER_ATTR_ID]) {
> -               err = -ENODEV;
> -               goto exit_unlock;
> -       }
> -
>         meter_id = nla_get_u32(a[OVS_METER_ATTR_ID]);
>
>         /* Cannot fail after this. */
>
>
>
>

Thanks Justin! This is very nice and we will try it.
diff mbox series

Patch

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 6384651938f1..1c0c67ce6ffa 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -284,6 +284,7 @@  struct ovnact_log {
     uint8_t verdict;            /* One of LOG_VERDICT_*. */
     uint8_t severity;           /* One of LOG_SEVERITY_*. */
     char *name;
+    char *meter;
 };
 
 /* OVNACT_SET_METER. */
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 26cdb4fdd482..6d4ed1e2c4ed 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -78,7 +78,7 @@  ovnact_init(struct ovnact *ovnact, enum ovnact_type type, size_t len)
 
 static size_t
 encode_start_controller_op(enum action_opcode opcode, bool pause,
-                           struct ofpbuf *ofpacts)
+                           uint32_t meter_id, struct ofpbuf *ofpacts)
 {
     size_t ofs = ofpacts->size;
 
@@ -86,6 +86,7 @@  encode_start_controller_op(enum action_opcode opcode, bool pause,
     oc->max_len = UINT16_MAX;
     oc->reason = OFPR_ACTION;
     oc->pause = pause;
+    oc->meter_id = meter_id;
 
     struct action_header ah = { .opcode = htonl(opcode) };
     ofpbuf_put(ofpacts, &ah, sizeof ah);
@@ -105,7 +106,8 @@  encode_finish_controller_op(size_t ofs, struct ofpbuf *ofpacts)
 static void
 encode_controller_op(enum action_opcode opcode, struct ofpbuf *ofpacts)
 {
-    size_t ofs = encode_start_controller_op(opcode, false, ofpacts);
+    size_t ofs = encode_start_controller_op(opcode, false, NX_CTLR_NO_METER,
+                                            ofpacts);
     encode_finish_controller_op(ofs, ofpacts);
 }
 
@@ -1245,7 +1247,8 @@  encode_nested_actions(const struct ovnact_nest *on,
      * converted to OpenFlow, as its userdata.  ovn-controller will convert the
      * packet to ARP or NA and then send the packet and actions back to the
      * switch inside an OFPT_PACKET_OUT message. */
-    size_t oc_offset = encode_start_controller_op(opcode, false, ofpacts);
+    size_t oc_offset = encode_start_controller_op(opcode, false,
+                                                  NX_CTLR_NO_METER, ofpacts);
     ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
                                  ofpacts, OFP13_VERSION);
     encode_finish_controller_op(oc_offset, ofpacts);
@@ -1738,7 +1741,8 @@  encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo,
     struct mf_subfield dst = expr_resolve_field(&pdo->dst);
 
     size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_PUT_DHCP_OPTS,
-                                                  true, ofpacts);
+                                                  true, NX_CTLR_NO_METER,
+                                                  ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
     ofpbuf_put(ofpacts, &ofs, sizeof ofs);
@@ -1769,7 +1773,7 @@  encode_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo,
     struct mf_subfield dst = expr_resolve_field(&pdo->dst);
 
     size_t oc_offset = encode_start_controller_op(
-        ACTION_OPCODE_PUT_DHCPV6_OPTS, true, ofpacts);
+        ACTION_OPCODE_PUT_DHCPV6_OPTS, true, NX_CTLR_NO_METER, ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
     ofpbuf_put(ofpacts, &ofs, sizeof ofs);
@@ -1864,7 +1868,8 @@  encode_DNS_LOOKUP(const struct ovnact_dns_lookup *dl,
     struct mf_subfield dst = expr_resolve_field(&dl->dst);
 
     size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_DNS_LOOKUP,
-                                                  true, ofpacts);
+                                                  true, NX_CTLR_NO_METER,
+                                                  ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
     ofpbuf_put(ofpacts, &ofs, sizeof ofs);
@@ -2027,7 +2032,7 @@  encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
     struct mf_subfield dst = expr_resolve_field(&po->dst);
 
     size_t oc_offset = encode_start_controller_op(
-        ACTION_OPCODE_PUT_ND_RA_OPTS, true, ofpacts);
+        ACTION_OPCODE_PUT_ND_RA_OPTS, true, NX_CTLR_NO_METER, ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
     ofpbuf_put(ofpacts, &ofs, sizeof ofs);
@@ -2101,6 +2106,19 @@  parse_log_arg(struct action_context *ctx, struct ovnact_log *log)
             }
         }
         lexer_syntax_error(ctx->lexer, "expecting severity");
+    } else if (lexer_match_id(ctx->lexer, "meter")) {
+        if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
+            return;
+        }
+        /* If multiple meters are given, use the most recent. */
+        if (ctx->lexer->token.type == LEX_T_STRING) {
+            free(log->meter);
+            log->meter = xstrdup(ctx->lexer->token.s);
+        } else {
+            lexer_syntax_error(ctx->lexer, "expecting string");
+            return;
+        }
+        lexer_get(ctx->lexer);
     } else {
         lexer_syntax_error(ctx->lexer, NULL);
     }
@@ -2136,16 +2154,31 @@  format_LOG(const struct ovnact_log *log, struct ds *s)
     }
 
     ds_put_format(s, "verdict=%s, ", log_verdict_to_string(log->verdict));
-    ds_put_format(s, "severity=%s);", log_severity_to_string(log->severity));
+    ds_put_format(s, "severity=%s", log_severity_to_string(log->severity));
+
+    if (log->meter) {
+        ds_put_format(s, ",meter=%s", log->meter);
+    }
+
+    ds_put_cstr(s, ");");
 }
 
 static void
 encode_LOG(const struct ovnact_log *log,
-           const struct ovnact_encode_params *ep OVS_UNUSED,
-           struct ofpbuf *ofpacts)
+           const struct ovnact_encode_params *ep, struct ofpbuf *ofpacts)
 {
+    uint32_t meter_id = NX_CTLR_NO_METER;
+
+    if (log->meter) {
+        meter_id = ovn_extend_table_assign_id(ep->meter_table, log->meter);
+        if (meter_id == EXT_TABLE_ID_INVALID) {
+            VLOG_WARN("log specified unknown meter: %"PRIu32, meter_id);
+            return;
+        }
+    }
+
     size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_LOG, false,
-                                                  ofpacts);
+                                                  meter_id, ofpacts);
 
     struct log_pin_header *lph = ofpbuf_put_uninit(ofpacts, sizeof *lph);
     lph->verdict = log->verdict;
@@ -2163,6 +2196,7 @@  static void
 ovnact_log_free(struct ovnact_log *log)
 {
     free(log->name);
+    free(log->meter);
 }
 
 static void
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 45557170edc8..ccf74f7c5299 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -3140,6 +3140,10 @@  build_acl_log(struct ds *actions, const struct nbrec_acl *acl)
         ds_put_cstr(actions, "verdict=allow, ");
     }
 
+    if (acl->meter) {
+        ds_put_format(actions, "meter=\"%s\", ", acl->meter);
+    }
+
     ds_chomp(actions, ' ');
     ds_chomp(actions, ',');
     ds_put_cstr(actions, "); ");
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 9a0d8ec70514..3e7164baafaa 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "5.12.0",
-    "cksum": "2812995200 20238",
+    "version": "5.13.0",
+    "cksum": "1278623084 20312",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -166,6 +166,7 @@ 
                                                         "notice", "info",
                                                         "debug"]]},
                                       "min": 0, "max": 1}},
+                "meter": {"type": {"key": "string", "min": 0, "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 1feb2af52027..e124d9489e7c 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -1159,6 +1159,15 @@ 
           <code>info</code>.
         </p>
       </column>
+
+      <column name="meter">
+        <p>
+            The name of a meter to rate-limit log messages for the ACL.
+            The string must match the <ref column="name" table="meter"/>
+            column of a row in the <ref table="Meter"/> table.  By
+            default, log messages are not rate-limited.
+        </p>
+      </column>
     </group>
 
     <group title="Common Columns">
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index a8ea7d8cb1e1..269e0fb37e8b 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -85,7 +85,7 @@ 
       must be either <code>switch</code> or <code>port-group</code>.
     </p>
     <dl>
-      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] [<code>--log</code>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--may-exist</code>] <code>acl-add</code> <var>entity</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>
+      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] [<code>--log</code>] [<code>--meter=</code><var>meter</var>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--may-exist</code>] <code>acl-add</code> <var>entity</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>
       <dd>
         <p>
           Adds the specified ACL to <var>entity</var>.  <var>direction</var>
@@ -105,7 +105,9 @@ 
           logging).  The severity must be one of <code>alert</code>,
           <code>warning</code>, <code>notice</code>, <code>info</code>, or
           <code>debug</code>.  If a severity is not specified, the default is
-          <code>info</code>.
+          <code>info</code>.  The <code>--meter=<var>meter</var></code> option
+          is used to rate-limit packet logging.  The <var>meter</var> argument
+          names a meter configured by <code>meter-add</code>.
         </p>
       </dd>
 
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index 9f0e6347c104..a82e9c96f446 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -1822,7 +1822,10 @@  nbctl_acl_list(struct ctl_context *ctx)
                 ds_put_format(&ctx->output, "name=%s,", acl->name);
             }
             if (acl->severity) {
-                ds_put_format(&ctx->output, "severity=%s", acl->severity);
+                ds_put_format(&ctx->output, "severity=%s,", acl->severity);
+            }
+            if (acl->meter) {
+                ds_put_format(&ctx->output, "meter=\"%s\",", acl->meter);
             }
             ds_chomp(&ctx->output, ',');
             ds_put_cstr(&ctx->output, ")");
@@ -1927,7 +1930,8 @@  nbctl_acl_add(struct ctl_context *ctx)
     bool log = shash_find(&ctx->options, "--log") != NULL;
     const char *severity = shash_find_data(&ctx->options, "--severity");
     const char *name = shash_find_data(&ctx->options, "--name");
-    if (log || severity || name) {
+    const char *meter = shash_find_data(&ctx->options, "--meter");
+    if (log || severity || name || meter) {
         nbrec_acl_set_log(acl, true);
     }
     if (severity) {
@@ -1940,6 +1944,9 @@  nbctl_acl_add(struct ctl_context *ctx)
     if (name) {
         nbrec_acl_set_name(acl, name);
     }
+    if (meter) {
+        nbrec_acl_set_meter(acl, meter);
+    }
 
     /* Check if same acl already exists for the ls/portgroup */
     size_t n_acls = pg ? pg->n_acls : ls->n_acls;
@@ -4801,7 +4808,7 @@  static const struct ctl_command_syntax nbctl_commands[] = {
     /* acl commands. */
     { "acl-add", 5, 6, "{SWITCH | PORTGROUP} DIRECTION PRIORITY MATCH ACTION",
       NULL, nbctl_acl_add, NULL,
-      "--log,--may-exist,--type=,--name=,--severity=", RW },
+      "--log,--may-exist,--type=,--name=,--severity=,--meter=", RW },
     { "acl-del", 1, 4, "{SWITCH | PORTGROUP} [DIRECTION [PRIORITY MATCH]]",
       NULL, nbctl_acl_del, NULL, "--type=", RW },
     { "acl-list", 1, 1, "{SWITCH | PORTGROUP}",
diff --git a/tests/ovn.at b/tests/ovn.at
index d1a8967dd300..81edeafc0848 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -6153,6 +6153,79 @@  OVN_CLEANUP([hv])
 AT_CLEANUP
 
 
+AT_SETUP([ovn -- ACL rate-limited logging])
+AT_KEYWORDS([ovn])
+ovn_start
+
+net_add n1
+
+sim_add hv
+as hv
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+for i in lp1 lp2; do
+    ovs-vsctl -- add-port br-int $i -- \
+        set interface $i external-ids:iface-id=$i \
+        options:tx_pcap=hv/$i-tx.pcap \
+        options:rxq_pcap=hv/$i-rx.pcap
+done
+
+lp1_mac="f0:00:00:00:00:01"
+lp1_ip="192.168.1.2"
+
+lp2_mac="f0:00:00:00:00:02"
+lp2_ip="192.168.1.3"
+
+ovn-nbctl ls-add lsw0
+ovn-nbctl --wait=sb lsp-add lsw0 lp1
+ovn-nbctl --wait=sb lsp-add lsw0 lp2
+ovn-nbctl lsp-set-addresses lp1 $lp1_mac
+ovn-nbctl lsp-set-addresses lp2 $lp2_mac
+ovn-nbctl --wait=sb sync
+
+
+# Add an ACL that rate-limits logs at 10 per second.
+ovn-nbctl meter-add http-rl1 pktps drop 10
+ovn-nbctl --log --severity=alert --meter=http-rl1 --name=http-acl1 acl-add lsw0 to-lport 1000 'tcp.dst==80' drop
+
+# Add an ACL that rate-limits logs at 5 per second.
+ovn-nbctl meter-add http-rl2 pktps drop 5
+ovn-nbctl --log --severity=alert --meter=http-rl2 --name=http-acl2 acl-add lsw0 to-lport 1000 'tcp.dst==81' allow
+
+# Add an ACL that doesn't rate-limit logs.
+ovn-nbctl --log --severity=alert --name=http-acl3 acl-add lsw0 to-lport 1000 'tcp.dst==82' drop
+
+
+# For each ACL, send 100 packets.
+for i in `seq 1 100`; do
+    ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=80)'
+
+    ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=81)'
+
+    ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=82)'
+done
+
+# Print some information that may help debugging.
+as hv ovs-appctl -t ovn-controller meter-table-list
+as hv ovs-ofctl -O OpenFlow13 meter-stats br-int
+
+# The userspace meter implementation doesn't precisely enforce counts,
+# so we just check that exactly 100 "http-acl3" actions were logged and
+# that there were more "http-acl1" actions than "http-acl2" ones.
+OVS_WAIT_UNTIL([ test 100 = $(grep -c 'http-acl3' hv/ovn-controller.log) ])
+
+n_acl1=$(grep -c 'http-acl1' hv/ovn-controller.log)
+n_acl2=$(grep -c 'http-acl2' hv/ovn-controller.log)
+n_acl3=$(grep -c 'http-acl3' hv/ovn-controller.log)
+
+AT_CHECK([ test $n_acl3 -gt $n_acl1 ], [0], [])
+AT_CHECK([ test $n_acl1 -gt $n_acl2 ], [0], [])
+
+
+OVN_CLEANUP([hv])
+AT_CLEANUP
+
+
 AT_SETUP([ovn -- DSCP marking and meter check])
 AT_KEYWORDS([ovn])
 ovn_start