diff --git a/include/mnl_utils.h b/include/mnl_utils.h
index 2193934849e1..450f304bb302 100644
--- a/include/mnl_utils.h
+++ b/include/mnl_utils.h
@@ -11,6 +11,31 @@ struct mnlu_gen_socket {
 	uint8_t version;
 };
 
+/**
+ * struct mnlu_attr_policy
+ *
+ * Structure representing the policy for a single attribute. Pass an array of
+ * at least nlg->maxattr + 1 to mnlu_gen_get_op_policy to extract the policy
+ * for a netlink op.
+ *
+ * @attr_type: the attribute type, extracted from the NLA data
+ *
+ * @valid: if set, the attribute is accepted by the policy. If not set, the
+ *         attribute is not accepted by the policy.
+ *
+ * @tb: Pointers to the NLA data. Only remains valid until a new netlink
+ *      command is sent, as it points directly to the netlink message buffer.
+ */
+struct mnlu_attr_policy {
+	/* Pointers to the nla attributes describing the policy for this
+	 * attribute. Note that these only remain valid until the next netlink
+	 * message is sent.
+	 */
+	const struct nlattr *tb[NL_POLICY_TYPE_ATTR_MAX + 1];
+	uint32_t attr_type;
+	uint8_t valid : 1;
+};
+
 int mnlu_gen_socket_open(struct mnlu_gen_socket *nlg, const char *family_name,
 			 uint8_t version);
 void mnlu_gen_socket_close(struct mnlu_gen_socket *nlg);
@@ -30,5 +55,7 @@ int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, siz
 			 mnl_cb_t cb, void *data);
 int mnlu_gen_socket_recv_run(struct mnlu_gen_socket *nlg, mnl_cb_t cb,
 			     void *data);
+int mnlu_gen_get_op_policy(struct mnlu_gen_socket *nlg, uint32_t op, bool dump,
+			   struct mnlu_attr_policy *policy);
 
 #endif /* __MNL_UTILS_H__ */
diff --git a/lib/mnl_utils.c b/lib/mnl_utils.c
index 79bac5cfd4de..f9918afd517d 100644
--- a/lib/mnl_utils.c
+++ b/lib/mnl_utils.c
@@ -252,3 +252,243 @@ int mnlu_gen_socket_recv_run(struct mnlu_gen_socket *nlg, mnl_cb_t cb,
 				    MNL_SOCKET_BUFFER_SIZE,
 				    cb, data);
 }
+
+struct policy_info {
+	struct mnlu_gen_socket *nlg;
+	struct mnlu_attr_policy *policy;
+	uint32_t policy_idx;
+	uint32_t op;
+	bool dump;
+};
+
+static int
+parse_policy_idx_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_POLICY_DUMP_MAX) < 0)
+		return MNL_CB_OK;
+
+	switch (type) {
+	case CTRL_ATTR_POLICY_DO:
+	case CTRL_ATTR_POLICY_DUMP:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+		break;
+	default:
+		break;
+	}
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int
+parse_policy_idx(const struct nlattr *attr, struct policy_info *info)
+{
+	struct nlattr *tb[CTRL_ATTR_POLICY_DUMP_MAX + 1] = {};
+	struct nlattr *idx_attr;
+	uint32_t op;
+
+	/* The type of this nested attribute is the op */
+	op = mnl_attr_get_type(attr);
+
+	/* Skip ops we're not interested in */
+	if (op != info->op)
+		return MNL_CB_OK;
+
+	mnl_attr_parse_nested(attr, parse_policy_idx_cb, tb);
+	if (info->dump)
+		idx_attr = tb[CTRL_ATTR_POLICY_DUMP];
+	else
+		idx_attr = tb[CTRL_ATTR_POLICY_DO];
+	if (!idx_attr)
+		return MNL_CB_ERROR;
+
+	info->policy_idx = mnl_attr_get_u32(idx_attr);
+
+	return MNL_CB_OK;
+}
+
+static int
+parse_policy_attr_data_cb(const struct nlattr *attr, void *data)
+{
+	struct mnlu_attr_policy *policy = data;
+	int type = mnl_attr_get_type(attr);
+
+	/* Unknown policy type attributes are ignored */
+	if (mnl_attr_type_valid(attr, NL_POLICY_TYPE_ATTR_MAX) < 0)
+		return MNL_CB_OK;
+
+	switch (type) {
+	case NL_POLICY_TYPE_ATTR_TYPE:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+
+		policy->attr_type = mnl_attr_get_u32(attr);
+		policy->valid = 1;
+		break;
+	case NL_POLICY_TYPE_ATTR_MIN_VALUE_S:
+		/* libmnl doesn't yet have s64 */
+		if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_MAX_VALUE_S:
+		/* libmnl doesn't yet have s64 */
+		if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_MIN_VALUE_U:
+		if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_MAX_VALUE_U:
+		if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_MIN_LENGTH:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_MAX_LENGTH:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_POLICY_IDX:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_BITFIELD32_MASK:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case NL_POLICY_TYPE_ATTR_MASK:
+		if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0)
+			return MNL_CB_ERROR;
+		break;
+	default:
+		break;
+	}
+
+	policy->tb[type] = attr;
+
+	return MNL_CB_OK;
+}
+
+static int
+parse_policy_data(const struct nlattr *attr, struct policy_info *info)
+{
+	const struct nlattr *nested_attr;
+	int policy_idx, attr_id;
+
+	/* The type of this nested attribute is the policy index */
+	policy_idx = mnl_attr_get_type(attr);
+
+	/* Skip policies we're not interested in */
+	if (policy_idx != info->policy_idx)
+		return MNL_CB_OK;
+
+	if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
+		return MNL_CB_ERROR;
+	nested_attr = mnl_attr_get_payload(attr);
+	if (!mnl_attr_ok(nested_attr, mnl_attr_get_payload_len(attr)))
+		return MNL_CB_ERROR;
+	if (mnl_attr_validate(nested_attr, MNL_TYPE_NESTED) < 0)
+		return MNL_CB_ERROR;
+	if (mnl_attr_type_valid(attr, info->nlg->maxattr) < 0)
+		return MNL_CB_ERROR;
+
+	attr_id = mnl_attr_get_type(nested_attr);
+
+	return mnl_attr_parse_nested(nested_attr, parse_policy_attr_data_cb,
+				     &info->policy[attr_id]);
+}
+
+static int get_policy_attrs_cb(const struct nlattr *attr, void *data)
+{
+	int type = mnl_attr_get_type(attr);
+	struct policy_info *info = data;
+	struct nlattr *nested_attr;
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+		return MNL_CB_ERROR;
+
+	switch (type) {
+	case CTRL_ATTR_OP_POLICY:
+	case CTRL_ATTR_POLICY:
+		if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
+			return MNL_CB_ERROR;
+
+		nested_attr = mnl_attr_get_payload(attr);
+		if (!mnl_attr_ok(nested_attr, mnl_attr_get_payload_len(attr)))
+			return MNL_CB_ERROR;
+
+		if (mnl_attr_validate(nested_attr, MNL_TYPE_NESTED) < 0)
+			return MNL_CB_ERROR;
+
+		if (type == CTRL_ATTR_OP_POLICY)
+			return parse_policy_idx(nested_attr, info);
+
+		return parse_policy_data(nested_attr, info);
+	default:
+		break;
+	}
+
+	return MNL_CB_OK;
+}
+
+static int get_policy_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	return mnl_attr_parse(nlh, sizeof(*genl), get_policy_attrs_cb, data);
+}
+
+/**
+ * mnlu_get_op_policy - Get policy for a specific op.
+ * @nlg: the socket to get policy on
+ * @op: the op to get the policy for
+ * @dump: if true, get the policy NLM_F_DUMP
+ * @policy: an array of size nlg->maxattr used to return policy data
+ *
+ * Uses CTRL_CMD_GETPOLICY to extract policy information from the kernel for
+ * the specified op. The data is extracted into the provided policy buffer
+ * which is expected to be of size nlg->maxattr.
+ *
+ * This function must be called after mnlu_gen_socket_open.
+ */
+int mnlu_gen_get_op_policy(struct mnlu_gen_socket *nlg, uint32_t op, bool dump,
+			   struct mnlu_attr_policy *policy)
+{
+	struct policy_info info = {};
+	struct genlmsghdr hdr = {};
+	struct nlmsghdr *nlh;
+	int err;
+
+	info.nlg = nlg;
+	info.policy = policy;
+	info.dump = dump;
+	info.op = op;
+
+	hdr.cmd = CTRL_CMD_GETPOLICY;
+	hdr.version = 0x1;
+
+	nlh = mnlu_msg_prepare(nlg->buf, GENL_ID_CTRL,
+			       NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP,
+			       &hdr, sizeof(hdr));
+
+	mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, nlg->family);
+	mnl_attr_put_u32(nlh, CTRL_ATTR_OP, op);
+
+	err = mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
+	if (err < 0)
+		return err;
+
+	return mnlu_socket_recv_run(nlg->nl, nlh->nlmsg_seq, nlg->buf,
+				    MNL_SOCKET_BUFFER_SIZE,
+				    get_policy_cb, &info);
+}
