diff --git a/bpf/ovn_xdp.c b/bpf/ovn_xdp.c
index 9515a1d1bf..d0c91602de 100644
--- a/bpf/ovn_xdp.c
+++ b/bpf/ovn_xdp.c
@@ -1,10 +1,155 @@
 /* SPDX-License-Identifier: GPL-2.0 */
+#include <arpa/inet.h>
 #include <linux/bpf.h>
+#include <linux/in.h>
+#include <stddef.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/ip.h>
 #include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_endian.h>
+
+#define ETH_ALEN 6
+#define VIF_ADDR_FLAG_ETH_SET 0x01
+#define VIF_ADDR_FLAG_IP_SET  0x02
+
+#define ETH_P_IP 0x0800
+
+#define OVN_CHECK_PORT_SEC_MAC      0x00000001
+#define OVN_CHECK_PORT_SEC_MAC_IP   0x00000002
+
+
+struct bpf_map_def SEC("maps") ovn_vif_map = {
+    .type        = BPF_MAP_TYPE_ARRAY,
+    .key_size    = sizeof(__u32),
+    .value_size  = sizeof(__u32),
+    .max_entries = 1,
+};
+
+/* Map for mac port security table*/
+struct bpf_map_def SEC("maps") port_sec_mac_table = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(__u64),
+    .value_size = sizeof(__u8),
+    .max_entries = 4,
+};
+
+struct lpm_mac_ip_key {
+    struct bpf_lpm_trie_key trie_key;
+    __u8 data[10]; /* 6 bytes for mac, 4 bytes for ip */
+};
+
+/* Map for mac + ip4 port security table*/
+struct bpf_map_def SEC("maps") port_sec_mac_ip_table = {
+    .type = BPF_MAP_TYPE_LPM_TRIE,
+    .key_size = sizeof(struct lpm_mac_ip_key),
+    .value_size = sizeof(__u8),
+    .max_entries = 4,
+    .map_flags = BPF_F_NO_PREALLOC,
+};
+
+static inline __u64
+eth_addr_to_uint64(unsigned char ethaddr[ETH_ALEN]) {
+    return ((uint64_t) ethaddr[0] << 40) |
+           ((uint64_t) ethaddr[1] << 32) |
+           ((uint64_t) ethaddr[2] << 24) |
+           ((uint64_t) ethaddr[3] << 16) |
+           ((uint64_t) ethaddr[4] << 8) |
+           ((uint64_t) ethaddr[5] << 0);
+}
 
 SEC("xdp")
 int  xdp_ovn_vif(struct xdp_md *xdp)
 {
+    __u32 key = 0;
+
+    void *data_end = (void *)(long)xdp->data_end;
+    void *data = (void *)(long)xdp->data;
+    struct ethhdr *eth = data;
+
+    int ret = XDP_PASS;
+
+    if (data + sizeof(*eth) > data_end) {
+        return XDP_DROP;
+    }
+
+    __u32 *vif_flags = bpf_map_lookup_elem(&ovn_vif_map, &key);
+    if (!vif_flags || !(*vif_flags)) {
+        /* No checks to be done. */
+        return XDP_PASS;
+    }
+
+    if (eth->h_source[0] & 0x01) {
+        /* Multicast eth src. Drop it. */
+        return XDP_DROP;
+    }
+
+    if (eth->h_proto == htons(ETH_P_ARP)) {
+        __u8 ps_check_pass = 0;
+        __u64 src_mac;
+
+        src_mac = eth_addr_to_uint64(eth->h_source);
+        if (bpf_map_lookup_elem(&port_sec_mac_table, &src_mac)) {
+            ps_check_pass = 1;
+        }
+
+        if (!ps_check_pass) {
+            return XDP_DROP;
+        }
+
+        /* TODO.  Inspect the arp header and check if arp.sha is allowed or not. */
+        return XDP_PASS;
+    } else if (eth->h_proto == htons(ETH_P_IP)) {
+        __u8 ps_check_pass = 0;
+        if (*vif_flags & OVN_CHECK_PORT_SEC_MAC_IP) {
+            struct iphdr *iph;
+            struct lpm_mac_ip_key key = {
+                .trie_key = {
+                    .prefixlen = 80,
+                },
+            };
+
+            iph = (struct iphdr *)(eth + 1);
+            if ((void *)(iph + 1) > data_end) {
+                return XDP_DROP;
+            }
+
+            key.data[0] = eth->h_source[0];
+            key.data[1] = eth->h_source[1];
+            key.data[2] = eth->h_source[2];
+            key.data[3] = eth->h_source[3];
+            key.data[4] = eth->h_source[4];
+            key.data[5] = eth->h_source[5];
+
+            key.data[6] = iph->saddr & 0xff;
+            key.data[7] = (iph->saddr >> 8) & 0xff;
+            key.data[8] = (iph->saddr >> 16) & 0xff;
+            key.data[9] = (iph->saddr >> 24) & 0xff;
+
+            __u8 *v;
+            v = bpf_map_lookup_elem(&port_sec_mac_ip_table, &key);
+            if (v && *v) {
+                ps_check_pass = 1;
+            }
+        }
+
+        if (!ps_check_pass && (*vif_flags & OVN_CHECK_PORT_SEC_MAC)) {
+            __u64 src_mac;
+            __u8 *v;
+
+            src_mac = eth_addr_to_uint64(eth->h_source);
+            v = bpf_map_lookup_elem(&port_sec_mac_table, &src_mac);
+            if (v && *v) {
+                ps_check_pass = 1;
+            }
+        }
+
+        if (!ps_check_pass) {
+            return XDP_DROP;
+        }
+    }
+
     return XDP_PASS;
 }
 
diff --git a/configure.ac b/configure.ac
index 8665e71f41..8a77b3b31b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -174,6 +174,8 @@ OVS_CHECK_PRAGMA_MESSAGE
 OVN_CHECK_OVS
 OVN_CHECK_VIF_PLUG_PROVIDER
 OVN_ENABLE_VIF_PLUG
+OVN_CHECK_BPF
+OVN_CHECK_XDP
 OVS_CTAGS_IDENTIFIERS
 AC_SUBST([OVS_CFLAGS])
 AC_SUBST([OVS_LDFLAGS])
diff --git a/controller/automake.mk b/controller/automake.mk
index c2ab1bbe6a..c5354ea0ab 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
 	controller/ovsport.h \
 	controller/ovsport.c \
 	controller/vif-plug.h \
-	controller/vif-plug.c
+	controller/vif-plug.c \
+	controller/xdp.c \
+	controller/xdp.h
 
 controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
 man_MANS += controller/ovn-controller.8
diff --git a/controller/binding.c b/controller/binding.c
index a900c9a509..870d09a3f1 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -36,6 +36,7 @@
 #include "lport.h"
 #include "ovn-controller.h"
 #include "patch.h"
+#include "xdp.h"
 
 VLOG_DEFINE_THIS_MODULE(binding);
 
@@ -77,6 +78,7 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd);
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_status);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ifindex);
 
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
@@ -539,8 +541,6 @@ static struct binding_lport *local_binding_add_lport(
     struct local_binding *,
     const struct sbrec_port_binding *,
     enum en_lport_type);
-static struct binding_lport *local_binding_get_primary_lport(
-    struct local_binding *);
 static struct binding_lport *local_binding_get_first_lport(
     struct local_binding *lbinding);
 static struct binding_lport *local_binding_get_primary_or_localport_lport(
@@ -807,6 +807,27 @@ binding_dump_local_bindings(struct local_binding_data *lbinding_data,
     free(nodes);
 }
 
+/* Returns the primary binding lport if present in lbinding's
+ * binding lports list.  A binding lport is considered primary
+ * if binding lport's type is LP_VIF and the name matches
+ * with the 'lbinding'.
+ */
+struct binding_lport *
+local_binding_get_primary_lport(struct local_binding *lbinding)
+{
+    if (!lbinding) {
+        return NULL;
+    }
+
+    struct binding_lport *b_lport = local_binding_get_first_lport(lbinding);
+    if (b_lport && b_lport->type == LP_VIF &&
+            !strcmp(lbinding->name, b_lport->name)) {
+        return b_lport;
+    }
+
+    return NULL;
+}
+
 static bool
 is_lport_vif(const struct sbrec_port_binding *pb)
 {
@@ -2636,26 +2657,6 @@ local_binding_get_first_lport(struct local_binding *lbinding)
     return NULL;
 }
 
-/* Returns the primary binding lport if present in lbinding's
- * binding lports list.  A binding lport is considered primary
- * if binding lport's type is LP_VIF and the name matches
- * with the 'lbinding'.
- */
-static struct binding_lport *
-local_binding_get_primary_lport(struct local_binding *lbinding)
-{
-    if (!lbinding) {
-        return NULL;
-    }
-
-    struct binding_lport *b_lport = local_binding_get_first_lport(lbinding);
-    if (b_lport && b_lport->type == LP_VIF &&
-            !strcmp(lbinding->name, b_lport->name)) {
-        return b_lport;
-    }
-
-    return NULL;
-}
 
 static struct binding_lport *
 local_binding_get_primary_or_localport_lport(struct local_binding *lbinding)
diff --git a/controller/binding.h b/controller/binding.h
index a3858d78bc..7eeed68a99 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -214,4 +214,11 @@ struct binding_lport {
     size_t n_port_security;
 };
 
+/* Returns the primary binding lport if present in lbinding's
+ * binding lports list.  A binding lport is considered primary
+ * if binding lport's type is LP_VIF and the name matches
+ * with the 'lbinding'.
+ */
+struct binding_lport *local_binding_get_primary_lport(struct local_binding *);
+
 #endif /* controller/binding.h */
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index f0c2e23d85..a20d6a4701 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -76,6 +76,7 @@
 #include "stopwatch.h"
 #include "lib/inc-proc-eng.h"
 #include "hmapx.h"
+#include "xdp.h"
 
 VLOG_DEFINE_THIS_MODULE(main);
 
@@ -1417,7 +1418,6 @@ en_runtime_data_run(struct engine_node *node, void *data)
     }
 
     binding_run(&b_ctx_in, &b_ctx_out);
-
     engine_set_node_state(node, EN_UPDATED);
 }
 
@@ -3207,6 +3207,77 @@ flow_output_lflow_output_handler(struct engine_node *node,
     return true;
 }
 
+struct ed_type_xdp {
+    struct shash xdp_lports;
+};
+
+static void *
+en_xdp_init(struct engine_node *node OVS_UNUSED,
+            struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_xdp *xdp = xzalloc(sizeof *xdp);
+    ovn_xdp_init(&xdp->xdp_lports);
+    return xdp;
+}
+
+static void
+en_xdp_cleanup(void *data)
+{
+    struct ed_type_xdp *xdp = data;
+    ovn_xdp_destroy(&xdp->xdp_lports);
+}
+
+static void
+en_xdp_run(struct engine_node *node, void *data)
+{
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    struct ed_type_xdp *xdp_data = data;
+    ovn_xdp_run(&rt_data->lbinding_data.bindings, &rt_data->local_lports,
+                &xdp_data->xdp_lports);
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+static bool
+xdp_runtime_data_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    /* There is no tracked data. Fall back to full recompute of xdp. */
+    if (!rt_data->tracked) {
+        return false;
+    }
+
+    struct ed_type_xdp *xdp_data = data;
+
+    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
+    struct tracked_datapath *tdp;
+    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
+        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
+            /* Fall back to full recompute when a local datapath
+             * is added or deleted. */
+            return false;
+        }
+
+        struct shash_node *shash_node;
+        SHASH_FOR_EACH (shash_node, &tdp->lports) {
+            struct tracked_lport *lport = shash_node->data;
+            bool removed =
+                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
+            if (!ovn_xdp_handle_lport(
+                    lport->pb, removed, &rt_data->lbinding_data.bindings,
+                    &xdp_data->xdp_lports)) {
+                return false;
+            }
+        }
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+}
+
 struct ovn_controller_exit_args {
     bool *exiting;
     bool *restart;
@@ -3459,6 +3530,7 @@ main(int argc, char *argv[])
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets");
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
     ENGINE_NODE(northd_internal_version, "northd_internal_version");
+    ENGINE_NODE(xdp, "xdp");
 
 #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
     SB_NODES
@@ -3601,6 +3673,11 @@ main(int argc, char *argv[])
                      flow_output_lflow_output_handler);
     engine_add_input(&en_flow_output, &en_pflow_output,
                      flow_output_pflow_output_handler);
+    engine_add_input(&en_flow_output, &en_xdp,
+                     engine_noop_handler);
+
+    engine_add_input(&en_xdp, &en_runtime_data,
+                     xdp_runtime_data_handler);
 
     struct engine_arg engine_arg = {
         .sb_idl = ovnsb_idl_loop.idl,
diff --git a/controller/xdp.c b/controller/xdp.c
new file mode 100644
index 0000000000..c8201cacd9
--- /dev/null
+++ b/controller/xdp.c
@@ -0,0 +1,389 @@
+/* Copyright (c) 2022 Red Hat, 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 <config.h>
+#include <unistd.h>
+
+/* library headers */
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+#include <xdp/libxdp.h>
+
+/* OVS includes. */
+#include "lib/vswitch-idl.h"
+#include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "binding.h"
+#include "lib/ovn-dirs.h"
+#include "lib/ovn-util.h"
+#include "lib/ovn-sb-idl.h"
+#include "xdp.h"
+
+VLOG_DEFINE_THIS_MODULE(xdp);
+
+#define MAX_ERRNO       4095
+#define STRERR_BUFSIZE 1024
+
+#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO)
+
+struct lpm_mac_ip_key {
+    struct bpf_lpm_trie_key trie_key;
+    __u8 data[10]; /* 6 bytes for mac, 4 bytes for ip */
+};
+
+struct xdp_lport {
+    int ifindex;
+    bool bpf_attached;
+    bool bpf_attach_failed;
+
+    struct xdp_program *prog;
+    int vif_map_fd;
+    int port_sec_mac_map_fd;
+    int port_sec_mac_ip_map_fd;
+
+    struct lport_addresses *ps_addrs;   /* Port security addresses. */
+    unsigned int n_ps_addrs;
+};
+
+static void xdp_handle_lport(const char *lport, struct shash *local_bindings,
+                             struct shash *xdp_lports);
+static struct xdp_lport *xdp_lport_find(struct shash *xdp_lports,
+                                        const char *lport);
+static void xdp_lport_attach_prog(struct xdp_lport *);
+static void xdp_lport_update_maps(struct xdp_lport *, bool allow);
+static void xdp_lport_detach_prog(struct xdp_lport *);
+static void xdp_lport_update_sb_bpf(struct xdp_lport *,
+                                    const struct sbrec_port_binding *);
+static struct xdp_lport *xdp_lport_create(
+    struct shash *xdp_lports, const struct binding_lport *);
+static void xdp_lport_destroy(struct xdp_lport *);
+
+
+void
+ovn_xdp_init(struct shash *xdp_lports)
+{
+    shash_init(xdp_lports);
+}
+
+void
+ovn_xdp_run(struct shash *local_bindings, struct sset *local_lports,
+            struct shash *xdp_lports)
+{
+    struct shash_node *node, *next;
+    SHASH_FOR_EACH_SAFE (node, next, xdp_lports) {
+        if (!sset_contains(local_lports, node->name)) {
+            struct xdp_lport *xdp_lport = node->data;
+            shash_delete(xdp_lports, node);
+            xdp_lport_destroy(xdp_lport);
+        }
+    }
+
+    const char *lport;
+    SSET_FOR_EACH (lport, local_lports) {
+        xdp_handle_lport(lport, local_bindings, xdp_lports);
+    }
+}
+
+bool
+ovn_xdp_handle_lport(const struct sbrec_port_binding *pb, bool removed,
+                     struct shash *local_bindings, struct shash *xdp_lports)
+{
+    if (removed) {
+        struct xdp_lport *xdp_lport =
+            shash_find_and_delete(xdp_lports, pb->logical_port);
+        if (xdp_lport) {
+            xdp_lport_destroy(xdp_lport);
+        }
+    } else {
+        xdp_handle_lport(pb->logical_port, local_bindings, xdp_lports);
+    }
+
+    return true;
+}
+
+void
+ovn_xdp_destroy(struct shash *xdp_lports)
+{
+    struct shash_node *node, *next;
+    SHASH_FOR_EACH_SAFE (node, next, xdp_lports) {
+        struct xdp_lport *xdp_lport = node->data;
+        shash_delete(xdp_lports, node);
+        xdp_lport_destroy(xdp_lport);
+    }
+}
+
+/* static functions. */
+
+static void
+xdp_handle_lport(const char *lport, struct shash *local_bindings,
+                 struct shash *xdp_lports)
+{
+    struct local_binding *lbinding = local_binding_find(local_bindings, lport);
+    if (!lbinding) {
+        return;
+    }
+
+    struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
+    if (!b_lport) {
+        return;
+    }
+
+    int ifindex = lbinding->iface->n_ifindex ? lbinding->iface->ifindex[0]
+                                                : -1;
+    struct xdp_lport *xdp_lport = xdp_lport_find(xdp_lports, lport);
+    if (xdp_lport && ifindex < 1) {
+        /* The interface has been deleted. */
+        shash_find_and_delete(xdp_lports, lport);
+        xdp_lport_destroy(xdp_lport);
+        return;
+    }
+
+    if (ifindex < 1) {
+        return;
+    }
+
+    if (!xdp_lport) {
+        xdp_lport = xdp_lport_create(xdp_lports, b_lport);
+        if (!xdp_lport) {
+            return;
+        }
+    } else {
+        if (xdp_lport->bpf_attached) {
+            xdp_lport_update_maps(xdp_lport, false);
+        }
+        xdp_lport_update_sb_bpf(xdp_lport, b_lport->pb);
+    }
+
+    xdp_lport_attach_prog(xdp_lport);
+    if (xdp_lport->bpf_attached) {
+        xdp_lport_update_maps(xdp_lport, true);
+    }
+}
+
+static void
+xdp_lport_attach_prog(struct xdp_lport *xdp_lport)
+{
+    if (xdp_lport->bpf_attached || xdp_lport->bpf_attach_failed) {
+        return;
+    }
+
+    /* Detach any xdp program if it is already attached. */
+    xdp_lport_detach_prog(xdp_lport);
+
+    char *ovn_xdp_file = xasprintf("%s/ovn_xdp.o", ovn_pkgdatadir());
+    struct xdp_program *prog =
+        xdp_program__open_file(ovn_xdp_file, "xdp", NULL);
+    free(ovn_xdp_file);
+
+    int err = libxdp_get_error(prog);
+    char errmsg[STRERR_BUFSIZE];
+    if (err) {
+        libxdp_strerror(err, errmsg, sizeof(errmsg));
+        VLOG_ERR("ovn_xdp_attach: failed to open xdp program: %s", errmsg);
+        return;
+    }
+
+    err = xdp_program__attach(prog, xdp_lport->ifindex, XDP_MODE_NATIVE, 0);
+
+    if (err) {
+        libxdp_strerror(err, errmsg, sizeof(errmsg));
+        VLOG_ERR("ovn_xdp_attach: failed to attach xdp program: %s", errmsg);
+        xdp_lport->bpf_attach_failed = true;
+        return;
+    }
+
+    struct bpf_object *obj = xdp_program__bpf_obj(prog);
+    struct bpf_map *map = bpf_object__find_map_by_name(obj, "ovn_vif_map");
+
+    xdp_lport->vif_map_fd = map ? bpf_map__fd(map) : -1;
+
+    map = bpf_object__find_map_by_name(obj, "port_sec_mac_table");
+    xdp_lport->port_sec_mac_map_fd = map ? bpf_map__fd(map) : -1;
+
+    map = bpf_object__find_map_by_name(obj, "port_sec_mac_ip_table");
+    xdp_lport->port_sec_mac_ip_map_fd = map ? bpf_map__fd(map) : -1;
+
+    xdp_lport->bpf_attached = true;
+    xdp_lport->bpf_attach_failed = false;
+    xdp_lport->prog = prog;
+}
+
+
+static void
+xdp_lport_update_maps(struct xdp_lport *xdp_lport, bool allow)
+{
+    if (!xdp_lport->bpf_attached || xdp_lport->vif_map_fd < 1 ||
+        xdp_lport->port_sec_mac_map_fd < 1 ||
+        xdp_lport->port_sec_mac_ip_map_fd < 1) {
+        return;
+    }
+
+    uint32_t ovn_xdp_checks = 0;
+    for (size_t i = 0 ; i < xdp_lport->n_ps_addrs; i++) {
+        uint8_t ps_only_l2 = 1;
+        struct lport_addresses *ps = &xdp_lport->ps_addrs[i];
+
+        if (ps->n_ipv4_addrs) {
+            struct lpm_mac_ip_key key;
+            key.data[0] = ps->ea.ea[0];
+            key.data[1] = ps->ea.ea[1];
+            key.data[2] = ps->ea.ea[2];
+            key.data[3] = ps->ea.ea[3];
+            key.data[4] = ps->ea.ea[4];
+            key.data[5] = ps->ea.ea[5];
+
+            for (size_t j = 0; j < ps->n_ipv4_addrs; j++) {
+                key.trie_key.prefixlen = ps->ipv4_addrs[j].plen + 48;
+
+                uint32_t addr = (OVS_FORCE uint32_t)ps->ipv4_addrs[j].addr;
+                key.data[6] = addr & 0xff;
+                key.data[7] = (addr >> 8) & 0xff;
+                key.data[8] = (addr >> 16) & 0xff;
+                key.data[9] = (addr >> 24) & 0xff;
+                
+                uint8_t v = allow ? 1 : 0;
+                if (bpf_map_update_elem(xdp_lport->port_sec_mac_ip_map_fd,
+                                        &key, &v, 0) < 0) {
+                    VLOG_ERR("ovn_xdp_attach: failed to update port_sec_mac_ip_table");
+                    return;
+                }
+                ovn_xdp_checks |= OVN_CHECK_PORT_SEC_MAC_IP;
+            }
+            ps_only_l2 = 0;
+        }
+
+        ovn_xdp_checks |= OVN_CHECK_PORT_SEC_MAC;
+        uint64_t mac = eth_addr_to_uint64(ps->ea);
+
+        if (bpf_map_update_elem(xdp_lport->port_sec_mac_map_fd, &mac,
+                                &ps_only_l2, 0) < 0) {
+            VLOG_ERR("ovn_xdp_attach: failed to update port_sec_mac_table");
+            return;
+        }
+    }
+
+    int key = 0;
+    if (bpf_map_update_elem(xdp_lport->vif_map_fd, &key,
+                            &ovn_xdp_checks, 0) < 0) {
+        VLOG_ERR("ovn_xdp_attach: failed to update ovn_check_map");
+    }
+}
+
+static void
+xdp_lport_detach_prog(struct xdp_lport *xdp_lport)
+{
+    if (xdp_lport->bpf_attached && xdp_lport->prog) {
+        if (xdp_lport->vif_map_fd > 0) {
+            close(xdp_lport->vif_map_fd);
+            xdp_lport->vif_map_fd = -1;
+        }
+
+        if (xdp_lport->port_sec_mac_map_fd > 0) {
+            close(xdp_lport->port_sec_mac_map_fd);
+            xdp_lport->port_sec_mac_map_fd = -1;
+        }
+
+        if (xdp_lport->port_sec_mac_ip_map_fd > 0) {
+            close(xdp_lport->port_sec_mac_ip_map_fd);
+            xdp_lport->port_sec_mac_ip_map_fd = -1;
+        }
+
+        xdp_program__detach(xdp_lport->prog, xdp_lport->ifindex, XDP_MODE_NATIVE, 0);
+        xdp_program__close(xdp_lport->prog);
+        xdp_lport->bpf_attached = false;
+        xdp_lport->bpf_attach_failed = false;
+        xdp_lport->prog = NULL;
+    } else {
+        struct xdp_multiprog *mp = NULL;
+        mp = xdp_multiprog__get_from_ifindex(xdp_lport->ifindex);
+
+        if (!mp || IS_ERR_VALUE((unsigned long) mp)) {
+            return;
+        }
+
+        if (xdp_multiprog__detach(mp)) {
+            VLOG_ERR("ovn_xdp_detach: failed to detach xdp program");
+            return;
+        }
+
+        xdp_multiprog__close(mp);
+    }
+}
+
+static struct xdp_lport *
+xdp_lport_find(struct shash *xdp_lports, const char *lport)
+{
+    return shash_find_data(xdp_lports, lport);
+}
+
+static void
+xdp_lport_update_sb_bpf(struct xdp_lport *xdp_lp,
+                        const struct sbrec_port_binding *sb_pb)
+{
+    if (xdp_lp->n_ps_addrs) {
+        for (size_t i = 0; i < xdp_lp->n_ps_addrs; i++) {
+            destroy_lport_addresses(&xdp_lp->ps_addrs[i]);
+        }
+        free(xdp_lp->ps_addrs);
+        xdp_lp->ps_addrs = NULL;
+        xdp_lp->n_ps_addrs = 0;
+    }
+
+    xdp_lp->ps_addrs =
+        xmalloc(sizeof *xdp_lp->ps_addrs * sb_pb->n_port_security);
+    for (size_t i = 0; i < sb_pb->n_port_security; i++) {
+        if (!extract_lsp_addresses(
+            sb_pb->port_security[i],
+            &xdp_lp->ps_addrs[xdp_lp->n_ps_addrs])) {
+            static struct vlog_rate_limit rl
+                = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_INFO_RL(&rl, "invalid syntax '%s' in lport port security."
+                            " No MAC address found", sb_pb->port_security[i]);
+            continue;
+        }
+        xdp_lp->n_ps_addrs++;
+    }
+}
+
+static struct xdp_lport *
+xdp_lport_create(struct shash *xdp_lports, const struct binding_lport *b_lport)
+{
+    struct xdp_lport *xdp_lport = xzalloc(sizeof(*xdp_lport));
+    ovs_assert(b_lport->lbinding->iface->n_ifindex);
+    xdp_lport->ifindex = b_lport->lbinding->iface->ifindex[0];
+    shash_add(xdp_lports, b_lport->lbinding->name, xdp_lport);
+
+    if (b_lport->pb && b_lport->pb->n_port_security) {
+        xdp_lport_update_sb_bpf(xdp_lport, b_lport->pb);
+    }
+
+    xdp_lport->vif_map_fd = -1;
+    xdp_lport->port_sec_mac_map_fd = -1;
+    xdp_lport->port_sec_mac_ip_map_fd = -1;
+
+    return xdp_lport;
+}
+
+static void
+xdp_lport_destroy(struct xdp_lport *xdp_lport)
+{
+    xdp_lport_detach_prog(xdp_lport);
+    for (size_t i = 0; i < xdp_lport->n_ps_addrs; i++) {
+        destroy_lport_addresses(&xdp_lport->ps_addrs[i]);
+    }
+    free(xdp_lport->ps_addrs);
+    free(xdp_lport);
+}
diff --git a/controller/xdp.h b/controller/xdp.h
new file mode 100644
index 0000000000..2034b70e36
--- /dev/null
+++ b/controller/xdp.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 2022 Red Hat, 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 OVN_XDP_H
+#define OVN_XDP_H 1
+
+#define ETH_ALEN 6
+
+struct vif_addr {
+    uint8_t flags;
+    uint8_t eth_addr[ETH_ALEN];
+    ovs_be32 ip_addr;
+};
+
+#define OVN_CHECK_PORT_SEC_MAC      0x00000001
+#define OVN_CHECK_PORT_SEC_MAC_IP   0x00000002
+
+struct sbrec_port_binding;
+struct local_binding;
+struct shash;
+
+void ovn_xdp_init(struct shash *);
+void ovn_xdp_destroy(struct shash *);
+void ovn_xdp_run(struct shash *local_bindings, struct sset *local_lports,
+                 struct shash *xdp_lports);
+bool ovn_xdp_handle_lport(const struct sbrec_port_binding *pb, bool removed,
+                          struct shash *local_bindings,
+                          struct shash *xdp_lports);
+#endif
diff --git a/m4/ovn.m4 b/m4/ovn.m4
index 2909914fb8..2c42fda97b 100644
--- a/m4/ovn.m4
+++ b/m4/ovn.m4
@@ -592,3 +592,23 @@ AC_DEFUN([OVS_CHECK_DDLOG_FAST_BUILD],
    if $ddlog_fast_build; then
       DDLOG_EXTRA_RUSTFLAGS="-C opt-level=z"
    fi])
+
+dnl Checks for libbpf.
+AC_DEFUN([OVN_CHECK_BPF],
+  [AC_CHECK_LIB(bpf, bpf_load_program, [HAVE_BPF=yes], [HAVE_BPF=no])
+   if test "$HAVE_BPF" = yes; then
+     AC_DEFINE([HAVE_BPF], [1], [Define to 1 if bpf is detected.])
+     LIBS="$LIBS -lbpf"
+   fi
+   AM_CONDITIONAL([HAVE_BPF], [test "$HAVE_BPF" = yes])
+   AC_SUBST([HAVE_BPF])])
+
+dnl Checks for libxdp.
+AC_DEFUN([OVN_CHECK_XDP],
+  [AC_CHECK_LIB(xdp, xdp_program__attach, [HAVE_XDP=yes], [HAVE_XDP=no])
+   if test "$HAVE_XDP" = yes; then
+     AC_DEFINE([HAVE_XDP], [1], [Define to 1 if xdp is detected.])
+     LIBS="$LIBS -lxdp"
+   fi
+   AM_CONDITIONAL([HAVE_XDP], [test "$HAVE_XDP" = yes])
+   AC_SUBST([HAVE_XDP])])
diff --git a/tests/automake.mk b/tests/automake.mk
index 9733e8fa0f..6858c5a33c 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -265,6 +265,7 @@ tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \
 	controller/ovsport.$(OBJEXT) \
 	controller/patch.$(OBJEXT) \
 	controller/vif-plug.$(OBJEXT) \
+	controller/xdp.$(OBJEXT) \
 	northd/ipam.$(OBJEXT)
 
 # Python tests.
