diff --git a/datapath-windows/automake.mk b/datapath-windows/automake.mk
index 60b3d6033..2d9f9a40d 100644
--- a/datapath-windows/automake.mk
+++ b/datapath-windows/automake.mk
@@ -42,6 +42,8 @@ EXTRA_DIST += \
 	datapath-windows/ovsext/Jhash.c \
 	datapath-windows/ovsext/Jhash.h \
 	datapath-windows/ovsext/Mpls.h \
+	datapath-windows/ovsext/Meter.c \
+	datapath-windows/ovsext/Meter.h \
 	datapath-windows/ovsext/NetProto.h \
 	datapath-windows/ovsext/Netlink/Netlink.c \
 	datapath-windows/ovsext/Netlink/Netlink.h \
diff --git a/datapath-windows/include/OvsDpInterfaceExt.h b/datapath-windows/include/OvsDpInterfaceExt.h
index 5fd8000d1..045e4cbd6 100644
--- a/datapath-windows/include/OvsDpInterfaceExt.h
+++ b/datapath-windows/include/OvsDpInterfaceExt.h
@@ -74,6 +74,9 @@
 #define OVS_WIN_NL_CT_FAMILY_ID              (NLMSG_MIN_TYPE + 7)
 #define OVS_WIN_NL_CTLIMIT_FAMILY_ID         (NLMSG_MIN_TYPE + 8)
 
+/* Meter Family */
+#define OVS_WIN_NL_METER_FAMILY_ID            (NLMSG_MIN_TYPE + 9)
+
 #define OVS_WIN_NL_INVALID_MCGRP_ID          0
 #define OVS_WIN_NL_MCGRP_START_ID            100
 #define OVS_WIN_NL_VPORT_MCGRP_ID            (OVS_WIN_NL_MCGRP_START_ID + 1)
diff --git a/datapath-windows/ovsext/Actions.c b/datapath-windows/ovsext/Actions.c
index 20de4db4c..63ee574c5 100644
--- a/datapath-windows/ovsext/Actions.c
+++ b/datapath-windows/ovsext/Actions.c
@@ -23,6 +23,7 @@
 #include "Flow.h"
 #include "Gre.h"
 #include "Jhash.h"
+#include "Meter.h"
 #include "Mpls.h"
 #include "NetProto.h"
 #include "Offload.h"
@@ -2502,6 +2503,15 @@ OvsDoExecuteActions(POVS_SWITCH_CONTEXT switchContext,
             }
             break;
         }
+        case OVS_ACTION_ATTR_METER: {
+            if (OvsMeterExecute(&ovsFwdCtx, NlAttrGetU32(a))) {
+                OVS_LOG_INFO("Drop packet");
+                dropReason = L"Ovs-meter exceed max rate";
+                goto dropit;
+            }
+
+            break;
+        }
         case OVS_ACTION_ATTR_SAMPLE:
         {
             if (ovsFwdCtx.destPortsSizeOut > 0 || ovsFwdCtx.tunnelTxNic != NULL
diff --git a/datapath-windows/ovsext/Datapath.c b/datapath-windows/ovsext/Datapath.c
index fa994840a..37db5bd17 100644
--- a/datapath-windows/ovsext/Datapath.c
+++ b/datapath-windows/ovsext/Datapath.c
@@ -100,7 +100,11 @@ NetlinkCmdHandler        OvsGetNetdevCmdHandler,
                          OvsReadPacketCmdHandler,
                          OvsCtDeleteCmdHandler,
                          OvsCtDumpCmdHandler,
-                         OvsCtLimitHandler;
+                         OvsCtLimitHandler,
+                         OvsMeterFeatureProbe,
+                         OvsNewMeterCmdHandler,
+                         OvsMeterDestroy,
+                         OvsMeterGet;
 
 static NTSTATUS HandleGetDpTransaction(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                                        UINT32 *replyLen);
@@ -307,6 +311,43 @@ NETLINK_FAMILY nlCtFamilyOps = {
     .opsCount = ARRAY_SIZE(nlCtFamilyCmdOps)
 };
 
+/* Netlink Meter family */
+NETLINK_CMD nlMeterFamilyCmdOps[] = {
+    {  .cmd             = OVS_METER_CMD_FEATURES,
+       .handler         = OvsMeterFeatureProbe,
+       .supportedDevOp  = OVS_TRANSACTION_DEV_OP |
+                             OVS_WRITE_DEV_OP | OVS_READ_DEV_OP,
+       .validateDpIndex = FALSE
+    },
+    { .cmd              = OVS_METER_CMD_SET,
+      .handler          = OvsNewMeterCmdHandler,
+      .supportedDevOp   = OVS_TRANSACTION_DEV_OP |
+                          OVS_WRITE_DEV_OP | OVS_READ_DEV_OP,
+      .validateDpIndex  = FALSE
+    },
+    { .cmd              = OVS_METER_CMD_GET,
+      .handler          = OvsMeterGet,
+      .supportedDevOp   = OVS_TRANSACTION_DEV_OP |
+                          OVS_WRITE_DEV_OP | OVS_READ_DEV_OP,
+      .validateDpIndex  = FALSE
+    },
+    { .cmd              = OVS_METER_CMD_DEL,
+      .handler          = OvsMeterDestroy,
+      .supportedDevOp   = OVS_TRANSACTION_DEV_OP |
+                          OVS_WRITE_DEV_OP | OVS_READ_DEV_OP,
+      .validateDpIndex  = FALSE
+    },
+};
+
+NETLINK_FAMILY nlMeterFamilyOps = {
+    .name     = OVS_METER_FAMILY,
+    .id       = OVS_WIN_NL_METER_FAMILY_ID,
+    .version  = OVS_METER_VERSION,
+    .maxAttr  = __OVS_METER_ATTR_MAX,
+    .cmds     = nlMeterFamilyCmdOps,
+    .opsCount = ARRAY_SIZE(nlMeterFamilyCmdOps)
+};
+
 /* Netlink netdev family. */
 NETLINK_CMD nlNetdevFamilyCmdOps[] = {
     { .cmd = OVS_WIN_NETDEV_CMD_GET,
@@ -952,6 +993,9 @@ OvsDeviceControl(PDEVICE_OBJECT deviceObject,
     case NFNL_TYPE_CT_DEL:
         nlFamilyOps = &nlCtFamilyOps;
         break;
+    case OVS_WIN_NL_METER_FAMILY_ID:
+        nlFamilyOps = &nlMeterFamilyOps;
+        break;
     case OVS_WIN_NL_CTRL_FAMILY_ID:
         nlFamilyOps = &nlControlFamilyOps;
         break;
diff --git a/datapath-windows/ovsext/Meter.c b/datapath-windows/ovsext/Meter.c
new file mode 100644
index 000000000..a896f999d
--- /dev/null
+++ b/datapath-windows/ovsext/Meter.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (c) 2022 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Meter.h"
+#include "precomp.h"
+#include "Switch.h"
+#include "User.h"
+#include "Datapath.h"
+#include "Event.h"
+#include "NetProto.h"
+#include "Flow.h"
+#include "PacketParser.h"
+#include "util.h"
+
+static PNDIS_RW_LOCK_EX meterGlobalTableLock;
+PLIST_ENTRY meterGlobalTable;
+
+const NL_POLICY nlMeterPolicy[OVS_METER_ATTR_MAX + 1] = {
+        [OVS_METER_ATTR_ID] = { .type = NL_A_U32, },
+        [OVS_METER_ATTR_KBPS] = { .type = NL_A_FLAG, .optional = TRUE },
+        [OVS_METER_ATTR_STATS] = { .minLen = sizeof(struct ovs_flow_stats),
+                                   .maxLen = sizeof(struct ovs_flow_stats),
+                                   .optional = TRUE },
+        [OVS_METER_ATTR_BANDS] = { .type = NL_A_NESTED, .optional = TRUE },
+        [OVS_METER_ATTR_USED] = { .type = NL_A_U64, .optional = TRUE },
+        [OVS_METER_ATTR_CLEAR] = { .type = NL_A_FLAG, .optional = TRUE },
+        [OVS_METER_ATTR_MAX_METERS] = { .type = NL_A_U32, .optional = TRUE },
+        [OVS_METER_ATTR_MAX_BANDS] = { .type = NL_A_U32,  .optional = TRUE },
+};
+
+const NL_POLICY bandPolicy[OVS_BAND_ATTR_MAX + 1] = {
+        [OVS_BAND_ATTR_TYPE] = { .type = NL_A_U32, .optional = FALSE },
+        [OVS_BAND_ATTR_RATE] = { .type = NL_A_U32, .optional = TRUE },
+        [OVS_BAND_ATTR_BURST] = { .type = NL_A_U32,  .optional = TRUE },
+        [OVS_BAND_ATTR_STATS] = { .minLen = sizeof(struct ovs_flow_stats),
+                                  .maxLen = sizeof(struct ovs_flow_stats),
+                                  .optional = TRUE },
+};
+
+NTSTATUS
+OvsInitMeter(POVS_SWITCH_CONTEXT context)
+{
+    UINT32 maxEntry = METER_HASH_BUCKET_MAX;
+
+    meterGlobalTableLock = NdisAllocateRWLock(context->NdisFilterHandle);
+    if (meterGlobalTableLock == NULL) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    meterGlobalTable = OvsAllocateMemoryWithTag(sizeof(LIST_ENTRY) * maxEntry,
+                                                OVS_METER_TAG);
+    if (!meterGlobalTable) {
+        NdisFreeRWLock(meterGlobalTableLock);
+        return NDIS_STATUS_RESOURCES;
+    }
+
+    for (UINT32 index = 0; index < maxEntry; index++) {
+        InitializeListHead(&meterGlobalTable[index]);
+    }
+
+    return NDIS_STATUS_SUCCESS;
+}
+
+NDIS_STATUS
+FillBandIntoMeter(PNL_ATTR meterAttrs[], DpMeter *meter, PNL_MSG_HDR nlMsgHdr)
+{
+    PNL_ATTR a = NULL;
+    INT rem = 0;
+    UINT32 bandMaxDelta = 0;
+    UINT32 attrOffset = 0;
+    DpMeterBand *band = NULL;
+    PNL_ATTR bandAttrs[OVS_BAND_ATTR_MAX + 1];
+    UINT16 nBands = 0;
+
+    band = meter->bands;
+    NL_ATTR_FOR_EACH(a, rem, NlAttrData(meterAttrs[OVS_METER_ATTR_BANDS]),
+                     NlAttrGetSize(meterAttrs[OVS_METER_ATTR_BANDS])) {
+        RtlZeroMemory(bandAttrs, sizeof(bandAttrs));
+        attrOffset = (UINT32)((PCHAR)NlAttrData(a) - (PCHAR)nlMsgHdr);
+        if (!NlAttrParse(nlMsgHdr,
+                         attrOffset,
+                         NlAttrGetSize(a),
+                         bandPolicy, ARRAY_SIZE(bandPolicy),
+                         bandAttrs, ARRAY_SIZE(bandAttrs))) {
+            return STATUS_INVALID_PARAMETER;
+        }
+
+        if (bandAttrs[OVS_BAND_ATTR_TYPE]) {
+            band->type = NlAttrGetU32(bandAttrs[OVS_BAND_ATTR_TYPE]);
+        }
+
+        if (bandAttrs[OVS_BAND_ATTR_RATE]) {
+            band->rate = NlAttrGetU32(bandAttrs[OVS_BAND_ATTR_RATE]);
+        }
+
+        if (bandAttrs[OVS_BAND_ATTR_BURST]) {
+            band->burst_size = NlAttrGetU32(bandAttrs[OVS_BAND_ATTR_BURST]);
+        }
+
+        band->bucket = (band->burst_size + band->rate) * 1000;
+        bandMaxDelta = (UINT32)((band->bucket / band->rate)  / 10);
+        if (bandMaxDelta > meter->maxDelta) {
+            meter->maxDelta = bandMaxDelta;
+        }
+
+        nBands++;
+        band++;
+    }
+
+    meter->nBands = nBands;
+    return NDIS_STATUS_SUCCESS;
+}
+
+NDIS_STATUS
+OvsNewMeterCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
+                      UINT32 *replyLen)
+{
+    DpMeter *meter = NULL;
+    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
+    PNL_MSG_HDR nlMsgHdr = &(msgIn->nlMsg);
+    PGENL_MSG_HDR genlMsgHdr = &(msgIn->genlMsg);
+    POVS_HDR ovsHdr = &(msgIn->ovsHdr);
+    LOCK_STATE_EX lockState;
+    PNL_ATTR meterAttrs[ARRAY_SIZE(nlMeterPolicy)];
+    ASSERT(usrParamsCtx->inputBuffer != NULL);
+    PNL_MSG_HDR nlMsgOutHdr = NULL;
+    NL_BUFFER nlBuf;
+    NL_ERROR nlError = NL_ERROR_SUCCESS;
+
+    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
+                     NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
+                     NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
+                     nlMeterPolicy, ARRAY_SIZE(nlMeterPolicy),
+                     meterAttrs, ARRAY_SIZE(meterAttrs))) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+    }
+
+    meter = OvsAllocateMemoryWithTag(sizeof(*meter), OVS_METER_TAG);
+    if (!meter) {
+        nlError = NL_ERROR_NOMEM;
+        goto Done;
+    }
+
+    RtlZeroMemory(meter, sizeof(*meter));
+    meter->id = NlAttrGetU32(meterAttrs[OVS_METER_ATTR_ID]);
+    meter->kbps = meterAttrs[OVS_METER_ATTR_KBPS] ? 1 : 0;
+    meter->keepStatus = meterAttrs[OVS_METER_ATTR_CLEAR] ? 1 : 0;
+    if (meter->keepStatus && meterAttrs[OVS_METER_ATTR_STATS]) {
+        meter->stats = *(struct ovs_flow_stats *)NlAttrData(
+                        meterAttrs[OVS_METER_ATTR_STATS]);
+    }
+
+    if (FillBandIntoMeter(meterAttrs, meter, nlMsgHdr) != NDIS_STATUS_SUCCESS) {
+        nlError = NL_ERROR_NOMSG;
+        OvsFreeMemoryWithTag(meter, OVS_METER_TAG);
+        goto Done;
+    }
+
+    NdisAcquireRWLockWrite(meterGlobalTableLock, &lockState, 0);
+    InsertHeadList(&meterGlobalTable[meter->id & (METER_HASH_BUCKET_MAX - 1)],
+                   &(meter->link));
+    NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+
+    NlBufInit(&nlBuf, usrParamsCtx->outputBuffer, usrParamsCtx->outputLength);
+    nlMsgOutHdr = (PNL_MSG_HDR)(NlBufAt(&nlBuf, 0, 0));
+    if (!NlFillOvsMsg(&nlBuf, nlMsgHdr->nlmsgType, 0,
+                     nlMsgHdr->nlmsgSeq, nlMsgHdr->nlmsgPid,
+                     genlMsgHdr->cmd, OVS_METER_CMD_GET,
+                     ovsHdr->dp_ifindex)) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+    }
+
+    if (!buildOvsMeterReplyMsg(&nlBuf, meter)) {
+        nlError = NL_ERROR_NOMEM;
+        goto Done;
+    }
+
+    NlMsgSetSize(nlMsgOutHdr, NlBufSize(&nlBuf));
+    NlMsgAlignSize(nlMsgOutHdr);
+    *replyLen += NlMsgSize(nlMsgOutHdr);
+
+Done:
+    if (nlError != NL_ERROR_SUCCESS) {
+        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
+                usrParamsCtx->outputBuffer;
+
+        ASSERT(msgError);
+        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
+        ASSERT(*replyLen != 0);
+    }
+
+    return NDIS_STATUS_SUCCESS;
+}
+
+NDIS_STATUS OvsMeterFeatureProbe(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
+                                 UINT32 *replyLen)
+{
+    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
+    PNL_MSG_HDR nlMsgHdr = &(msgIn->nlMsg);
+    POVS_HDR ovsHdr = &(msgIn->ovsHdr);
+    PNL_MSG_HDR nlMsgOutHdr = NULL;
+    BOOLEAN ok = FALSE;
+    PGENL_MSG_HDR genlMsgHdr = &(msgIn->genlMsg);
+    NL_BUFFER nlBuf;
+    NL_ERROR nlError = NL_ERROR_SUCCESS;
+    UINT32 bandsOffset = 0;
+    UINT32 bandAttrOffset = 0;
+
+    NlBufInit(&nlBuf, usrParamsCtx->outputBuffer, usrParamsCtx->outputLength);
+    nlMsgOutHdr = (PNL_MSG_HDR)(NlBufAt(&nlBuf, 0, 0));
+    ok = NlFillOvsMsg(&nlBuf, nlMsgHdr->nlmsgType, 0,
+                      nlMsgHdr->nlmsgSeq, nlMsgHdr->nlmsgPid,
+                      genlMsgHdr->cmd, OVS_METER_CMD_FEATURES,
+                      ovsHdr->dp_ifindex);
+    if (!ok) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+    }
+
+    if (!NlMsgPutTailU32(&nlBuf, OVS_METER_ATTR_MAX_METERS, UINT32_MAX)) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+    }
+
+    if (!NlMsgPutTailU32(&nlBuf, OVS_METER_ATTR_MAX_BANDS, OVS_MAX_BANDS)) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+    }
+
+    bandsOffset = NlMsgStartNested(&nlBuf, OVS_METER_ATTR_BANDS);
+    bandAttrOffset = NlMsgStartNested(&nlBuf, OVS_METER_ATTR_UNSPEC);
+    if (!NlMsgPutTailU32(&nlBuf, OVS_BAND_ATTR_TYPE,
+                         OVS_METER_BAND_TYPE_DROP)) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+
+    }
+    NlMsgEndNested(&nlBuf, bandAttrOffset);
+    NlMsgEndNested(&nlBuf, bandsOffset);
+
+    NlMsgSetSize(nlMsgOutHdr, NlBufSize(&nlBuf));
+    NlMsgAlignSize(nlMsgOutHdr);
+    *replyLen += NlMsgSize(nlMsgOutHdr);
+
+Done:
+    if (nlError != NL_ERROR_SUCCESS) {
+        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
+                usrParamsCtx->outputBuffer;
+
+        ASSERT(msgError);
+        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
+        ASSERT(*replyLen != 0);
+    }
+
+    return STATUS_SUCCESS;
+}
+
+BOOLEAN
+buildOvsMeterReplyMsg(NL_BUFFER *nlBuf, DpMeter *dpMeter)
+{
+    BOOLEAN ok = FALSE;
+    UINT32 bandAttrOffset;
+    UINT32 bandsOffset;
+
+    /* Add meter element. */
+    ok = NlMsgPutTailU32(nlBuf, OVS_METER_ATTR_ID, dpMeter->id);
+    if (!ok) {
+        OVS_LOG_ERROR("Could not add meter id %d.", dpMeter->id);
+        return ok;
+    }
+
+    ok = NlMsgPutTailUnspec(nlBuf, OVS_METER_ATTR_STATS,
+                            (PCHAR)&(dpMeter->stats),
+                            sizeof(dpMeter->stats));
+    if (!ok) {
+        OVS_LOG_ERROR("Could not add ovs meter stats.");
+        return ok;
+    }
+
+    bandsOffset = NlMsgStartNested(nlBuf, OVS_METER_ATTR_BANDS);
+    for (int index = 0; index < dpMeter->nBands; index++) {
+        bandAttrOffset = NlMsgStartNested(nlBuf, OVS_BAND_ATTR_UNSPEC);
+        ok = NlMsgPutTailUnspec(nlBuf, OVS_BAND_ATTR_STATS,
+                                (PCHAR)&(dpMeter->bands[index].stats),
+                                sizeof(dpMeter->bands[index].stats));
+        NlMsgEndNested(nlBuf, bandAttrOffset);
+        if (!ok) {
+            break;
+        }
+    }
+
+    NlMsgEndNested(nlBuf, bandsOffset);
+    return ok;
+}
+
+NDIS_STATUS
+OvsMeterGet(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
+            UINT32 *replyLen)
+{
+    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
+    PNL_MSG_HDR nlMsgHdr = &(msgIn->nlMsg);
+    POVS_HDR ovsHdr = &(msgIn->ovsHdr);
+    PNL_MSG_HDR nlMsgOutHdr = NULL;
+    PNL_ATTR meterAttrs[ARRAY_SIZE(nlMeterPolicy)];
+    UINT32 meterId = 0;
+    DpMeter *dpMeter = NULL;
+    BOOLEAN ok = FALSE;
+    PGENL_MSG_HDR genlMsgHdr = &(msgIn->genlMsg);
+    NL_BUFFER nlBuf;
+    NL_ERROR nlError = NL_ERROR_SUCCESS;
+    LOCK_STATE_EX lockState;
+
+    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
+                     NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
+                     NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
+                     nlMeterPolicy, ARRAY_SIZE(nlMeterPolicy),
+                     meterAttrs, ARRAY_SIZE(meterAttrs))) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+    }
+
+    NlBufInit(&nlBuf, usrParamsCtx->outputBuffer, usrParamsCtx->outputLength);
+    meterId = NlAttrGetU32(meterAttrs[OVS_METER_ATTR_ID]);
+
+    /* Reply message header */
+    nlMsgOutHdr = (PNL_MSG_HDR)(NlBufAt(&nlBuf, 0, 0));
+    ok = NlFillOvsMsg(&nlBuf, nlMsgHdr->nlmsgType, 0,
+                      nlMsgHdr->nlmsgSeq, nlMsgHdr->nlmsgPid,
+                      genlMsgHdr->cmd, OVS_METER_CMD_GET,
+                      ovsHdr->dp_ifindex);
+    if (!ok) {
+        nlError = NL_ERROR_NOMSG;
+        goto Done;
+    }
+
+    NdisAcquireRWLockRead(meterGlobalTableLock, &lockState, 0);
+    dpMeter = OvsMeterLookup(meterId);
+    if (!dpMeter) {
+        OVS_LOG_WARN("Has not find %d associated meter", meterId);
+        nlError = NL_ERROR_EXIST;
+        NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+        goto Done;
+    }
+
+    if (!buildOvsMeterReplyMsg(&nlBuf, dpMeter)) {
+        nlError = NL_ERROR_NOMEM;
+        OVS_LOG_ERROR("Could not build ovs meter reply msg.");
+        NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+        goto Done;
+    }
+
+    NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+    NlMsgSetSize(nlMsgOutHdr, NlBufSize(&nlBuf));
+    NlMsgAlignSize(nlMsgOutHdr);
+    *replyLen += NlMsgSize(nlMsgOutHdr);
+
+Done:
+    if (nlError != NL_ERROR_SUCCESS) {
+        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
+                usrParamsCtx->outputBuffer;
+
+        ASSERT(msgError);
+        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
+        ASSERT(*replyLen != 0);
+    }
+
+    return NDIS_STATUS_SUCCESS;
+}
+
+NDIS_STATUS
+OvsMeterDestroy(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
+                UINT32 *replyLen)
+{
+    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
+    PNL_MSG_HDR nlMsgHdr = &(msgIn->nlMsg);
+    POVS_HDR ovsHdr = &(msgIn->ovsHdr);
+    PNL_MSG_HDR nlMsgOutHdr = NULL;
+    PNL_ATTR meterAttrs[ARRAY_SIZE(nlMeterPolicy)];
+    PGENL_MSG_HDR genlMsgHdr = &(msgIn->genlMsg);
+    LOCK_STATE_EX lockState;
+    UINT32 meterId = 0;
+    BOOLEAN ok;
+    NL_BUFFER nlBuf;
+    NL_ERROR nlError = NL_ERROR_SUCCESS;
+
+    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
+                     NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
+                     NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
+                     nlMeterPolicy, ARRAY_SIZE(nlMeterPolicy),
+            meterAttrs, ARRAY_SIZE(meterAttrs))) {
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    NlBufInit(&nlBuf, usrParamsCtx->outputBuffer, usrParamsCtx->outputLength);
+
+    meterId = NlAttrGetU32(meterAttrs[OVS_METER_ATTR_ID]);
+    nlMsgOutHdr = (PNL_MSG_HDR)(NlBufAt(&nlBuf, 0, 0));
+    ok = NlFillOvsMsg(&nlBuf, nlMsgHdr->nlmsgType, 0,
+                      nlMsgHdr->nlmsgSeq, nlMsgHdr->nlmsgPid,
+                      genlMsgHdr->cmd, OVS_METER_CMD_DEL,
+                      ovsHdr->dp_ifindex);
+    if (!ok) {
+        nlError = NL_ERROR_NOMEM;
+        goto Done;
+    }
+
+    NdisAcquireRWLockWrite(meterGlobalTableLock, &lockState, 0);
+    PLIST_ENTRY head = &meterGlobalTable[meterId & (METER_HASH_BUCKET_MAX - 1)];
+    PLIST_ENTRY link, next;
+    DpMeter *entry = NULL;
+
+    LIST_FORALL_SAFE(head, link, next) {
+        entry = CONTAINING_RECORD(link, DpMeter, link);
+        if (entry->id == meterId) {
+            if (!buildOvsMeterReplyMsg(&nlBuf, entry)) {
+                nlError = NL_ERROR_NOMEM;
+                NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+                goto Done;
+            }
+            RemoveEntryList(&entry->link);
+            OvsFreeMemoryWithTag(entry, OVS_METER_TAG);
+            break;
+        }
+    }
+
+    NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+    NlMsgSetSize(nlMsgOutHdr, NlBufSize(&nlBuf));
+    NlMsgAlignSize(nlMsgOutHdr);
+    *replyLen += NlMsgSize(nlMsgOutHdr);
+
+Done:
+    if (nlError != NL_ERROR_SUCCESS) {
+        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
+                usrParamsCtx->outputBuffer;
+
+        ASSERT(msgError);
+        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
+        ASSERT(*replyLen != 0);
+    }
+
+    return NDIS_STATUS_SUCCESS;
+}
+
+
+
+DpMeter*
+OvsMeterLookup(UINT32 meterId)
+{
+    PLIST_ENTRY head = &meterGlobalTable[meterId & (METER_HASH_BUCKET_MAX - 1)];
+    PLIST_ENTRY link, next;
+    DpMeter *entry = NULL;
+
+    LIST_FORALL_SAFE(head, link, next) {
+        entry = CONTAINING_RECORD(link, DpMeter, link);
+        if (entry->id == meterId) {
+            return entry;
+        }
+    }
+
+    return NULL;
+}
+
+BOOLEAN
+OvsMeterExecute(OvsForwardingContext *fwdCtx, UINT32 meterId)
+{
+    DpMeter *dpMeter;
+    DpMeterBand *band;
+    UINT32 longDeltaMs;
+    UINT32 deltaMs;
+    UINT64 currentTime;
+    LOCK_STATE_EX lockState;
+    UINT32 cost;
+    UINT32 bandExceededRate = 0;
+    INT32 bandExceedIndex = -1;
+    UINT64 maxBucketSize = 0;
+
+    NdisAcquireRWLockRead(meterGlobalTableLock, &lockState, 0);
+    dpMeter = OvsMeterLookup(meterId);
+    if (!dpMeter) {
+        OVS_LOG_ERROR("Not found meter id %d associated meter.", meterId);
+        NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+        return FALSE;
+    }
+
+    NdisGetCurrentSystemTime((LARGE_INTEGER *)&currentTime);
+    /* currentTime represent count of 100-nanosecond intervals, to convert it to
+     * ms, we need to divide 10000. */
+    longDeltaMs = (UINT32)((currentTime - dpMeter->used) / 10000);
+    deltaMs = longDeltaMs > dpMeter->maxDelta ? dpMeter->maxDelta :
+                longDeltaMs;
+    dpMeter->used = currentTime;
+    dpMeter->stats.n_packets += 1;
+    dpMeter->stats.n_bytes += OvsPacketLenNBL(fwdCtx->curNbl);
+    cost = dpMeter->kbps ? OvsPacketLenNBL(fwdCtx->curNbl) * 8 : 1000;
+    for (int index = 0; index < dpMeter->nBands; index++) {
+        band = &(dpMeter->bands[index]);
+        maxBucketSize = (band->burst_size + band->rate) * 1000LL;
+        band->bucket += deltaMs * band->rate;
+        if (band->bucket > maxBucketSize) {
+            band->bucket = maxBucketSize;
+        }
+
+        if (band->bucket >= cost) {
+            band->bucket -= cost;
+            band->stats.n_packets += 1;
+            band->stats.n_bytes += OvsPacketLenNBL(fwdCtx->curNbl);
+        } else if (band->rate > bandExceededRate) {
+            bandExceededRate = band->rate;
+            bandExceedIndex = index;
+        }
+    }
+
+    if (bandExceedIndex >= 0) {
+        band = &(dpMeter->bands[bandExceedIndex]);
+        band->stats.n_packets += 1;
+        band->stats.n_bytes += OvsPacketLenNBL(fwdCtx->curNbl);
+        if (band->type == OVS_METER_BAND_TYPE_DROP) {
+            NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+            return TRUE;
+        }
+    }
+
+    NdisReleaseRWLock(meterGlobalTableLock, &lockState);
+    return FALSE;
+}
diff --git a/datapath-windows/ovsext/Meter.h b/datapath-windows/ovsext/Meter.h
new file mode 100644
index 000000000..bc94489a5
--- /dev/null
+++ b/datapath-windows/ovsext/Meter.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2022 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVS_METER_H
+#define OVS_METER_H
+
+#include "precomp.h"
+#include "Switch.h"
+#include "User.h"
+#include "Datapath.h"
+#include "Event.h"
+#include "NetProto.h"
+#include "Netlink/Netlink.h"
+#include "Flow.h"
+
+#define OVS_MAX_BANDS 1
+#define OVS_MAX_METERS 32
+#define METER_HASH_BUCKET_MAX 1024
+
+typedef struct _DpMeterBand {
+    UINT32 type;
+    UINT32 rate;
+    UINT32 burst_size;
+    UINT64 bucket;
+    struct ovs_flow_stats stats;
+} DpMeterBand;
+
+typedef struct _DpMeter {
+    LIST_ENTRY link;
+    UINT32 id;
+    UINT16 kbps:1;
+    UINT16 keepStatus:1;
+    UINT16 nBands;
+    UINT32 maxDelta;
+    UINT64 used;
+    struct ovs_flow_stats stats;
+    DpMeterBand  bands[OVS_MAX_BANDS];
+} DpMeter;
+
+NTSTATUS OvsInitMeter(POVS_SWITCH_CONTEXT context);
+NDIS_STATUS OvsNewMeterCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
+                                  UINT32 *replyLen);
+NDIS_STATUS OvsMeterFeatureProbe(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
+UINT32 *replyLen);
+
+NDIS_STATUS OvsMeterDestroy(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
+                                   UINT32 *replyLen);
+DpMeter* OvsMeterLookup(UINT32 meterId);
+BOOLEAN
+OvsMeterExecute(OvsForwardingContext *fwdCtx, UINT32 meterId);
+BOOLEAN
+buildOvsMeterReplyMsg(NL_BUFFER *nlBuf, DpMeter *dpMeter);
+
+
+#endif //OVS_METER_H
diff --git a/datapath-windows/ovsext/Switch.c b/datapath-windows/ovsext/Switch.c
index 1ac4fa77c..088aee21a 100644
--- a/datapath-windows/ovsext/Switch.c
+++ b/datapath-windows/ovsext/Switch.c
@@ -24,6 +24,7 @@
 #include "Switch.h"
 #include "Vport.h"
 #include "Event.h"
+#include "Meter.h"
 #include "Flow.h"
 #include "IpHelper.h"
 #include "Oid.h"
@@ -239,6 +240,13 @@ OvsCreateSwitch(NDIS_HANDLE ndisFilterHandle,
         goto create_switch_done;
     }
 
+    status = OvsInitMeter(switchContext);
+    if (status != STATUS_SUCCESS) {
+        OvsUninitSwitchContext(switchContext);
+        OVS_LOG_ERROR("Exit: Failed to initialize Ovs meter.");
+        goto create_switch_done;
+    }
+
     *switchContextOut = switchContext;
 
 create_switch_done:
diff --git a/datapath-windows/ovsext/Util.h b/datapath-windows/ovsext/Util.h
index f63a885a9..16903ed46 100644
--- a/datapath-windows/ovsext/Util.h
+++ b/datapath-windows/ovsext/Util.h
@@ -38,6 +38,7 @@
 #define OVS_TUNFLT_POOL_TAG             'WSVO'
 #define OVS_RECIRC_POOL_TAG             'CSVO'
 #define OVS_CT_POOL_TAG                 'CTVO'
+#define OVS_METER_TAG                   'MEVO'
 #define OVS_GENEVE_POOL_TAG             'GNVO'
 #define OVS_IPFRAG_POOL_TAG             'FGVO'
 
diff --git a/datapath-windows/ovsext/ovsext.vcxproj b/datapath-windows/ovsext/ovsext.vcxproj
index 7a2cbd2de..6e54b71f9 100644
--- a/datapath-windows/ovsext/ovsext.vcxproj
+++ b/datapath-windows/ovsext/ovsext.vcxproj
@@ -164,6 +164,7 @@
     <ClInclude Include="IpFragment.h" />
     <ClInclude Include="IpHelper.h" />
     <ClInclude Include="Jhash.h" />
+    <ClInclude Include="Meter.h" />
     <ClInclude Include="Mpls.h" />
     <ClInclude Include="Netlink/Netlink.h" />
     <ClInclude Include="Netlink/NetlinkBuf.h" />
@@ -408,6 +409,7 @@
     <ClCompile Include="IpFragment.c" />
     <ClCompile Include="IpHelper.c" />
     <ClCompile Include="Jhash.c" />
+    <ClCompile Include="Meter.c" />
     <ClCompile Include="Netlink/Netlink.c" />
     <ClCompile Include="Netlink/NetlinkBuf.c" />
     <ClCompile Include="Datapath.c" />
diff --git a/lib/netlink-socket.c b/lib/netlink-socket.c
index 93c1fa561..80da20d9f 100644
--- a/lib/netlink-socket.c
+++ b/lib/netlink-socket.c
@@ -1574,6 +1574,11 @@ do_lookup_genl_family(const char *name, struct nlattr **attrs,
         family_name = OVS_FLOW_FAMILY;
         family_version = OVS_FLOW_VERSION;
         family_attrmax = OVS_FLOW_ATTR_MAX;
+    } else if (!strcmp(name, OVS_METER_FAMILY)) {
+        family_id =  OVS_WIN_NL_METER_FAMILY_ID;
+        family_name = OVS_METER_FAMILY;
+        family_version = OVS_METER_VERSION;
+        family_attrmax = __OVS_METER_ATTR_MAX;
     } else if (!strcmp(name, OVS_WIN_NETDEV_FAMILY)) {
         family_id = OVS_WIN_NL_NETDEV_FAMILY_ID;
         family_name = OVS_WIN_NETDEV_FAMILY;
