diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h
index 78f0ecd1..9d22961b 100644
--- a/include/uapi/linux/lwtunnel.h
+++ b/include/uapi/linux/lwtunnel.h
@@ -15,6 +15,7 @@ enum lwtunnel_encap_types {
 	LWTUNNEL_ENCAP_SEG6_LOCAL,
 	LWTUNNEL_ENCAP_RPL,
 	LWTUNNEL_ENCAP_IOAM6,
+	LWTUNNEL_ENCAP_XFRM,
 	__LWTUNNEL_ENCAP_MAX,
 };
 
@@ -111,4 +112,13 @@ enum {
 
 #define LWT_BPF_MAX_HEADROOM 256
 
+enum {
+	LWT_XFRM_UNSPEC,
+	LWT_XFRM_IF_ID,
+	LWT_XFRM_LINK,
+	__LWT_XFRM_MAX,
+};
+
+#define LWT_XFRM_MAX (__LWT_XFRM_MAX - 1)
+
 #endif /* _LWTUNNEL_H_ */
diff --git a/ip/iproute.c b/ip/iproute.c
index 8b2d1fbe..b4b9d1b2 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -102,8 +102,8 @@ static void usage(void)
 		"TIME := NUMBER[s|ms]\n"
 		"BOOL := [1|0]\n"
 		"FEATURES := ecn\n"
-		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 ]\n"
-		"ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR ]\n"
+		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 | xfrm ]\n"
+		"ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR | XFRMINFO ]\n"
 		"SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]\n"
 		"SEGMODE := [ encap | encap.red | inline | l2encap | l2encap.red ]\n"
 		"SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]\n"
@@ -116,6 +116,7 @@ static void usage(void)
 		"FLAVORS := { FLAVOR[,FLAVOR] }\n"
 		"FLAVOR := { psp | usp | usd | next-csid }\n"
 		"IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE\n"
+		"XFRMINFO := if_id IF_ID [ link_dev LINK ]\n"
 		"ROUTE_GET_FLAGS := [ fibmatch ]\n");
 	exit(-1);
 }
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 86128c9b..bf4468b6 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -58,6 +58,8 @@ static const char *format_encap_type(int type)
 		return "rpl";
 	case LWTUNNEL_ENCAP_IOAM6:
 		return "ioam6";
+	case LWTUNNEL_ENCAP_XFRM:
+		return "xfrm";
 	default:
 		return "unknown";
 	}
@@ -96,6 +98,8 @@ static int read_encap_type(const char *name)
 		return LWTUNNEL_ENCAP_RPL;
 	else if (strcmp(name, "ioam6") == 0)
 		return LWTUNNEL_ENCAP_IOAM6;
+	else if (strcmp(name, "xfrm") == 0)
+		return LWTUNNEL_ENCAP_XFRM;
 	else if (strcmp(name, "help") == 0)
 		encap_type_usage();
 
@@ -814,6 +818,24 @@ static void print_encap_bpf(FILE *fp, struct rtattr *encap)
 			   " %u ", rta_getattr_u32(tb[LWT_BPF_XMIT_HEADROOM]));
 }
 
+static void print_encap_xfrm(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[LWT_XFRM_MAX+1];
+
+	parse_rtattr_nested(tb, LWT_XFRM_MAX, encap);
+
+	if (tb[LWT_XFRM_IF_ID])
+		print_uint(PRINT_ANY, "if_id", "if_id %lu ",
+			   rta_getattr_u32(tb[LWT_XFRM_IF_ID]));
+
+	if (tb[LWT_XFRM_LINK]) {
+		int link = rta_getattr_u32(tb[LWT_XFRM_LINK]);
+
+		print_string(PRINT_ANY, "link_dev", "link_dev %s ",
+			     ll_index_to_name(link));
+	}
+}
+
 void lwt_print_encap(FILE *fp, struct rtattr *encap_type,
 			  struct rtattr *encap)
 {
@@ -854,6 +876,9 @@ void lwt_print_encap(FILE *fp, struct rtattr *encap_type,
 	case LWTUNNEL_ENCAP_IOAM6:
 		print_encap_ioam6(fp, encap);
 		break;
+	case LWTUNNEL_ENCAP_XFRM:
+		print_encap_xfrm(fp, encap);
+		break;
 	}
 }
 
@@ -2129,6 +2154,61 @@ static int parse_encap_bpf(struct rtattr *rta, size_t len, int *argcp,
 	return 0;
 }
 
+static void lwt_xfrm_usage(void)
+{
+	fprintf(stderr, "Usage: ip route ... encap xfrm if_id IF_ID [ link_dev LINK ]\n");
+	exit(-1);
+}
+
+static int parse_encap_xfrm(struct rtattr *rta, size_t len,
+			    int *argcp, char ***argvp)
+{
+	int if_id_ok = 0, link_ok = 0;
+	char **argv = *argvp;
+	int argc = *argcp;
+	int ret = 0;
+
+	while (argc > 0) {
+		if (!strcmp(*argv, "if_id")) {
+			__u32 if_id;
+
+			NEXT_ARG();
+			if (if_id_ok++)
+				duparg2("if_id", *argv);
+			if (get_u32(&if_id, *argv, 0) || if_id == 0)
+				invarg("\"if_id\" value is invalid\n", *argv);
+			ret = rta_addattr32(rta, len, LWT_XFRM_IF_ID, if_id);
+		} else if (!strcmp(*argv, "link_dev")) {
+			int link;
+
+			NEXT_ARG();
+			if (link_ok++)
+				duparg2("link_dev", *argv);
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+			ret = rta_addattr32(rta, len, LWT_XFRM_LINK, link);
+		} else if (!strcmp(*argv, "help")) {
+			lwt_xfrm_usage();
+		}
+		if (ret)
+			break;
+		argc--; argv++;
+	}
+
+	if (!if_id_ok)
+		lwt_xfrm_usage();
+
+	/* argv is currently the first unparsed argument,
+	 * but the lwt_parse_encap() caller will move to the next,
+	 * so step back
+	 */
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	return ret;
+}
+
 int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp,
 		    int encap_attr, int encap_type_attr)
 {
@@ -2180,6 +2260,9 @@ int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp,
 	case LWTUNNEL_ENCAP_IOAM6:
 		ret = parse_encap_ioam6(rta, len, &argc, &argv);
 		break;
+	case LWTUNNEL_ENCAP_XFRM:
+		ret = parse_encap_xfrm(rta, len, &argc, &argv);
+		break;
 	default:
 		fprintf(stderr, "Error: unsupported encap type\n");
 		break;
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index bd38b7d8..194dc780 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -738,6 +738,9 @@ is a string specifying the supported encapsulation type. Namely:
 .sp
 .BI ioam6
 - encapsulation type IPv6 IOAM
+.sp
+.BI xfrm
+- encapsulation type XFRM
 
 .in -8
 .I ENCAPHDR
@@ -1024,6 +1027,14 @@ mode.
 .B size
 .I IOAM6_TRACE_SIZE
 - Size, in octets, of the pre-allocated trace data block.
+.in -2
+
+.B xfrm
+.in +2
+.B if_id
+.I IF_ID
+.B  " [ link_dev
+.IR LINK_DEV " ] "
 .in -4
 
 .in -8
