diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 49a2e26124..1ae496960e 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -116,6 +116,8 @@ struct ovn_extend_table;
     OVNACT(PUT_FDB,           ovnact_put_fdb)         \
     OVNACT(GET_FDB,           ovnact_get_fdb)         \
     OVNACT(LOOKUP_FDB,        ovnact_lookup_fdb)      \
+    OVNACT(CHECK_IN_PORT_SEC,  ovnact_result)         \
+    OVNACT(CHECK_OUT_PORT_SEC, ovnact_result)         \
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
diff --git a/lib/actions.c b/lib/actions.c
index a9c27600c7..36d4a33ae5 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -4004,19 +4004,20 @@ format_CHK_LB_HAIRPIN_REPLY(const struct ovnact_result *res, struct ds *s)
 }
 
 static void
-encode_chk_lb_hairpin__(const struct ovnact_result *res,
-                        uint8_t hairpin_table,
-                        struct ofpbuf *ofpacts)
+encode_result_action__(const struct ovnact_result *res,
+                       uint8_t resubmit_table,
+                       int log_flags_result_bit,
+                       struct ofpbuf *ofpacts)
 {
     struct mf_subfield dst = expr_resolve_field(&res->dst);
     ovs_assert(dst.field);
-    put_load(0, MFF_LOG_FLAGS, MLF_LOOKUP_LB_HAIRPIN_BIT, 1, ofpacts);
-    emit_resubmit(ofpacts, hairpin_table);
+    put_load(0, MFF_LOG_FLAGS, log_flags_result_bit, 1, ofpacts);
+    emit_resubmit(ofpacts, resubmit_table);
 
     struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
     orm->dst = dst;
     orm->src.field = mf_from_id(MFF_LOG_FLAGS);
-    orm->src.ofs = MLF_LOOKUP_LB_HAIRPIN_BIT;
+    orm->src.ofs = log_flags_result_bit;
     orm->src.n_bits = 1;
 }
 
@@ -4025,7 +4026,8 @@ encode_CHK_LB_HAIRPIN(const struct ovnact_result *res,
                       const struct ovnact_encode_params *ep,
                       struct ofpbuf *ofpacts)
 {
-    encode_chk_lb_hairpin__(res, ep->lb_hairpin_ptable, ofpacts);
+    encode_result_action__(res, ep->lb_hairpin_ptable,
+                           MLF_LOOKUP_LB_HAIRPIN_BIT, ofpacts);
 }
 
 static void
@@ -4033,7 +4035,8 @@ encode_CHK_LB_HAIRPIN_REPLY(const struct ovnact_result *res,
                             const struct ovnact_encode_params *ep,
                             struct ofpbuf *ofpacts)
 {
-    encode_chk_lb_hairpin__(res, ep->lb_hairpin_reply_ptable, ofpacts);
+    encode_result_action__(res, ep->lb_hairpin_reply_ptable,
+                           MLF_LOOKUP_LB_HAIRPIN_BIT, ofpacts);
 }
 
 static void
@@ -4216,6 +4219,54 @@ ovnact_lookup_fdb_free(struct ovnact_lookup_fdb *get_fdb OVS_UNUSED)
 {
 }
 
+static void
+parse_check_in_port_sec(struct action_context *ctx,
+                        const struct expr_field *dst,
+                        struct ovnact_result *dl)
+{
+    parse_ovnact_result(ctx, "check_in_port_sec", NULL, dst, dl);
+}
+
+static void
+format_CHECK_IN_PORT_SEC(const struct ovnact_result *dl, struct ds *s)
+{
+    expr_field_format(&dl->dst, s);
+    ds_put_cstr(s, " = check_in_port_sec();");
+}
+
+static void
+encode_CHECK_IN_PORT_SEC(const struct ovnact_result *dl,
+                         const struct ovnact_encode_params *ep,
+                         struct ofpbuf *ofpacts)
+{
+    encode_result_action__(dl, ep->in_port_sec_ptable,
+                           MLF_CHECK_PORT_SEC_BIT, ofpacts);
+}
+
+static void
+parse_check_out_port_sec(struct action_context *ctx,
+                         const struct expr_field *dst,
+                         struct ovnact_result *dl)
+{
+    parse_ovnact_result(ctx, "check_out_port_sec", NULL, dst, dl);
+}
+
+static void
+format_CHECK_OUT_PORT_SEC(const struct ovnact_result *dl, struct ds *s)
+{
+    expr_field_format(&dl->dst, s);
+    ds_put_cstr(s, " = check_out_port_sec();");
+}
+
+static void
+encode_CHECK_OUT_PORT_SEC(const struct ovnact_result *dl,
+                         const struct ovnact_encode_params *ep,
+                         struct ofpbuf *ofpacts)
+{
+    encode_result_action__(dl, ep->out_port_sec_ptable,
+                           MLF_CHECK_PORT_SEC_BIT, ofpacts);
+}
+
 /* Parses an assignment or exchange or put_dhcp_opts action. */
 static void
 parse_set_action(struct action_context *ctx)
@@ -4284,6 +4335,14 @@ parse_set_action(struct action_context *ctx)
                    && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
             parse_lookup_fdb(
                 ctx, &lhs, ovnact_put_LOOKUP_FDB(ctx->ovnacts));
+        } else if (!strcmp(ctx->lexer->token.s, "check_in_port_sec")
+                   && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+            parse_check_in_port_sec(
+                ctx, &lhs, ovnact_put_CHECK_IN_PORT_SEC(ctx->ovnacts));
+        } else if (!strcmp(ctx->lexer->token.s, "check_out_port_sec")
+                   && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+            parse_check_out_port_sec(
+                ctx, &lhs, ovnact_put_CHECK_OUT_PORT_SEC(ctx->ovnacts));
         } else {
             parse_assignment_action(ctx, false, &lhs);
         }
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 086edddef4..4c35dda366 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2514,6 +2514,45 @@ tcp.flags = RST;
             action to advance the packet to the next stage.
           </p>
         </dd>
+
+        <dt><code><var>R</var> = check_in_port_sec();</code></dt>
+        <dd>
+          <p>
+            This action checks if the packet under consideration passes the
+            inport port security checks.  If the packet fails the port security
+            checks, then <code>1</code> is stored in the destination register
+            <var>R</var>.  Else 0 is stored.  The port security values to check
+            are retrieved from the the <code>inport</code> logical port.
+          </p>
+
+          <p>
+            This action should be used in the ingress logical switch pipeline.
+
+          </p>
+          <p>
+            <b>Example:</b> <code>reg8[0..7] = check_in_port_sec();</code>
+          </p>
+        </dd>
+
+        <dt><code><var>R</var> = check_out_port_sec();</code></dt>
+        <dd>
+          <p>
+            This action checks if the packet under consideration passes the
+            outport port security checks.  If the packet fails the port
+            security checks, then <code>1</code> is stored in the destination
+            register <var>R</var>.  Else 0 is stored.  The port security
+            values to check are retrieved from the the <code>outport</code>
+            logical port.
+          </p>
+
+          <p>
+            This action should be used in the egress logical switch pipeline.
+
+          </p>
+          <p>
+            <b>Example:</b> <code>reg8[0..7] = check_out_port_sec();</code>
+          </p>
+        </dd>
       </dl>
     </column>
 
diff --git a/tests/ovn.at b/tests/ovn.at
index e3d65b1cce..45cc6d5483 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2037,6 +2037,38 @@ reg1[1] = lookup_fdb(outport, ip4.src);
 reg1[1] = lookup_fdb(ip4.src, eth.src);
     Cannot use numeric field ip4.src where string field is required.
 
+# check_in_port_sec
+reg0[0] = check_in_port_sec();
+    encodes as set_field:0/0x1000->reg10,resubmit(,73),move:NXM_NX_REG10[12]->NXM_NX_XXREG0[96]
+
+reg2[2] = check_in_port_sec();
+    encodes as set_field:0/0x1000->reg10,resubmit(,73),move:NXM_NX_REG10[12]->NXM_NX_XXREG0[34]
+
+reg0 = check_in_port_sec();
+    Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
+
+reg0[0] = check_in_port_sec(foo);
+    check_in_port_sec doesn't take any parameters
+
+check_in_port_sec;
+    Syntax error at `check_in_port_sec' expecting action.
+
+# check_out_port_sec
+reg0[0] = check_out_port_sec();
+    encodes as set_field:0/0x1000->reg10,resubmit(,75),move:NXM_NX_REG10[12]->NXM_NX_XXREG0[96]
+
+reg2[2..3] = check_out_port_sec();
+    Cannot use 2-bit field reg2[2..3] where 1-bit field is required.
+
+reg0 = check_out_port_sec();
+    Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
+
+reg0[0] = check_out_port_sec(foo);
+    check_out_port_sec doesn't take any parameters
+
+check_out_port_sec;
+    Syntax error at `check_out_port_sec' expecting action.
+
 # push/pop
 push(xxreg0);push(xxreg1[10..20]);push(eth.src);pop(xxreg0[0..47]);pop(xxreg0[48..57]);pop(xxreg1);
     formats as push(xxreg0); push(xxreg1[10..20]); push(eth.src); pop(xxreg0[0..47]); pop(xxreg0[48..57]); pop(xxreg1);
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index 6704f612bf..2a84f79ede 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1352,6 +1352,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
                 .fdb_ptable = OFTABLE_GET_FDB,
                 .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
                 .common_nat_ct_zone = MFF_LOG_DNAT_ZONE,
+                .in_port_sec_ptable = OFTABLE_CHK_IN_PORT_SEC,
+                .out_port_sec_ptable = OFTABLE_CHK_OUT_PORT_SEC,
             };
             struct ofpbuf ofpacts;
             ofpbuf_init(&ofpacts, 0);
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 6abd9a83e4..c4110de0ac 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -431,6 +431,8 @@ struct ovntrace_port {
     uint16_t tunnel_key;
     struct ovntrace_port *peer; /* Patch ports only. */
     struct ovntrace_port *distributed_port; /* chassisredirect ports only. */
+    struct lport_addresses *ps_addrs;   /* Port security addresses. */
+    size_t n_ps_addrs;
 };
 
 struct ovntrace_mcgroup {
@@ -747,6 +749,22 @@ read_ports(void)
                 }
             }
         }
+
+        port->n_ps_addrs = 0;
+        port->ps_addrs =
+            xmalloc(sizeof *port->ps_addrs * sbpb->n_port_security);
+        for (size_t i = 0; i < sbpb->n_port_security; i++) {
+            if (!extract_lsp_addresses(sbpb->port_security[i],
+                                        &port->ps_addrs[port->n_ps_addrs])) {
+                static struct vlog_rate_limit rl
+                    = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_INFO_RL(&rl, "invalid syntax '%s' in port "
+                            "security. No MAC address found",
+                            sbpb->port_security[i]);
+                continue;
+            }
+            port->n_ps_addrs++;
+        }
     }
 
     SBREC_PORT_BINDING_FOR_EACH (sbpb, ovnsb_idl) {
@@ -2621,6 +2639,291 @@ execute_ct_snat_to_vip(struct flow *uflow OVS_UNUSED, struct ovs_list *super)
                          "*** ct_snat_to_vip action not implemented");
 }
 
+static bool
+check_in_port_sec_arp(const struct flow *uflow,
+                      struct lport_addresses *ps_addr)
+{
+    /* arp.sha should match the ps_addr's ea. */
+    if (!eth_addr_equals(uflow->arp_sha, ps_addr->ea)) {
+        return false;
+    }
+
+    if (!ps_addr->n_ipv4_addrs) {
+        return true;
+    }
+
+    /* arp.spa should match the allowed IPv4 addresses. */
+    for (size_t i = 0; i < ps_addr->n_ipv4_addrs; i++) {
+        if (uflow->nw_src == ps_addr->ipv4_addrs[i].addr) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static bool
+check_in_port_sec_ip4(const struct flow *uflow,
+                      struct lport_addresses *ps_addr)
+{
+    /* No IPs present in ps_addr.  Allow all IPs. */
+    if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+        return true;
+    }
+
+    /* ip4.src should match the allowed IPv4 addresses. */
+    for (size_t i = 0; i < ps_addr->n_ipv4_addrs; i++) {
+        if (uflow->nw_src == ps_addr->ipv4_addrs[i].addr) {
+            return true;
+        }
+    }
+
+    /* If its dhcp packet, then the ip4.src == 0.0.0.0 is allowed if
+     * ip4.dst == 255.255.255.255. */
+    if (uflow->nw_proto == IPPROTO_UDP && uflow->tp_src == htons(68) &&
+            uflow->tp_dst == htons(67) && uflow->nw_src == htonl(0) &&
+            uflow->nw_dst == htonl(0xffffffff)) {
+        return true;
+    }
+
+    return false;
+}
+
+static bool
+check_in_port_sec_ip6(const struct flow *uflow,
+                      struct lport_addresses *ps_addr)
+{
+    /* No IPs present in ps_addr.  Allow all IPs. */
+    if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+        return true;
+    }
+
+    /* ip6.src should match the allowed IPv6 addresses. */
+    bool passed = false;
+    for (size_t i = 0; i < ps_addr->n_ipv6_addrs; i++) {
+        if (ipv6_addr_equals(&uflow->ipv6_src, &ps_addr->ipv6_addrs[i].addr)) {
+            passed = true;
+            break;
+        }
+    }
+
+    struct in6_addr lla;
+    in6_generate_lla(ps_addr->ea, &lla);
+
+    if (!passed && !ipv6_addr_equals(&uflow->ipv6_src, &lla)) {
+        return false;
+    }
+
+    if (uflow->nw_proto == IPPROTO_ICMPV6) {
+        if (ntohs(uflow->tp_src) == 135) {
+            if (!eth_addr_equals(uflow->arp_sha, eth_addr_zero) &&
+                 !eth_addr_equals(uflow->arp_sha, ps_addr->ea)) {
+                return false;
+            }
+        }
+
+        if (ntohs(uflow->tp_src) == 136) {
+            if (!eth_addr_equals(uflow->arp_tha, eth_addr_zero) &&
+                 !eth_addr_equals(uflow->arp_tha, ps_addr->ea)) {
+                return false;
+            }
+
+            if (ps_addr->n_ipv6_addrs) {
+                passed = false;
+                for (size_t i = 0; i < ps_addr->n_ipv6_addrs; i++) {
+                    if (ipv6_addr_equals(&uflow->nd_target,
+                                         &ps_addr->ipv6_addrs[i].addr)) {
+                        passed = true;
+                        break;
+                    }
+                }
+
+                if (!passed && !ipv6_addr_equals(&uflow->nd_target, &lla)) {
+                    return false;
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+static void
+execute_check_in_port_sec(const struct ovnact_result *dl,
+                          const struct ovntrace_datapath *dp,
+                          struct flow *uflow)
+{
+    struct mf_subfield sf = expr_resolve_field(&dl->dst);
+    union mf_subvalue sv = { .u8_val = 1 };
+
+    /* Get the input port .*/
+    uint32_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0];
+    ovs_assert(in_key);
+    const struct ovntrace_port *inport = ovntrace_port_find_by_key(dp, in_key);
+    ovs_assert(inport);
+
+    /* No port security is enabled on the input port.
+     * Set result bit 'sv' to 0. */
+    if (!inport->n_ps_addrs) {
+        sv.u8_val = 0;
+        mf_write_subfield_flow(&sf, &sv, uflow);
+        return;
+    }
+
+    bool allow = false;
+    for (size_t i = 0; i < inport->n_ps_addrs; i++) {
+        struct lport_addresses *ps_addr = &inport->ps_addrs[i];
+
+        /* Check L2 first. */
+        if (!eth_addr_equals(uflow->dl_src, ps_addr->ea)) {
+            continue;
+        }
+
+        if (uflow->dl_type == htons(ETH_TYPE_IP)) {
+            allow = check_in_port_sec_ip4(uflow, ps_addr);
+        } else if (uflow->dl_type == htons(ETH_TYPE_ARP)) {
+            allow = check_in_port_sec_arp(uflow, ps_addr);
+        } else if (uflow->dl_type == htons(ETH_TYPE_IPV6)) {
+            allow = check_in_port_sec_ip6(uflow, ps_addr);
+        } else {
+            allow = true;
+        }
+        break;
+    }
+
+    if (allow) {
+        sv.u8_val = 0;
+    }
+
+    mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
+static bool
+check_out_port_sec_ip4(const struct flow *uflow,
+                       struct lport_addresses *ps_addr)
+{
+    /* No IPs present in ps_addr.  Allow all IPs. */
+    if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+        return true;
+    }
+
+    if (!ps_addr->n_ipv4_addrs) {
+        /* Only IPv6 is allowed. */
+        return false;
+    }
+
+    /* ip4.dst should match from the allowed IPv4 addresses. */
+    for (size_t i = 0; i < ps_addr->n_ipv4_addrs; i++) {
+        if (uflow->nw_dst == ps_addr->ipv4_addrs[i].addr) {
+            return true;
+        }
+    }
+
+    if (uflow->nw_dst == htonl(0xffffffff)) {
+        return true;
+    }
+
+    ovs_be32 mcast_network, mcast_mask;
+    mcast_network = htonl(0xe0000000);
+    mcast_mask = htonl(0xf0000000);
+    if (!((mcast_network ^ uflow->nw_dst) & mcast_mask)) {
+        return true;
+    }
+
+    return false;
+}
+
+static bool
+check_out_port_sec_ip6(const struct flow *uflow,
+                       struct lport_addresses *ps_addr)
+{
+    /* No IPs present in ps_addr.  Allow all IPs. */
+    if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+        return true;
+    }
+
+    if (!ps_addr->n_ipv6_addrs) {
+        /* Only IPv4 is allowed. */
+        return false;
+    }
+
+    /* ip6.dst should match from the allowed IPv6 addresses. */
+    for (size_t i = 0; i < ps_addr->n_ipv6_addrs; i++) {
+        if (ipv6_addr_equals(&uflow->ipv6_dst, &ps_addr->ipv6_addrs[i].addr)) {
+            return true;
+        }
+    }
+
+    struct in6_addr lla;
+    in6_generate_lla(ps_addr->ea, &lla);
+
+    if (ipv6_addr_equals(&uflow->ipv6_dst, &lla)) {
+        return true;
+    }
+
+    struct in6_addr mcast6_network, mcast6_mask;
+    char *error = ipv6_parse_masked("ff00::/8", &mcast6_network, &mcast6_mask);
+    ovs_assert(!error);
+
+    struct in6_addr ip6_mask = ipv6_addr_bitxor(&uflow->ipv6_dst,
+                                                &mcast6_network);
+    ip6_mask = ipv6_addr_bitand(&ip6_mask, &mcast6_mask);
+    if (ipv6_mask_is_any(&ip6_mask)) {
+        return true;
+    }
+
+    return false;
+}
+
+static void
+execute_check_out_port_sec(const struct ovnact_result *dl,
+                           const struct ovntrace_datapath *dp,
+                           struct flow *uflow)
+{
+    struct mf_subfield sf = expr_resolve_field(&dl->dst);
+    union mf_subvalue sv = { .u8_val = 1 };
+
+    /* Get the output port .*/
+    uint32_t out_key = uflow->regs[MFF_LOG_OUTPORT - MFF_REG0];
+    ovs_assert(out_key);
+    const struct ovntrace_port *outport =
+        ovntrace_port_find_by_key(dp, out_key);
+    ovs_assert(outport);
+
+    /* No port security is enabled on the output port.
+     * Set result bit 'sv' to 0. */
+    if (!outport->n_ps_addrs) {
+        sv.u8_val = 0;
+        mf_write_subfield_flow(&sf, &sv, uflow);
+        return;
+    }
+
+    bool allow = false;
+    for (size_t i = 0; i < outport->n_ps_addrs; i++) {
+        struct lport_addresses *ps_addr = &outport->ps_addrs[i];
+
+        /* Check L2 first. */
+        if (!eth_addr_equals(uflow->dl_dst, ps_addr->ea)) {
+            continue;
+        }
+
+        if (uflow->dl_type == htons(ETH_TYPE_IP)) {
+            allow = check_out_port_sec_ip4(uflow, ps_addr);
+        } else if (uflow->dl_type == htons(ETH_TYPE_IPV6)) {
+            allow = check_out_port_sec_ip6(uflow, ps_addr);
+        } else {
+            allow = true;
+        }
+        break;
+    }
+
+    if (allow) {
+        sv.u8_val = 0;
+    }
+
+    mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
 static void
 trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
               const struct ovntrace_datapath *dp, struct flow *uflow,
@@ -2911,6 +3214,16 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
         case OVNACT_LOOKUP_FDB:
             execute_lookup_fdb(ovnact_get_LOOKUP_FDB(a), dp, uflow, super);
             break;
+
+        case OVNACT_CHECK_IN_PORT_SEC:
+            execute_check_in_port_sec(ovnact_get_CHECK_IN_PORT_SEC(a),
+                                      dp, uflow);
+            break;
+
+        case OVNACT_CHECK_OUT_PORT_SEC:
+            execute_check_out_port_sec(ovnact_get_CHECK_OUT_PORT_SEC(a),
+                                       dp, uflow);
+            break;
         }
     }
     ofpbuf_uninit(&stack);
