Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# XFRM configuration
#
config XFRM
bool
depends on INET
select GRO_CELLS
select SKB_EXTENSIONS
config XFRM_OFFLOAD
bool
config XFRM_ALGO
tristate
select XFRM
select CRYPTO
select CRYPTO_AEAD
select CRYPTO_HASH
select CRYPTO_SKCIPHER
if INET
config XFRM_USER
tristate "Transformation user configuration interface"
select XFRM_ALGO
help
Support for Transformation(XFRM) user configuration interface
like IPsec used by native Linux tools.
If unsure, say Y.
config XFRM_USER_COMPAT
tristate "Compatible ABI support"
depends on XFRM_USER && COMPAT_FOR_U64_ALIGNMENT && \
HAVE_EFFICIENT_UNALIGNED_ACCESS
select WANT_COMPAT_NETLINK_MESSAGES
help
Transformation(XFRM) user configuration interface like IPsec
used by compatible Linux applications.
If unsure, say N.
config XFRM_INTERFACE
tristate "Transformation virtual interface"
depends on XFRM && IPV6
help
This provides a virtual interface to route IPsec traffic.
If unsure, say N.
config XFRM_SUB_POLICY
bool "Transformation sub policy support"
depends on XFRM
help
Support sub policy for developers. By using sub policy with main
one, two policies can be applied to the same packet at once.
Policy which lives shorter time in kernel should be a sub.
If unsure, say N.
config XFRM_MIGRATE
bool "Transformation migrate database"
depends on XFRM
help
A feature to update locator(s) of a given IPsec security
association dynamically. This feature is required, for
instance, in a Mobile IPv6 environment with IPsec configuration
where mobile nodes change their attachment point to the Internet.
If unsure, say N.
config XFRM_STATISTICS
bool "Transformation statistics"
depends on XFRM && PROC_FS
help
This statistics is not a SNMP/MIB specification but shows
statistics about transformation error (or almost error) factor
at packet processing for developer.
If unsure, say N.
# This option selects XFRM_ALGO along with the AH authentication algorithms that
# RFC 8221 lists as MUST be implemented.
config XFRM_AH
tristate
select XFRM_ALGO
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_SHA256
# This option selects XFRM_ALGO along with the ESP encryption and authentication
# algorithms that RFC 8221 lists as MUST be implemented.
config XFRM_ESP
tristate
select XFRM_ALGO
select CRYPTO
select CRYPTO_AES
select CRYPTO_AUTHENC
select CRYPTO_CBC
select CRYPTO_ECHAINIV
select CRYPTO_GCM
select CRYPTO_HMAC
select CRYPTO_SEQIV
select CRYPTO_SHA256
config XFRM_IPCOMP
tristate
select XFRM_ALGO
select CRYPTO
select CRYPTO_DEFLATE
config NET_KEY
tristate "PF_KEY sockets (deprecated)"
select XFRM_ALGO
help
PF_KEYv2 socket family, compatible to KAME ones.
The PF_KEYv2 socket interface is deprecated and
scheduled for removal. All maintained IKE daemons
no longer need PF_KEY sockets. Please use the netlink
interface (XFRM_USER) to configure IPsec.
If unsure, say N.
config NET_KEY_MIGRATE
bool "PF_KEY MIGRATE"
depends on NET_KEY
select XFRM_MIGRATE
help
Add a PF_KEY MIGRATE message to PF_KEYv2 socket family.
The PF_KEY MIGRATE message is used to dynamically update
locator(s) of a given IPsec security association.
This feature is required, for instance, in a Mobile IPv6
environment with IPsec configuration where mobile nodes
change their attachment point to the Internet. Detail
information can be found in the internet-draft
<draft-sugimoto-mip6-pfkey-migrate>.
If unsure, say N.
config XFRM_IPTFS
tristate "IPsec IP-TFS/AGGFRAG (RFC 9347) encapsulation support"
depends on XFRM
help
Information on the IP-TFS/AGGFRAG encapsulation can be found
in RFC 9347. This feature supports demand driven (i.e.,
non-constant send rate) IP-TFS to take advantage of the
AGGFRAG ESP payload encapsulation. This payload type
supports aggregation and fragmentation of the inner IP
packet stream which in turn yields higher small-packet
bandwidth as well as reducing MTU/PMTU issues. Congestion
control is unimplementated as the send rate is demand driven
rather than constant.
If unsure, say N.
config XFRM_ESPINTCP
bool
endif # INET
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the XFRM subsystem.
#
xfrm_interface-$(CONFIG_XFRM_INTERFACE) += xfrm_interface_core.o
ifeq ($(CONFIG_XFRM_INTERFACE),m)
xfrm_interface-$(CONFIG_DEBUG_INFO_BTF_MODULES) += xfrm_interface_bpf.o
else ifeq ($(CONFIG_XFRM_INTERFACE),y)
xfrm_interface-$(CONFIG_DEBUG_INFO_BTF) += xfrm_interface_bpf.o
endif
obj-$(CONFIG_XFRM) := xfrm_policy.o xfrm_state.o xfrm_hash.o \
xfrm_input.o xfrm_output.o \
xfrm_sysctl.o xfrm_replay.o xfrm_device.o \
xfrm_nat_keepalive.o
obj-$(CONFIG_XFRM_STATISTICS) += xfrm_proc.o
obj-$(CONFIG_XFRM_ALGO) += xfrm_algo.o
obj-$(CONFIG_XFRM_USER) += xfrm_user.o
obj-$(CONFIG_XFRM_USER_COMPAT) += xfrm_compat.o
obj-$(CONFIG_XFRM_IPCOMP) += xfrm_ipcomp.o
obj-$(CONFIG_XFRM_INTERFACE) += xfrm_interface.o
obj-$(CONFIG_XFRM_IPTFS) += xfrm_iptfs.o
obj-$(CONFIG_XFRM_ESPINTCP) += espintcp.o
obj-$(CONFIG_DEBUG_INFO_BTF) += xfrm_state_bpf.o
+577
View File
@@ -0,0 +1,577 @@
// SPDX-License-Identifier: GPL-2.0
#include <net/tcp.h>
#include <net/strparser.h>
#include <net/xfrm.h>
#include <net/esp.h>
#include <net/espintcp.h>
#include <linux/skmsg.h>
#include <net/inet_common.h>
#include <trace/events/sock.h>
#include <net/hotdata.h>
static void handle_nonesp(struct espintcp_ctx *ctx, struct sk_buff *skb,
struct sock *sk)
{
if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf ||
!sk_rmem_schedule(sk, skb, skb->truesize)) {
XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR);
kfree_skb(skb);
return;
}
skb_set_owner_r(skb, sk);
memset(skb->cb, 0, sizeof(skb->cb));
skb_queue_tail(&ctx->ike_queue, skb);
ctx->saved_data_ready(sk);
}
static void handle_esp(struct sk_buff *skb, struct sock *sk)
{
struct tcp_skb_cb *tcp_cb = (struct tcp_skb_cb *)skb->cb;
skb_reset_transport_header(skb);
/* restore IP CB, we need at least IP6CB->nhoff */
memmove(skb->cb, &tcp_cb->header, sizeof(tcp_cb->header));
rcu_read_lock();
skb->dev = dev_get_by_index_rcu(sock_net(sk), skb->skb_iif);
if (!skb->dev) {
XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR);
kfree_skb(skb);
goto out;
}
local_bh_disable();
#if IS_ENABLED(CONFIG_IPV6)
if (sk->sk_family == AF_INET6)
xfrm6_rcv_encap(skb, IPPROTO_ESP, 0, TCP_ENCAP_ESPINTCP);
else
#endif
xfrm4_rcv_encap(skb, IPPROTO_ESP, 0, TCP_ENCAP_ESPINTCP);
local_bh_enable();
out:
rcu_read_unlock();
}
static void espintcp_rcv(struct strparser *strp, struct sk_buff *skb)
{
struct espintcp_ctx *ctx = container_of(strp, struct espintcp_ctx,
strp);
struct strp_msg *rxm = strp_msg(skb);
int len = rxm->full_len - 2;
u32 nonesp_marker;
int err;
/* keepalive packet? */
if (unlikely(len == 1)) {
u8 data;
err = skb_copy_bits(skb, rxm->offset + 2, &data, 1);
if (err < 0) {
XFRM_INC_STATS(sock_net(strp->sk), LINUX_MIB_XFRMINHDRERROR);
kfree_skb(skb);
return;
}
if (data == 0xff) {
kfree_skb(skb);
return;
}
}
/* drop other short messages */
if (unlikely(len <= sizeof(nonesp_marker))) {
XFRM_INC_STATS(sock_net(strp->sk), LINUX_MIB_XFRMINHDRERROR);
kfree_skb(skb);
return;
}
err = skb_copy_bits(skb, rxm->offset + 2, &nonesp_marker,
sizeof(nonesp_marker));
if (err < 0) {
XFRM_INC_STATS(sock_net(strp->sk), LINUX_MIB_XFRMINHDRERROR);
kfree_skb(skb);
return;
}
/* remove header, leave non-ESP marker/SPI */
if (!pskb_pull(skb, rxm->offset + 2)) {
XFRM_INC_STATS(sock_net(strp->sk), LINUX_MIB_XFRMINERROR);
kfree_skb(skb);
return;
}
if (pskb_trim(skb, rxm->full_len - 2) != 0) {
XFRM_INC_STATS(sock_net(strp->sk), LINUX_MIB_XFRMINERROR);
kfree_skb(skb);
return;
}
if (nonesp_marker == 0)
handle_nonesp(ctx, skb, strp->sk);
else
handle_esp(skb, strp->sk);
}
static int espintcp_parse(struct strparser *strp, struct sk_buff *skb)
{
struct strp_msg *rxm = strp_msg(skb);
__be16 blen;
u16 len;
int err;
if (skb->len < rxm->offset + 2)
return 0;
err = skb_copy_bits(skb, rxm->offset, &blen, sizeof(blen));
if (err < 0)
return err;
len = be16_to_cpu(blen);
if (len < 2)
return -EINVAL;
return len;
}
static int espintcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
int flags)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
struct sk_buff *skb;
int err = 0;
int copied;
int off = 0;
skb = __skb_recv_datagram(sk, &ctx->ike_queue, flags, &off, &err);
if (!skb) {
if (err == -EAGAIN && sk->sk_shutdown & RCV_SHUTDOWN)
return 0;
return err;
}
copied = len;
if (copied > skb->len)
copied = skb->len;
else if (copied < skb->len)
msg->msg_flags |= MSG_TRUNC;
err = skb_copy_datagram_msg(skb, 0, msg, copied);
if (unlikely(err)) {
kfree_skb(skb);
return err;
}
if (flags & MSG_TRUNC)
copied = skb->len;
kfree_skb(skb);
return copied;
}
int espintcp_queue_out(struct sock *sk, struct sk_buff *skb)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
if (skb_queue_len(&ctx->out_queue) >=
READ_ONCE(net_hotdata.max_backlog)) {
kfree_skb(skb);
return -ENOBUFS;
}
__skb_queue_tail(&ctx->out_queue, skb);
return 0;
}
EXPORT_SYMBOL_GPL(espintcp_queue_out);
/* espintcp length field is 2B and length includes the length field's size */
#define MAX_ESPINTCP_MSG (((1 << 16) - 1) - 2)
static int espintcp_sendskb_locked(struct sock *sk, struct espintcp_msg *emsg,
int flags)
{
do {
int ret;
ret = skb_send_sock_locked(sk, emsg->skb,
emsg->offset, emsg->len);
if (ret < 0)
return ret;
emsg->len -= ret;
emsg->offset += ret;
} while (emsg->len > 0);
kfree_skb(emsg->skb);
memset(emsg, 0, sizeof(*emsg));
return 0;
}
static int espintcp_sendskmsg_locked(struct sock *sk,
struct espintcp_msg *emsg, int flags)
{
struct msghdr msghdr = {
.msg_flags = flags | MSG_SPLICE_PAGES | MSG_MORE,
};
struct sk_msg *skmsg = &emsg->skmsg;
bool more = flags & MSG_MORE;
struct scatterlist *sg;
int ret;
do {
struct bio_vec bvec;
sg = &skmsg->sg.data[skmsg->sg.start];
if (sg_is_last(sg) && !more)
msghdr.msg_flags &= ~MSG_MORE;
bvec_set_page(&bvec, sg_page(sg), sg->length, sg->offset);
iov_iter_bvec(&msghdr.msg_iter, ITER_SOURCE, &bvec, 1, sg->length);
ret = tcp_sendmsg_locked(sk, &msghdr, sg->length);
if (ret < 0)
return ret;
sk_msg_free_partial(sk, skmsg, ret);
} while (skmsg->sg.size);
memset(emsg, 0, sizeof(*emsg));
return 0;
}
static int espintcp_push_msgs(struct sock *sk, int flags)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
struct espintcp_msg *emsg = &ctx->partial;
int err;
if (!emsg->len)
return 0;
if (ctx->tx_running)
return -EAGAIN;
ctx->tx_running = 1;
if (emsg->skb)
err = espintcp_sendskb_locked(sk, emsg, flags);
else
err = espintcp_sendskmsg_locked(sk, emsg, flags);
if (err == -EAGAIN) {
ctx->tx_running = 0;
return flags & MSG_DONTWAIT ? -EAGAIN : 0;
}
if (!err)
memset(emsg, 0, sizeof(*emsg));
ctx->tx_running = 0;
return err;
}
int espintcp_push_skb(struct sock *sk, struct sk_buff *skb)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
struct espintcp_msg *emsg = &ctx->partial;
unsigned int len;
int offset;
if (sk->sk_state != TCP_ESTABLISHED) {
kfree_skb(skb);
return -ECONNRESET;
}
offset = skb_transport_offset(skb);
len = skb->len - offset;
espintcp_push_msgs(sk, 0);
if (emsg->len) {
kfree_skb(skb);
return -ENOBUFS;
}
skb_set_owner_w(skb, sk);
emsg->offset = offset;
emsg->len = len;
emsg->skb = skb;
espintcp_push_msgs(sk, 0);
return 0;
}
EXPORT_SYMBOL_GPL(espintcp_push_skb);
static int espintcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
long timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
struct espintcp_ctx *ctx = espintcp_getctx(sk);
struct espintcp_msg *emsg = &ctx->partial;
struct iov_iter pfx_iter;
struct kvec pfx_iov = {};
size_t msglen = size + 2;
char buf[2] = {0};
int err, end;
if (msg->msg_flags & ~MSG_DONTWAIT)
return -EOPNOTSUPP;
if (size > MAX_ESPINTCP_MSG)
return -EMSGSIZE;
if (msg->msg_controllen)
return -EOPNOTSUPP;
lock_sock(sk);
err = espintcp_push_msgs(sk, msg->msg_flags & MSG_DONTWAIT);
if (err < 0) {
if (err != -EAGAIN || !(msg->msg_flags & MSG_DONTWAIT))
err = -ENOBUFS;
goto unlock;
}
if (emsg->len) {
err = -ENOBUFS;
goto unlock;
}
sk_msg_init(&emsg->skmsg);
while (1) {
/* only -ENOMEM is possible since we don't coalesce */
err = sk_msg_alloc(sk, &emsg->skmsg, msglen, 0);
if (!err)
break;
err = sk_stream_wait_memory(sk, &timeo);
if (err)
goto fail;
}
*((__be16 *)buf) = cpu_to_be16(msglen);
pfx_iov.iov_base = buf;
pfx_iov.iov_len = sizeof(buf);
iov_iter_kvec(&pfx_iter, ITER_SOURCE, &pfx_iov, 1, pfx_iov.iov_len);
err = sk_msg_memcopy_from_iter(sk, &pfx_iter, &emsg->skmsg,
pfx_iov.iov_len);
if (err < 0)
goto fail;
err = sk_msg_memcopy_from_iter(sk, &msg->msg_iter, &emsg->skmsg, size);
if (err < 0)
goto fail;
end = emsg->skmsg.sg.end;
emsg->len = size;
sk_msg_iter_var_prev(end);
sg_mark_end(sk_msg_elem(&emsg->skmsg, end));
tcp_rate_check_app_limited(sk);
err = espintcp_push_msgs(sk, msg->msg_flags & MSG_DONTWAIT);
/* this message could be partially sent, keep it */
release_sock(sk);
return size;
fail:
sk_msg_free(sk, &emsg->skmsg);
memset(emsg, 0, sizeof(*emsg));
unlock:
release_sock(sk);
return err;
}
static struct proto espintcp_prot __ro_after_init;
static struct proto_ops espintcp_ops __ro_after_init;
static struct proto espintcp6_prot;
static struct proto_ops espintcp6_ops;
static DEFINE_MUTEX(tcpv6_prot_mutex);
static void espintcp_data_ready(struct sock *sk)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
trace_sk_data_ready(sk);
strp_data_ready(&ctx->strp);
}
static void espintcp_tx_work(struct work_struct *work)
{
struct espintcp_ctx *ctx = container_of(work,
struct espintcp_ctx, work);
struct sock *sk = ctx->strp.sk;
lock_sock(sk);
if (!ctx->tx_running)
espintcp_push_msgs(sk, 0);
release_sock(sk);
}
static void espintcp_write_space(struct sock *sk)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
schedule_work(&ctx->work);
ctx->saved_write_space(sk);
}
static void espintcp_destruct(struct sock *sk)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
ctx->saved_destruct(sk);
kfree(ctx);
}
bool tcp_is_ulp_esp(struct sock *sk)
{
return sk->sk_prot == &espintcp_prot || sk->sk_prot == &espintcp6_prot;
}
EXPORT_SYMBOL_GPL(tcp_is_ulp_esp);
static void build_protos(struct proto *espintcp_prot,
struct proto_ops *espintcp_ops,
const struct proto *orig_prot,
const struct proto_ops *orig_ops);
static int espintcp_init_sk(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct strp_callbacks cb = {
.rcv_msg = espintcp_rcv,
.parse_msg = espintcp_parse,
};
struct espintcp_ctx *ctx;
int err;
/* sockmap is not compatible with espintcp */
if (sk->sk_user_data)
return -EBUSY;
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
err = strp_init(&ctx->strp, sk, &cb);
if (err)
goto free;
__sk_dst_reset(sk);
strp_check_rcv(&ctx->strp);
skb_queue_head_init(&ctx->ike_queue);
skb_queue_head_init(&ctx->out_queue);
if (sk->sk_family == AF_INET) {
sk->sk_prot = &espintcp_prot;
sk->sk_socket->ops = &espintcp_ops;
} else {
mutex_lock(&tcpv6_prot_mutex);
if (!espintcp6_prot.recvmsg)
build_protos(&espintcp6_prot, &espintcp6_ops, sk->sk_prot, sk->sk_socket->ops);
mutex_unlock(&tcpv6_prot_mutex);
sk->sk_prot = &espintcp6_prot;
sk->sk_socket->ops = &espintcp6_ops;
}
ctx->saved_data_ready = sk->sk_data_ready;
ctx->saved_write_space = sk->sk_write_space;
ctx->saved_destruct = sk->sk_destruct;
sk->sk_data_ready = espintcp_data_ready;
sk->sk_write_space = espintcp_write_space;
sk->sk_destruct = espintcp_destruct;
rcu_assign_pointer(icsk->icsk_ulp_data, ctx);
INIT_WORK(&ctx->work, espintcp_tx_work);
/* avoid using task_frag */
sk->sk_allocation = GFP_ATOMIC;
sk->sk_use_task_frag = false;
return 0;
free:
kfree(ctx);
return err;
}
static void espintcp_release(struct sock *sk)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
struct sk_buff_head queue;
struct sk_buff *skb;
__skb_queue_head_init(&queue);
skb_queue_splice_init(&ctx->out_queue, &queue);
while ((skb = __skb_dequeue(&queue)))
espintcp_push_skb(sk, skb);
tcp_release_cb(sk);
}
static void espintcp_close(struct sock *sk, long timeout)
{
struct espintcp_ctx *ctx = espintcp_getctx(sk);
struct espintcp_msg *emsg = &ctx->partial;
strp_stop(&ctx->strp);
sk->sk_prot = &tcp_prot;
synchronize_rcu();
disable_work_sync(&ctx->work);
strp_done(&ctx->strp);
skb_queue_purge(&ctx->out_queue);
skb_queue_purge(&ctx->ike_queue);
if (emsg->len) {
if (emsg->skb)
kfree_skb(emsg->skb);
else
sk_msg_free(sk, &emsg->skmsg);
}
tcp_close(sk, timeout);
}
static __poll_t espintcp_poll(struct file *file, struct socket *sock,
poll_table *wait)
{
struct sock *sk = sock->sk;
struct espintcp_ctx *ctx = espintcp_getctx(sk);
return datagram_poll_queue(file, sock, wait, &ctx->ike_queue);
}
static void build_protos(struct proto *espintcp_prot,
struct proto_ops *espintcp_ops,
const struct proto *orig_prot,
const struct proto_ops *orig_ops)
{
memcpy(espintcp_prot, orig_prot, sizeof(struct proto));
memcpy(espintcp_ops, orig_ops, sizeof(struct proto_ops));
espintcp_prot->sendmsg = espintcp_sendmsg;
espintcp_prot->recvmsg = espintcp_recvmsg;
espintcp_prot->close = espintcp_close;
espintcp_prot->release_cb = espintcp_release;
espintcp_ops->poll = espintcp_poll;
}
static struct tcp_ulp_ops espintcp_ulp __read_mostly = {
.name = "espintcp",
.owner = THIS_MODULE,
.init = espintcp_init_sk,
};
void __init espintcp_init(void)
{
build_protos(&espintcp_prot, &espintcp_ops, &tcp_prot, &inet_stream_ops);
tcp_register_ulp(&espintcp_ulp);
}
+218
View File
@@ -0,0 +1,218 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* xfrm_trace_iptfs.h
*
* August 12 2023, Christian Hopps <chopps@labn.net>
*
* Copyright (c) 2023, LabN Consulting, L.L.C.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM iptfs
#if !defined(_TRACE_IPTFS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_IPTFS_H
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/tracepoint.h>
#include <net/ip.h>
struct xfrm_iptfs_data;
TRACE_EVENT(iptfs_egress_recv,
TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u16 blkoff),
TP_ARGS(skb, xtfs, blkoff),
TP_STRUCT__entry(__field(struct sk_buff *, skb)
__field(void *, head)
__field(void *, head_pg_addr)
__field(void *, pg0addr)
__field(u32, skb_len)
__field(u32, data_len)
__field(u32, headroom)
__field(u32, tailroom)
__field(u32, tail)
__field(u32, end)
__field(u32, pg0off)
__field(u8, head_frag)
__field(u8, frag_list)
__field(u8, nr_frags)
__field(u16, blkoff)),
TP_fast_assign(__entry->skb = skb;
__entry->head = skb->head;
__entry->skb_len = skb->len;
__entry->data_len = skb->data_len;
__entry->headroom = skb_headroom(skb);
__entry->tailroom = skb_tailroom(skb);
__entry->tail = (u32)skb->tail;
__entry->end = (u32)skb->end;
__entry->head_frag = skb->head_frag;
__entry->frag_list = (bool)skb_shinfo(skb)->frag_list;
__entry->nr_frags = skb_shinfo(skb)->nr_frags;
__entry->blkoff = blkoff;
__entry->head_pg_addr = page_address(virt_to_head_page(skb->head));
__entry->pg0addr = (__entry->nr_frags
? page_address(netmem_to_page(skb_shinfo(skb)->frags[0].netmem))
: NULL);
__entry->pg0off = (__entry->nr_frags
? skb_shinfo(skb)->frags[0].offset
: 0);
),
TP_printk("EGRESS: skb=%p len=%u data_len=%u headroom=%u head_frag=%u frag_list=%u nr_frags=%u blkoff=%u\n\t\ttailroom=%u tail=%u end=%u head=%p hdpgaddr=%p pg0->addr=%p pg0->data=%p pg0->off=%u",
__entry->skb, __entry->skb_len, __entry->data_len, __entry->headroom,
__entry->head_frag, __entry->frag_list, __entry->nr_frags, __entry->blkoff,
__entry->tailroom, __entry->tail, __entry->end, __entry->head,
__entry->head_pg_addr, __entry->pg0addr, __entry->pg0addr + __entry->pg0off,
__entry->pg0off)
)
DECLARE_EVENT_CLASS(iptfs_ingress_preq_event,
TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs,
u32 pmtu, u8 was_gso),
TP_ARGS(skb, xtfs, pmtu, was_gso),
TP_STRUCT__entry(__field(struct sk_buff *, skb)
__field(u32, skb_len)
__field(u32, data_len)
__field(u32, pmtu)
__field(u32, queue_size)
__field(u32, proto_seq)
__field(u8, proto)
__field(u8, was_gso)
),
TP_fast_assign(__entry->skb = skb;
__entry->skb_len = skb->len;
__entry->data_len = skb->data_len;
__entry->queue_size =
xtfs->cfg.max_queue_size - xtfs->queue_size;
__entry->proto = __trace_ip_proto(ip_hdr(skb));
__entry->proto_seq = __trace_ip_proto_seq(ip_hdr(skb));
__entry->pmtu = pmtu;
__entry->was_gso = was_gso;
),
TP_printk("INGRPREQ: skb=%p len=%u data_len=%u qsize=%u proto=%u proto_seq=%u pmtu=%u was_gso=%u",
__entry->skb, __entry->skb_len, __entry->data_len,
__entry->queue_size, __entry->proto, __entry->proto_seq,
__entry->pmtu, __entry->was_gso));
DEFINE_EVENT(iptfs_ingress_preq_event, iptfs_enqueue,
TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u32 pmtu, u8 was_gso),
TP_ARGS(skb, xtfs, pmtu, was_gso));
DEFINE_EVENT(iptfs_ingress_preq_event, iptfs_no_queue_space,
TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u32 pmtu, u8 was_gso),
TP_ARGS(skb, xtfs, pmtu, was_gso));
DEFINE_EVENT(iptfs_ingress_preq_event, iptfs_too_big,
TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u32 pmtu, u8 was_gso),
TP_ARGS(skb, xtfs, pmtu, was_gso));
DECLARE_EVENT_CLASS(iptfs_ingress_postq_event,
TP_PROTO(struct sk_buff *skb, u32 mtu, u16 blkoff, struct iphdr *iph),
TP_ARGS(skb, mtu, blkoff, iph),
TP_STRUCT__entry(__field(struct sk_buff *, skb)
__field(u32, skb_len)
__field(u32, data_len)
__field(u32, mtu)
__field(u32, proto_seq)
__field(u16, blkoff)
__field(u8, proto)),
TP_fast_assign(__entry->skb = skb;
__entry->skb_len = skb->len;
__entry->data_len = skb->data_len;
__entry->mtu = mtu;
__entry->blkoff = blkoff;
__entry->proto = iph ? __trace_ip_proto(iph) : 0;
__entry->proto_seq = iph ? __trace_ip_proto_seq(iph) : 0;
),
TP_printk("INGRPSTQ: skb=%p len=%u data_len=%u mtu=%u blkoff=%u proto=%u proto_seq=%u",
__entry->skb, __entry->skb_len, __entry->data_len, __entry->mtu,
__entry->blkoff, __entry->proto, __entry->proto_seq));
DEFINE_EVENT(iptfs_ingress_postq_event, iptfs_first_dequeue,
TP_PROTO(struct sk_buff *skb, u32 mtu, u16 blkoff,
struct iphdr *iph),
TP_ARGS(skb, mtu, blkoff, iph));
DEFINE_EVENT(iptfs_ingress_postq_event, iptfs_first_fragmenting,
TP_PROTO(struct sk_buff *skb, u32 mtu, u16 blkoff,
struct iphdr *iph),
TP_ARGS(skb, mtu, blkoff, iph));
DEFINE_EVENT(iptfs_ingress_postq_event, iptfs_first_final_fragment,
TP_PROTO(struct sk_buff *skb, u32 mtu, u16 blkoff,
struct iphdr *iph),
TP_ARGS(skb, mtu, blkoff, iph));
DEFINE_EVENT(iptfs_ingress_postq_event, iptfs_first_toobig,
TP_PROTO(struct sk_buff *skb, u32 mtu, u16 blkoff,
struct iphdr *iph),
TP_ARGS(skb, mtu, blkoff, iph));
TRACE_EVENT(iptfs_ingress_nth_peek,
TP_PROTO(struct sk_buff *skb, u32 remaining),
TP_ARGS(skb, remaining),
TP_STRUCT__entry(__field(struct sk_buff *, skb)
__field(u32, skb_len)
__field(u32, remaining)),
TP_fast_assign(__entry->skb = skb;
__entry->skb_len = skb->len;
__entry->remaining = remaining;
),
TP_printk("INGRPSTQ: NTHPEEK: skb=%p len=%u remaining=%u",
__entry->skb, __entry->skb_len, __entry->remaining));
TRACE_EVENT(iptfs_ingress_nth_add, TP_PROTO(struct sk_buff *skb, u8 share_ok),
TP_ARGS(skb, share_ok),
TP_STRUCT__entry(__field(struct sk_buff *, skb)
__field(u32, skb_len)
__field(u32, data_len)
__field(u8, share_ok)
__field(u8, head_frag)
__field(u8, pp_recycle)
__field(u8, cloned)
__field(u8, shared)
__field(u8, nr_frags)
__field(u8, frag_list)
),
TP_fast_assign(__entry->skb = skb;
__entry->skb_len = skb->len;
__entry->data_len = skb->data_len;
__entry->share_ok = share_ok;
__entry->head_frag = skb->head_frag;
__entry->pp_recycle = skb->pp_recycle;
__entry->cloned = skb_cloned(skb);
__entry->shared = skb_shared(skb);
__entry->nr_frags = skb_shinfo(skb)->nr_frags;
__entry->frag_list = (bool)skb_shinfo(skb)->frag_list;
),
TP_printk("INGRPSTQ: NTHADD: skb=%p len=%u data_len=%u share_ok=%u head_frag=%u pp_recycle=%u cloned=%u shared=%u nr_frags=%u frag_list=%u",
__entry->skb, __entry->skb_len, __entry->data_len, __entry->share_ok,
__entry->head_frag, __entry->pp_recycle, __entry->cloned, __entry->shared,
__entry->nr_frags, __entry->frag_list));
DECLARE_EVENT_CLASS(iptfs_timer_event,
TP_PROTO(struct xfrm_iptfs_data *xtfs, u64 time_val),
TP_ARGS(xtfs, time_val),
TP_STRUCT__entry(__field(u64, time_val)
__field(u64, set_time)),
TP_fast_assign(__entry->time_val = time_val;
__entry->set_time = xtfs->iptfs_settime;
),
TP_printk("TIMER: set_time=%llu time_val=%llu",
__entry->set_time, __entry->time_val));
DEFINE_EVENT(iptfs_timer_event, iptfs_timer_start,
TP_PROTO(struct xfrm_iptfs_data *xtfs, u64 time_val),
TP_ARGS(xtfs, time_val));
DEFINE_EVENT(iptfs_timer_event, iptfs_timer_expire,
TP_PROTO(struct xfrm_iptfs_data *xtfs, u64 time_val),
TP_ARGS(xtfs, time_val));
#endif /* _TRACE_IPTFS_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../net/xfrm
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace_iptfs
#include <trace/define_trace.h>
+841
View File
@@ -0,0 +1,841 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* xfrm algorithm interface
*
* Copyright (c) 2002 James Morris <jmorris@intercode.com.au>
*/
#include <crypto/acompress.h>
#include <crypto/aead.h>
#include <crypto/hash.h>
#include <crypto/skcipher.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pfkeyv2.h>
#include <linux/scatterlist.h>
#include <net/xfrm.h>
#if IS_ENABLED(CONFIG_INET_ESP) || IS_ENABLED(CONFIG_INET6_ESP)
#include <net/esp.h>
#endif
/*
* Algorithms supported by IPsec. These entries contain properties which
* are used in key negotiation and xfrm processing, and are used to verify
* that instantiated crypto transforms have correct parameters for IPsec
* purposes.
*/
static struct xfrm_algo_desc aead_list[] = {
{
.name = "rfc4106(gcm(aes))",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 64,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AES_GCM_ICV8,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc4106(gcm(aes))",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 96,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AES_GCM_ICV12,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc4106(gcm(aes))",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AES_GCM_ICV16,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc4309(ccm(aes))",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 64,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AES_CCM_ICV8,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc4309(ccm(aes))",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 96,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AES_CCM_ICV12,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc4309(ccm(aes))",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AES_CCM_ICV16,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc4543(gcm(aes))",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_NULL_AES_GMAC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc7539esp(chacha20,poly1305)",
.uinfo = {
.aead = {
.geniv = "seqiv",
.icv_truncbits = 128,
}
},
.pfkey_supported = 0,
},
};
static struct xfrm_algo_desc aalg_list[] = {
{
.name = "digest_null",
.uinfo = {
.auth = {
.icv_truncbits = 0,
.icv_fullbits = 0,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_AALG_NULL,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 0,
.sadb_alg_maxbits = 0
}
},
{
.name = "hmac(md5)",
.compat = "md5",
.uinfo = {
.auth = {
.icv_truncbits = 96,
.icv_fullbits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_AALG_MD5HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 128
}
},
{
.name = "hmac(sha1)",
.compat = "sha1",
.uinfo = {
.auth = {
.icv_truncbits = 96,
.icv_fullbits = 160,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_AALG_SHA1HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 160,
.sadb_alg_maxbits = 160
}
},
{
.name = "hmac(sha256)",
.compat = "sha256",
.uinfo = {
.auth = {
.icv_truncbits = 96,
.icv_fullbits = 256,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_AALG_SHA2_256HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 256,
.sadb_alg_maxbits = 256
}
},
{
.name = "hmac(sha384)",
.uinfo = {
.auth = {
.icv_truncbits = 192,
.icv_fullbits = 384,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_AALG_SHA2_384HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 384,
.sadb_alg_maxbits = 384
}
},
{
.name = "hmac(sha512)",
.uinfo = {
.auth = {
.icv_truncbits = 256,
.icv_fullbits = 512,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_AALG_SHA2_512HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 512,
.sadb_alg_maxbits = 512
}
},
{
.name = "xcbc(aes)",
.uinfo = {
.auth = {
.icv_truncbits = 96,
.icv_fullbits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_AALG_AES_XCBC_MAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 128
}
},
{
/* rfc4494 */
.name = "cmac(aes)",
.uinfo = {
.auth = {
.icv_truncbits = 96,
.icv_fullbits = 128,
}
},
.pfkey_supported = 0,
},
{
.name = "hmac(sm3)",
.compat = "sm3",
.uinfo = {
.auth = {
.icv_truncbits = 256,
.icv_fullbits = 256,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_AALG_SM3_256HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 256,
.sadb_alg_maxbits = 256
}
},
};
static struct xfrm_algo_desc ealg_list[] = {
{
.name = "ecb(cipher_null)",
.compat = "cipher_null",
.uinfo = {
.encr = {
.blockbits = 8,
.defkeybits = 0,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_EALG_NULL,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 0,
.sadb_alg_maxbits = 0
}
},
{
.name = "cbc(des)",
.compat = "des",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 64,
.defkeybits = 64,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_EALG_DESCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 64,
.sadb_alg_maxbits = 64
}
},
{
.name = "cbc(des3_ede)",
.compat = "des3_ede",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 64,
.defkeybits = 192,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_EALG_3DESCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 192,
.sadb_alg_maxbits = 192
}
},
{
.name = "cbc(cast5)",
.compat = "cast5",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 64,
.defkeybits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_CASTCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 40,
.sadb_alg_maxbits = 128
}
},
{
.name = "cbc(blowfish)",
.compat = "blowfish",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 64,
.defkeybits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_BLOWFISHCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 40,
.sadb_alg_maxbits = 448
}
},
{
.name = "cbc(aes)",
.compat = "aes",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 128,
.defkeybits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AESCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "cbc(serpent)",
.compat = "serpent",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 128,
.defkeybits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_SERPENTCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256,
}
},
{
.name = "cbc(camellia)",
.compat = "camellia",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 128,
.defkeybits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_CAMELLIACBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "cbc(twofish)",
.compat = "twofish",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 128,
.defkeybits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_TWOFISHCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
{
.name = "rfc3686(ctr(aes))",
.uinfo = {
.encr = {
.geniv = "seqiv",
.blockbits = 128,
.defkeybits = 160, /* 128-bit key + 32-bit nonce */
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_AESCTR,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 160,
.sadb_alg_maxbits = 288
}
},
{
.name = "cbc(sm4)",
.compat = "sm4",
.uinfo = {
.encr = {
.geniv = "echainiv",
.blockbits = 128,
.defkeybits = 128,
}
},
.pfkey_supported = 1,
.desc = {
.sadb_alg_id = SADB_X_EALG_SM4CBC,
.sadb_alg_ivlen = 16,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 256
}
},
};
static struct xfrm_algo_desc calg_list[] = {
{
.name = "deflate",
.uinfo = {
.comp = {
.threshold = 90,
}
},
.pfkey_supported = 1,
.desc = { .sadb_alg_id = SADB_X_CALG_DEFLATE }
},
{
.name = "lzs",
.uinfo = {
.comp = {
.threshold = 90,
}
},
.pfkey_supported = 1,
.desc = { .sadb_alg_id = SADB_X_CALG_LZS }
},
{
.name = "lzjh",
.uinfo = {
.comp = {
.threshold = 50,
}
},
.pfkey_supported = 1,
.desc = { .sadb_alg_id = SADB_X_CALG_LZJH }
},
};
static inline int aalg_entries(void)
{
return ARRAY_SIZE(aalg_list);
}
static inline int ealg_entries(void)
{
return ARRAY_SIZE(ealg_list);
}
static inline int calg_entries(void)
{
return ARRAY_SIZE(calg_list);
}
struct xfrm_algo_list {
int (*find)(const char *name, u32 type, u32 mask);
struct xfrm_algo_desc *algs;
int entries;
};
static const struct xfrm_algo_list xfrm_aead_list = {
.find = crypto_has_aead,
.algs = aead_list,
.entries = ARRAY_SIZE(aead_list),
};
static const struct xfrm_algo_list xfrm_aalg_list = {
.find = crypto_has_ahash,
.algs = aalg_list,
.entries = ARRAY_SIZE(aalg_list),
};
static const struct xfrm_algo_list xfrm_ealg_list = {
.find = crypto_has_skcipher,
.algs = ealg_list,
.entries = ARRAY_SIZE(ealg_list),
};
static const struct xfrm_algo_list xfrm_calg_list = {
.find = crypto_has_acomp,
.algs = calg_list,
.entries = ARRAY_SIZE(calg_list),
};
static struct xfrm_algo_desc *xfrm_find_algo(
const struct xfrm_algo_list *algo_list,
int match(const struct xfrm_algo_desc *entry, const void *data),
const void *data, int probe)
{
struct xfrm_algo_desc *list = algo_list->algs;
int i, status;
for (i = 0; i < algo_list->entries; i++) {
if (!match(list + i, data))
continue;
if (list[i].available)
return &list[i];
if (!probe)
break;
status = algo_list->find(list[i].name, 0, 0);
if (!status)
break;
list[i].available = status;
return &list[i];
}
return NULL;
}
static int xfrm_alg_id_match(const struct xfrm_algo_desc *entry,
const void *data)
{
return entry->desc.sadb_alg_id == (unsigned long)data;
}
struct xfrm_algo_desc *xfrm_aalg_get_byid(int alg_id)
{
return xfrm_find_algo(&xfrm_aalg_list, xfrm_alg_id_match,
(void *)(unsigned long)alg_id, 1);
}
EXPORT_SYMBOL_GPL(xfrm_aalg_get_byid);
struct xfrm_algo_desc *xfrm_ealg_get_byid(int alg_id)
{
return xfrm_find_algo(&xfrm_ealg_list, xfrm_alg_id_match,
(void *)(unsigned long)alg_id, 1);
}
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byid);
struct xfrm_algo_desc *xfrm_calg_get_byid(int alg_id)
{
return xfrm_find_algo(&xfrm_calg_list, xfrm_alg_id_match,
(void *)(unsigned long)alg_id, 1);
}
EXPORT_SYMBOL_GPL(xfrm_calg_get_byid);
static int xfrm_alg_name_match(const struct xfrm_algo_desc *entry,
const void *data)
{
const char *name = data;
return name && (!strcmp(name, entry->name) ||
(entry->compat && !strcmp(name, entry->compat)));
}
struct xfrm_algo_desc *xfrm_aalg_get_byname(const char *name, int probe)
{
return xfrm_find_algo(&xfrm_aalg_list, xfrm_alg_name_match, name,
probe);
}
EXPORT_SYMBOL_GPL(xfrm_aalg_get_byname);
struct xfrm_algo_desc *xfrm_ealg_get_byname(const char *name, int probe)
{
return xfrm_find_algo(&xfrm_ealg_list, xfrm_alg_name_match, name,
probe);
}
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byname);
struct xfrm_algo_desc *xfrm_calg_get_byname(const char *name, int probe)
{
return xfrm_find_algo(&xfrm_calg_list, xfrm_alg_name_match, name,
probe);
}
EXPORT_SYMBOL_GPL(xfrm_calg_get_byname);
struct xfrm_aead_name {
const char *name;
int icvbits;
};
static int xfrm_aead_name_match(const struct xfrm_algo_desc *entry,
const void *data)
{
const struct xfrm_aead_name *aead = data;
const char *name = aead->name;
return aead->icvbits == entry->uinfo.aead.icv_truncbits && name &&
!strcmp(name, entry->name);
}
struct xfrm_algo_desc *xfrm_aead_get_byname(const char *name, int icv_len, int probe)
{
struct xfrm_aead_name data = {
.name = name,
.icvbits = icv_len,
};
return xfrm_find_algo(&xfrm_aead_list, xfrm_aead_name_match, &data,
probe);
}
EXPORT_SYMBOL_GPL(xfrm_aead_get_byname);
struct xfrm_algo_desc *xfrm_aalg_get_byidx(unsigned int idx)
{
if (idx >= aalg_entries())
return NULL;
return &aalg_list[idx];
}
EXPORT_SYMBOL_GPL(xfrm_aalg_get_byidx);
struct xfrm_algo_desc *xfrm_ealg_get_byidx(unsigned int idx)
{
if (idx >= ealg_entries())
return NULL;
return &ealg_list[idx];
}
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byidx);
/*
* Probe for the availability of crypto algorithms, and set the available
* flag for any algorithms found on the system. This is typically called by
* pfkey during userspace SA add, update or register.
*/
void xfrm_probe_algs(void)
{
int i, status;
BUG_ON(in_softirq());
for (i = 0; i < aalg_entries(); i++) {
status = crypto_has_ahash(aalg_list[i].name, 0, 0);
if (aalg_list[i].available != status)
aalg_list[i].available = status;
}
for (i = 0; i < ealg_entries(); i++) {
status = crypto_has_skcipher(ealg_list[i].name, 0, 0);
if (ealg_list[i].available != status)
ealg_list[i].available = status;
}
for (i = 0; i < calg_entries(); i++) {
status = crypto_has_acomp(calg_list[i].name, 0, 0);
if (calg_list[i].available != status)
calg_list[i].available = status;
}
}
EXPORT_SYMBOL_GPL(xfrm_probe_algs);
int xfrm_count_pfkey_auth_supported(void)
{
int i, n;
for (i = 0, n = 0; i < aalg_entries(); i++)
if (aalg_list[i].available && aalg_list[i].pfkey_supported)
n++;
return n;
}
EXPORT_SYMBOL_GPL(xfrm_count_pfkey_auth_supported);
int xfrm_count_pfkey_enc_supported(void)
{
int i, n;
for (i = 0, n = 0; i < ealg_entries(); i++)
if (ealg_list[i].available && ealg_list[i].pfkey_supported)
n++;
return n;
}
EXPORT_SYMBOL_GPL(xfrm_count_pfkey_enc_supported);
MODULE_DESCRIPTION("XFRM Algorithm interface");
MODULE_LICENSE("GPL");
+691
View File
@@ -0,0 +1,691 @@
// SPDX-License-Identifier: GPL-2.0
/*
* XFRM compat layer
* Author: Dmitry Safonov <dima@arista.com>
* Based on code and translator idea by: Florian Westphal <fw@strlen.de>
*/
#include <linux/compat.h>
#include <linux/nospec.h>
#include <linux/xfrm.h>
#include <net/xfrm.h>
struct compat_xfrm_lifetime_cfg {
compat_u64 soft_byte_limit, hard_byte_limit;
compat_u64 soft_packet_limit, hard_packet_limit;
compat_u64 soft_add_expires_seconds, hard_add_expires_seconds;
compat_u64 soft_use_expires_seconds, hard_use_expires_seconds;
}; /* same size on 32bit, but only 4 byte alignment required */
struct compat_xfrm_lifetime_cur {
compat_u64 bytes, packets, add_time, use_time;
}; /* same size on 32bit, but only 4 byte alignment required */
struct compat_xfrm_userpolicy_info {
struct xfrm_selector sel;
struct compat_xfrm_lifetime_cfg lft;
struct compat_xfrm_lifetime_cur curlft;
__u32 priority, index;
u8 dir, action, flags, share;
/* 4 bytes additional padding on 64bit */
};
struct compat_xfrm_usersa_info {
struct xfrm_selector sel;
struct xfrm_id id;
xfrm_address_t saddr;
struct compat_xfrm_lifetime_cfg lft;
struct compat_xfrm_lifetime_cur curlft;
struct xfrm_stats stats;
__u32 seq, reqid;
u16 family;
u8 mode, replay_window, flags;
/* 4 bytes additional padding on 64bit */
};
struct compat_xfrm_user_acquire {
struct xfrm_id id;
xfrm_address_t saddr;
struct xfrm_selector sel;
struct compat_xfrm_userpolicy_info policy;
/* 4 bytes additional padding on 64bit */
__u32 aalgos, ealgos, calgos, seq;
};
struct compat_xfrm_userspi_info {
struct compat_xfrm_usersa_info info;
/* 4 bytes additional padding on 64bit */
__u32 min, max;
};
struct compat_xfrm_user_expire {
struct compat_xfrm_usersa_info state;
/* 8 bytes additional padding on 64bit */
u8 hard;
};
struct compat_xfrm_user_polexpire {
struct compat_xfrm_userpolicy_info pol;
/* 8 bytes additional padding on 64bit */
u8 hard;
};
#define XMSGSIZE(type) sizeof(struct type)
static const int compat_msg_min[XFRM_NR_MSGTYPES] = {
[XFRM_MSG_NEWSA - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_usersa_info),
[XFRM_MSG_DELSA - XFRM_MSG_BASE] = XMSGSIZE(xfrm_usersa_id),
[XFRM_MSG_GETSA - XFRM_MSG_BASE] = XMSGSIZE(xfrm_usersa_id),
[XFRM_MSG_NEWPOLICY - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_userpolicy_info),
[XFRM_MSG_DELPOLICY - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_id),
[XFRM_MSG_GETPOLICY - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_id),
[XFRM_MSG_ALLOCSPI - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_userspi_info),
[XFRM_MSG_ACQUIRE - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_user_acquire),
[XFRM_MSG_EXPIRE - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_user_expire),
[XFRM_MSG_UPDPOLICY - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_userpolicy_info),
[XFRM_MSG_UPDSA - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_usersa_info),
[XFRM_MSG_POLEXPIRE - XFRM_MSG_BASE] = XMSGSIZE(compat_xfrm_user_polexpire),
[XFRM_MSG_FLUSHSA - XFRM_MSG_BASE] = XMSGSIZE(xfrm_usersa_flush),
[XFRM_MSG_FLUSHPOLICY - XFRM_MSG_BASE] = 0,
[XFRM_MSG_NEWAE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_aevent_id),
[XFRM_MSG_GETAE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_aevent_id),
[XFRM_MSG_REPORT - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_report),
[XFRM_MSG_MIGRATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_id),
[XFRM_MSG_NEWSADINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_GETSADINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_NEWSPDINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = sizeof(u32),
[XFRM_MSG_MAPPING - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping),
[XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state),
};
static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
[XFRMA_UNSPEC] = { .strict_start_type = XFRMA_SA_DIR },
[XFRMA_SA] = { .len = XMSGSIZE(compat_xfrm_usersa_info)},
[XFRMA_POLICY] = { .len = XMSGSIZE(compat_xfrm_userpolicy_info)},
[XFRMA_LASTUSED] = { .type = NLA_U64},
[XFRMA_ALG_AUTH_TRUNC] = { .len = sizeof(struct xfrm_algo_auth)},
[XFRMA_ALG_AEAD] = { .len = sizeof(struct xfrm_algo_aead) },
[XFRMA_ALG_AUTH] = { .len = sizeof(struct xfrm_algo) },
[XFRMA_ALG_CRYPT] = { .len = sizeof(struct xfrm_algo) },
[XFRMA_ALG_COMP] = { .len = sizeof(struct xfrm_algo) },
[XFRMA_ENCAP] = { .len = sizeof(struct xfrm_encap_tmpl) },
[XFRMA_TMPL] = { .len = sizeof(struct xfrm_user_tmpl) },
[XFRMA_SEC_CTX] = { .len = sizeof(struct xfrm_user_sec_ctx) },
[XFRMA_LTIME_VAL] = { .len = sizeof(struct xfrm_lifetime_cur) },
[XFRMA_REPLAY_VAL] = { .len = sizeof(struct xfrm_replay_state) },
[XFRMA_REPLAY_THRESH] = { .type = NLA_U32 },
[XFRMA_ETIMER_THRESH] = { .type = NLA_U32 },
[XFRMA_SRCADDR] = { .len = sizeof(xfrm_address_t) },
[XFRMA_COADDR] = { .len = sizeof(xfrm_address_t) },
[XFRMA_POLICY_TYPE] = { .len = sizeof(struct xfrm_userpolicy_type)},
[XFRMA_MIGRATE] = { .len = sizeof(struct xfrm_user_migrate) },
[XFRMA_KMADDRESS] = { .len = sizeof(struct xfrm_user_kmaddress) },
[XFRMA_MARK] = { .len = sizeof(struct xfrm_mark) },
[XFRMA_TFCPAD] = { .type = NLA_U32 },
[XFRMA_REPLAY_ESN_VAL] = { .len = sizeof(struct xfrm_replay_state_esn) },
[XFRMA_SA_EXTRA_FLAGS] = { .type = NLA_U32 },
[XFRMA_PROTO] = { .type = NLA_U8 },
[XFRMA_ADDRESS_FILTER] = { .len = sizeof(struct xfrm_address_filter) },
[XFRMA_OFFLOAD_DEV] = { .len = sizeof(struct xfrm_user_offload) },
[XFRMA_SET_MARK] = { .type = NLA_U32 },
[XFRMA_SET_MARK_MASK] = { .type = NLA_U32 },
[XFRMA_IF_ID] = { .type = NLA_U32 },
[XFRMA_MTIMER_THRESH] = { .type = NLA_U32 },
[XFRMA_SA_DIR] = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT),
[XFRMA_NAT_KEEPALIVE_INTERVAL] = { .type = NLA_U32 },
[XFRMA_SA_PCPU] = { .type = NLA_U32 },
};
static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb,
const struct nlmsghdr *nlh_src, u16 type)
{
int payload = compat_msg_min[type];
int src_len = xfrm_msg_min[type];
struct nlmsghdr *nlh_dst;
/* Compat messages are shorter or equal to native (+padding) */
if (WARN_ON_ONCE(src_len < payload))
return ERR_PTR(-EMSGSIZE);
nlh_dst = nlmsg_put(skb, nlh_src->nlmsg_pid, nlh_src->nlmsg_seq,
nlh_src->nlmsg_type, payload, nlh_src->nlmsg_flags);
if (!nlh_dst)
return ERR_PTR(-EMSGSIZE);
memset(nlmsg_data(nlh_dst), 0, payload);
switch (nlh_src->nlmsg_type) {
/* Compat message has the same layout as native */
case XFRM_MSG_DELSA:
case XFRM_MSG_DELPOLICY:
case XFRM_MSG_FLUSHSA:
case XFRM_MSG_FLUSHPOLICY:
case XFRM_MSG_NEWAE:
case XFRM_MSG_REPORT:
case XFRM_MSG_MIGRATE:
case XFRM_MSG_MIGRATE_STATE:
case XFRM_MSG_NEWSADINFO:
case XFRM_MSG_NEWSPDINFO:
case XFRM_MSG_MAPPING:
WARN_ON_ONCE(src_len != payload);
memcpy(nlmsg_data(nlh_dst), nlmsg_data(nlh_src), src_len);
break;
/* 4 byte alignment for trailing u64 on native, but not on compat */
case XFRM_MSG_NEWSA:
case XFRM_MSG_NEWPOLICY:
case XFRM_MSG_UPDSA:
case XFRM_MSG_UPDPOLICY:
WARN_ON_ONCE(src_len != payload + 4);
memcpy(nlmsg_data(nlh_dst), nlmsg_data(nlh_src), payload);
break;
case XFRM_MSG_EXPIRE: {
const struct xfrm_user_expire *src_ue = nlmsg_data(nlh_src);
struct compat_xfrm_user_expire *dst_ue = nlmsg_data(nlh_dst);
/* compat_xfrm_user_expire has 4-byte smaller state */
memcpy(dst_ue, src_ue, sizeof(dst_ue->state));
dst_ue->hard = src_ue->hard;
break;
}
case XFRM_MSG_ACQUIRE: {
const struct xfrm_user_acquire *src_ua = nlmsg_data(nlh_src);
struct compat_xfrm_user_acquire *dst_ua = nlmsg_data(nlh_dst);
memcpy(dst_ua, src_ua, offsetof(struct compat_xfrm_user_acquire, aalgos));
dst_ua->aalgos = src_ua->aalgos;
dst_ua->ealgos = src_ua->ealgos;
dst_ua->calgos = src_ua->calgos;
dst_ua->seq = src_ua->seq;
break;
}
case XFRM_MSG_POLEXPIRE: {
const struct xfrm_user_polexpire *src_upe = nlmsg_data(nlh_src);
struct compat_xfrm_user_polexpire *dst_upe = nlmsg_data(nlh_dst);
/* compat_xfrm_user_polexpire has 4-byte smaller state */
memcpy(dst_upe, src_upe, sizeof(dst_upe->pol));
dst_upe->hard = src_upe->hard;
break;
}
case XFRM_MSG_ALLOCSPI: {
const struct xfrm_userspi_info *src_usi = nlmsg_data(nlh_src);
struct compat_xfrm_userspi_info *dst_usi = nlmsg_data(nlh_dst);
/* compat_xfrm_user_polexpire has 4-byte smaller state */
memcpy(dst_usi, src_usi, sizeof(src_usi->info));
dst_usi->min = src_usi->min;
dst_usi->max = src_usi->max;
break;
}
/* Not being sent by kernel */
case XFRM_MSG_GETSA:
case XFRM_MSG_GETPOLICY:
case XFRM_MSG_GETAE:
case XFRM_MSG_GETSADINFO:
case XFRM_MSG_GETSPDINFO:
default:
pr_warn_once("unsupported nlmsg_type %d\n", nlh_src->nlmsg_type);
return ERR_PTR(-EOPNOTSUPP);
}
return nlh_dst;
}
static int xfrm_nla_cpy(struct sk_buff *dst, const struct nlattr *src, int len)
{
return nla_put(dst, src->nla_type, len, nla_data(src));
}
static int xfrm_xlate64_attr(struct sk_buff *dst, const struct nlattr *src)
{
switch (src->nla_type) {
case XFRMA_PAD:
/* Ignore */
return 0;
case XFRMA_UNSPEC:
case XFRMA_ALG_AUTH:
case XFRMA_ALG_CRYPT:
case XFRMA_ALG_COMP:
case XFRMA_ENCAP:
case XFRMA_TMPL:
return xfrm_nla_cpy(dst, src, nla_len(src));
case XFRMA_SA:
return xfrm_nla_cpy(dst, src, XMSGSIZE(compat_xfrm_usersa_info));
case XFRMA_POLICY:
return xfrm_nla_cpy(dst, src, XMSGSIZE(compat_xfrm_userpolicy_info));
case XFRMA_SEC_CTX:
return xfrm_nla_cpy(dst, src, nla_len(src));
case XFRMA_LTIME_VAL:
return nla_put_64bit(dst, src->nla_type, nla_len(src),
nla_data(src), XFRMA_PAD);
case XFRMA_REPLAY_VAL:
case XFRMA_REPLAY_THRESH:
case XFRMA_ETIMER_THRESH:
case XFRMA_SRCADDR:
case XFRMA_COADDR:
return xfrm_nla_cpy(dst, src, nla_len(src));
case XFRMA_LASTUSED:
return nla_put_64bit(dst, src->nla_type, nla_len(src),
nla_data(src), XFRMA_PAD);
case XFRMA_POLICY_TYPE:
case XFRMA_MIGRATE:
case XFRMA_ALG_AEAD:
case XFRMA_KMADDRESS:
case XFRMA_ALG_AUTH_TRUNC:
case XFRMA_MARK:
case XFRMA_TFCPAD:
case XFRMA_REPLAY_ESN_VAL:
case XFRMA_SA_EXTRA_FLAGS:
case XFRMA_PROTO:
case XFRMA_ADDRESS_FILTER:
case XFRMA_OFFLOAD_DEV:
case XFRMA_SET_MARK:
case XFRMA_SET_MARK_MASK:
case XFRMA_IF_ID:
case XFRMA_MTIMER_THRESH:
case XFRMA_SA_DIR:
case XFRMA_NAT_KEEPALIVE_INTERVAL:
case XFRMA_SA_PCPU:
case XFRMA_IPTFS_DROP_TIME:
case XFRMA_IPTFS_REORDER_WINDOW:
case XFRMA_IPTFS_DONT_FRAG:
case XFRMA_IPTFS_INIT_DELAY:
case XFRMA_IPTFS_MAX_QSIZE:
case XFRMA_IPTFS_PKT_SIZE:
return xfrm_nla_cpy(dst, src, nla_len(src));
default:
BUILD_BUG_ON(XFRMA_MAX != XFRMA_IPTFS_PKT_SIZE);
pr_warn_once("unsupported nla_type %d\n", src->nla_type);
return -EOPNOTSUPP;
}
}
/* Take kernel-built (64bit layout) and create 32bit layout for userspace */
static int xfrm_xlate64(struct sk_buff *dst, const struct nlmsghdr *nlh_src)
{
u16 type = nlh_src->nlmsg_type - XFRM_MSG_BASE;
const struct nlattr *nla, *attrs;
struct nlmsghdr *nlh_dst;
int len, remaining;
nlh_dst = xfrm_nlmsg_put_compat(dst, nlh_src, type);
if (IS_ERR(nlh_dst))
return PTR_ERR(nlh_dst);
attrs = nlmsg_attrdata(nlh_src, xfrm_msg_min[type]);
len = nlmsg_attrlen(nlh_src, xfrm_msg_min[type]);
nla_for_each_attr(nla, attrs, len, remaining) {
int err;
switch (nlh_src->nlmsg_type) {
case XFRM_MSG_NEWSPDINFO:
err = xfrm_nla_cpy(dst, nla, nla_len(nla));
break;
default:
err = xfrm_xlate64_attr(dst, nla);
break;
}
if (err)
return err;
}
nlmsg_end(dst, nlh_dst);
return 0;
}
static int xfrm_alloc_compat(struct sk_buff *skb, const struct nlmsghdr *nlh_src)
{
u16 type = nlh_src->nlmsg_type - XFRM_MSG_BASE;
struct sk_buff *new = NULL;
int err;
if (type >= ARRAY_SIZE(xfrm_msg_min)) {
pr_warn_once("unsupported nlmsg_type %d\n", nlh_src->nlmsg_type);
return -EOPNOTSUPP;
}
if (skb_shinfo(skb)->frag_list == NULL) {
new = alloc_skb(skb->len + skb_tailroom(skb), GFP_ATOMIC);
if (!new)
return -ENOMEM;
skb_shinfo(skb)->frag_list = new;
}
err = xfrm_xlate64(skb_shinfo(skb)->frag_list, nlh_src);
if (err) {
if (new) {
kfree_skb(new);
skb_shinfo(skb)->frag_list = NULL;
}
return err;
}
return 0;
}
/* Calculates len of translated 64-bit message. */
static size_t xfrm_user_rcv_calculate_len64(const struct nlmsghdr *src,
struct nlattr *attrs[XFRMA_MAX + 1],
int maxtype)
{
size_t len = nlmsg_len(src);
switch (src->nlmsg_type) {
case XFRM_MSG_NEWSA:
case XFRM_MSG_NEWPOLICY:
case XFRM_MSG_ALLOCSPI:
case XFRM_MSG_ACQUIRE:
case XFRM_MSG_UPDPOLICY:
case XFRM_MSG_UPDSA:
len += 4;
break;
case XFRM_MSG_EXPIRE:
case XFRM_MSG_POLEXPIRE:
len += 8;
break;
case XFRM_MSG_NEWSPDINFO:
/* attirbutes are xfrm_spdattr_type_t, not xfrm_attr_type_t */
return len;
default:
break;
}
/* Unexpected for anything, but XFRM_MSG_NEWSPDINFO, please
* correct both 64=>32-bit and 32=>64-bit translators to copy
* new attributes.
*/
if (WARN_ON_ONCE(maxtype))
return len;
if (attrs[XFRMA_SA])
len += 4;
if (attrs[XFRMA_POLICY])
len += 4;
/* XXX: some attrs may need to be realigned
* if !CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
*/
return len;
}
static int xfrm_attr_cpy32(void *dst, size_t *pos, const struct nlattr *src,
size_t size, int copy_len, int payload)
{
struct nlmsghdr *nlmsg = dst;
struct nlattr *nla;
/* xfrm_user_rcv_msg_compat() relies on fact that 32-bit messages
* have the same len or shorted than 64-bit ones.
* 32-bit translation that is bigger than 64-bit original is unexpected.
*/
if (WARN_ON_ONCE(copy_len > payload))
copy_len = payload;
if (size - *pos < nla_attr_size(payload))
return -ENOBUFS;
nla = dst + *pos;
memcpy(nla, src, nla_attr_size(copy_len));
nla->nla_len = nla_attr_size(payload);
*pos += nla_attr_size(copy_len);
nlmsg->nlmsg_len += nla->nla_len;
memset(dst + *pos, 0, payload - copy_len);
*pos += payload - copy_len;
return 0;
}
static int xfrm_xlate32_attr(void *dst, const struct nlattr *nla,
size_t *pos, size_t size,
struct netlink_ext_ack *extack)
{
int type = nla_type(nla);
u16 pol_len32, pol_len64;
int err;
if (type > XFRMA_MAX) {
BUILD_BUG_ON(XFRMA_MAX != XFRMA_IPTFS_PKT_SIZE);
NL_SET_ERR_MSG(extack, "Bad attribute");
return -EOPNOTSUPP;
}
type = array_index_nospec(type, XFRMA_MAX + 1);
if (nla_len(nla) < compat_policy[type].len) {
NL_SET_ERR_MSG(extack, "Attribute bad length");
return -EOPNOTSUPP;
}
pol_len32 = compat_policy[type].len;
pol_len64 = xfrma_policy[type].len;
/* XFRMA_SA and XFRMA_POLICY - need to know how-to translate */
if (pol_len32 != pol_len64) {
if (nla_len(nla) != compat_policy[type].len) {
NL_SET_ERR_MSG(extack, "Attribute bad length");
return -EOPNOTSUPP;
}
err = xfrm_attr_cpy32(dst, pos, nla, size, pol_len32, pol_len64);
if (err)
return err;
}
return xfrm_attr_cpy32(dst, pos, nla, size, nla_len(nla), nla_len(nla));
}
static int xfrm_xlate32(struct nlmsghdr *dst, const struct nlmsghdr *src,
struct nlattr *attrs[XFRMA_MAX+1],
size_t size, u8 type, int maxtype,
struct netlink_ext_ack *extack)
{
size_t pos;
int i;
memcpy(dst, src, NLMSG_HDRLEN);
dst->nlmsg_len = NLMSG_HDRLEN + xfrm_msg_min[type];
memset(nlmsg_data(dst), 0, xfrm_msg_min[type]);
switch (src->nlmsg_type) {
/* Compat message has the same layout as native */
case XFRM_MSG_DELSA:
case XFRM_MSG_GETSA:
case XFRM_MSG_DELPOLICY:
case XFRM_MSG_GETPOLICY:
case XFRM_MSG_FLUSHSA:
case XFRM_MSG_FLUSHPOLICY:
case XFRM_MSG_NEWAE:
case XFRM_MSG_GETAE:
case XFRM_MSG_REPORT:
case XFRM_MSG_MIGRATE:
case XFRM_MSG_MIGRATE_STATE:
case XFRM_MSG_NEWSADINFO:
case XFRM_MSG_GETSADINFO:
case XFRM_MSG_NEWSPDINFO:
case XFRM_MSG_GETSPDINFO:
case XFRM_MSG_MAPPING:
memcpy(nlmsg_data(dst), nlmsg_data(src), compat_msg_min[type]);
break;
/* 4 byte alignment for trailing u64 on native, but not on compat */
case XFRM_MSG_NEWSA:
case XFRM_MSG_NEWPOLICY:
case XFRM_MSG_UPDSA:
case XFRM_MSG_UPDPOLICY:
memcpy(nlmsg_data(dst), nlmsg_data(src), compat_msg_min[type]);
break;
case XFRM_MSG_EXPIRE: {
const struct compat_xfrm_user_expire *src_ue = nlmsg_data(src);
struct xfrm_user_expire *dst_ue = nlmsg_data(dst);
/* compat_xfrm_user_expire has 4-byte smaller state */
memcpy(dst_ue, src_ue, sizeof(src_ue->state));
dst_ue->hard = src_ue->hard;
break;
}
case XFRM_MSG_ACQUIRE: {
const struct compat_xfrm_user_acquire *src_ua = nlmsg_data(src);
struct xfrm_user_acquire *dst_ua = nlmsg_data(dst);
memcpy(dst_ua, src_ua, offsetof(struct compat_xfrm_user_acquire, aalgos));
dst_ua->aalgos = src_ua->aalgos;
dst_ua->ealgos = src_ua->ealgos;
dst_ua->calgos = src_ua->calgos;
dst_ua->seq = src_ua->seq;
break;
}
case XFRM_MSG_POLEXPIRE: {
const struct compat_xfrm_user_polexpire *src_upe = nlmsg_data(src);
struct xfrm_user_polexpire *dst_upe = nlmsg_data(dst);
/* compat_xfrm_user_polexpire has 4-byte smaller state */
memcpy(dst_upe, src_upe, sizeof(src_upe->pol));
dst_upe->hard = src_upe->hard;
break;
}
case XFRM_MSG_ALLOCSPI: {
const struct compat_xfrm_userspi_info *src_usi = nlmsg_data(src);
struct xfrm_userspi_info *dst_usi = nlmsg_data(dst);
/* compat_xfrm_user_polexpire has 4-byte smaller state */
memcpy(dst_usi, src_usi, sizeof(src_usi->info));
dst_usi->min = src_usi->min;
dst_usi->max = src_usi->max;
break;
}
default:
NL_SET_ERR_MSG(extack, "Unsupported message type");
return -EOPNOTSUPP;
}
pos = dst->nlmsg_len;
if (maxtype) {
/* attirbutes are xfrm_spdattr_type_t, not xfrm_attr_type_t */
WARN_ON_ONCE(src->nlmsg_type != XFRM_MSG_NEWSPDINFO);
for (i = 1; i <= maxtype; i++) {
int err;
if (!attrs[i])
continue;
/* just copy - no need for translation */
err = xfrm_attr_cpy32(dst, &pos, attrs[i], size,
nla_len(attrs[i]), nla_len(attrs[i]));
if (err)
return err;
}
return 0;
}
for (i = 1; i < XFRMA_MAX + 1; i++) {
int err;
if (i == XFRMA_PAD)
continue;
if (!attrs[i])
continue;
err = xfrm_xlate32_attr(dst, attrs[i], &pos, size, extack);
if (err)
return err;
}
return 0;
}
static struct nlmsghdr *xfrm_user_rcv_msg_compat(const struct nlmsghdr *h32,
int maxtype, const struct nla_policy *policy,
struct netlink_ext_ack *extack)
{
/* netlink_rcv_skb() checks if a message has full (struct nlmsghdr) */
u16 type = h32->nlmsg_type - XFRM_MSG_BASE;
struct nlattr *attrs[XFRMA_MAX+1];
struct nlmsghdr *h64;
size_t len;
int err;
BUILD_BUG_ON(ARRAY_SIZE(xfrm_msg_min) != ARRAY_SIZE(compat_msg_min));
if (type >= ARRAY_SIZE(xfrm_msg_min))
return ERR_PTR(-EINVAL);
/* Don't call parse: the message might have only nlmsg header */
if ((h32->nlmsg_type == XFRM_MSG_GETSA ||
h32->nlmsg_type == XFRM_MSG_GETPOLICY) &&
(h32->nlmsg_flags & NLM_F_DUMP))
return NULL;
err = nlmsg_parse_deprecated(h32, compat_msg_min[type], attrs,
maxtype ? : XFRMA_MAX, policy ? : compat_policy, extack);
if (err < 0)
return ERR_PTR(err);
len = xfrm_user_rcv_calculate_len64(h32, attrs, maxtype);
/* The message doesn't need translation */
if (len == nlmsg_len(h32))
return NULL;
len += NLMSG_HDRLEN;
h64 = kvmalloc(len, GFP_KERNEL);
if (!h64)
return ERR_PTR(-ENOMEM);
err = xfrm_xlate32(h64, h32, attrs, len, type, maxtype, extack);
if (err < 0) {
kvfree(h64);
return ERR_PTR(err);
}
return h64;
}
static int xfrm_user_policy_compat(u8 **pdata32, int optlen)
{
struct compat_xfrm_userpolicy_info *p = (void *)*pdata32;
u8 *src_templates, *dst_templates;
u8 *data64;
if (optlen < sizeof(*p))
return -EINVAL;
data64 = kmalloc_track_caller(optlen + 4, GFP_USER | __GFP_NOWARN);
if (!data64)
return -ENOMEM;
memcpy(data64, *pdata32, sizeof(*p));
memset(data64 + sizeof(*p), 0, 4);
src_templates = *pdata32 + sizeof(*p);
dst_templates = data64 + sizeof(*p) + 4;
memcpy(dst_templates, src_templates, optlen - sizeof(*p));
kfree(*pdata32);
*pdata32 = data64;
return 0;
}
static struct xfrm_translator xfrm_translator = {
.owner = THIS_MODULE,
.alloc_compat = xfrm_alloc_compat,
.rcv_msg_compat = xfrm_user_rcv_msg_compat,
.xlate_user_policy_sockptr = xfrm_user_policy_compat,
};
static int __init xfrm_compat_init(void)
{
return xfrm_register_translator(&xfrm_translator);
}
static void __exit xfrm_compat_exit(void)
{
xfrm_unregister_translator(&xfrm_translator);
}
module_init(xfrm_compat_init);
module_exit(xfrm_compat_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dmitry Safonov");
MODULE_DESCRIPTION("XFRM 32-bit compatibility layer");
+591
View File
@@ -0,0 +1,591 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* xfrm_device.c - IPsec device offloading code.
*
* Copyright (c) 2015 secunet Security Networks AG
*
* Author:
* Steffen Klassert <steffen.klassert@secunet.com>
*/
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <net/dst.h>
#include <net/gso.h>
#include <net/xfrm.h>
#include <linux/notifier.h>
#ifdef CONFIG_XFRM_OFFLOAD
static void __xfrm_transport_prep(struct xfrm_state *x, struct sk_buff *skb,
unsigned int hsize)
{
struct xfrm_offload *xo = xfrm_offload(skb);
skb_reset_mac_len(skb);
if (xo->flags & XFRM_GSO_SEGMENT)
skb->transport_header -= x->props.header_len;
pskb_pull(skb, skb_transport_offset(skb) + x->props.header_len);
}
static void __xfrm_mode_tunnel_prep(struct xfrm_state *x, struct sk_buff *skb,
unsigned int hsize)
{
struct xfrm_offload *xo = xfrm_offload(skb);
if (xo->flags & XFRM_GSO_SEGMENT)
skb->transport_header = skb->network_header + hsize;
skb_reset_mac_len(skb);
pskb_pull(skb,
skb->mac_len + x->props.header_len - x->props.enc_hdr_len);
}
static void __xfrm_mode_beet_prep(struct xfrm_state *x, struct sk_buff *skb,
unsigned int hsize)
{
struct xfrm_offload *xo = xfrm_offload(skb);
int phlen = 0;
if (xo->flags & XFRM_GSO_SEGMENT)
skb->transport_header = skb->network_header + hsize;
skb_reset_mac_len(skb);
if (x->sel.family != AF_INET6) {
phlen = IPV4_BEET_PHMAXLEN;
if (x->outer_mode.family == AF_INET6)
phlen += sizeof(struct ipv6hdr) - sizeof(struct iphdr);
}
pskb_pull(skb, skb->mac_len + hsize + (x->props.header_len - phlen));
}
/* Adjust pointers into the packet when IPsec is done at layer2 */
static void xfrm_outer_mode_prep(struct xfrm_state *x, struct sk_buff *skb)
{
switch (x->outer_mode.encap) {
case XFRM_MODE_IPTFS:
case XFRM_MODE_TUNNEL:
if (x->outer_mode.family == AF_INET)
return __xfrm_mode_tunnel_prep(x, skb,
sizeof(struct iphdr));
if (x->outer_mode.family == AF_INET6)
return __xfrm_mode_tunnel_prep(x, skb,
sizeof(struct ipv6hdr));
break;
case XFRM_MODE_TRANSPORT:
if (x->outer_mode.family == AF_INET)
return __xfrm_transport_prep(x, skb,
sizeof(struct iphdr));
if (x->outer_mode.family == AF_INET6)
return __xfrm_transport_prep(x, skb,
sizeof(struct ipv6hdr));
break;
case XFRM_MODE_BEET:
if (x->outer_mode.family == AF_INET)
return __xfrm_mode_beet_prep(x, skb,
sizeof(struct iphdr));
if (x->outer_mode.family == AF_INET6)
return __xfrm_mode_beet_prep(x, skb,
sizeof(struct ipv6hdr));
break;
case XFRM_MODE_ROUTEOPTIMIZATION:
case XFRM_MODE_IN_TRIGGER:
break;
}
}
static inline bool xmit_xfrm_check_overflow(struct sk_buff *skb)
{
struct xfrm_offload *xo = xfrm_offload(skb);
__u32 seq = xo->seq.low;
seq += skb_shinfo(skb)->gso_segs;
if (unlikely(seq < xo->seq.low))
return true;
return false;
}
struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t features, bool *again)
{
int err;
unsigned long flags;
struct xfrm_state *x;
struct softnet_data *sd;
struct sk_buff *skb2, *nskb, *pskb = NULL;
netdev_features_t esp_features = features;
struct xfrm_offload *xo = xfrm_offload(skb);
struct net_device *dev = skb->dev;
struct sec_path *sp;
if (!xo || (xo->flags & XFRM_XMIT))
return skb;
if (!(features & NETIF_F_HW_ESP))
esp_features = features & ~(NETIF_F_SG | NETIF_F_CSUM_MASK);
sp = skb_sec_path(skb);
x = sp->xvec[sp->len - 1];
if (xo->flags & XFRM_GRO || x->xso.dir == XFRM_DEV_OFFLOAD_IN)
return skb;
/* The packet was sent to HW IPsec packet offload engine,
* but to wrong device. Drop the packet, so it won't skip
* XFRM stack.
*/
if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET && x->xso.dev != dev) {
kfree_skb(skb);
dev_core_stats_tx_dropped_inc(dev);
return NULL;
}
local_irq_save(flags);
sd = this_cpu_ptr(&softnet_data);
err = !skb_queue_empty(&sd->xfrm_backlog);
local_irq_restore(flags);
if (err) {
*again = true;
return skb;
}
if (skb_is_gso(skb) && (unlikely(x->xso.dev != dev) ||
unlikely(xmit_xfrm_check_overflow(skb)))) {
struct sk_buff *segs;
/* Packet got rerouted, fixup features and segment it. */
esp_features = esp_features & ~(NETIF_F_HW_ESP | NETIF_F_GSO_ESP);
segs = skb_gso_segment(skb, esp_features);
if (IS_ERR(segs)) {
kfree_skb(skb);
dev_core_stats_tx_dropped_inc(dev);
return NULL;
} else {
consume_skb(skb);
skb = segs;
}
}
if (!skb->next) {
esp_features |= skb->dev->gso_partial_features;
xfrm_outer_mode_prep(x, skb);
xo->flags |= XFRM_DEV_RESUME;
err = x->type_offload->xmit(x, skb, esp_features);
if (err) {
if (err == -EINPROGRESS)
return ERR_PTR(-EINPROGRESS);
XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
kfree_skb(skb);
return NULL;
}
skb_push(skb, skb->data - skb_mac_header(skb));
return skb;
}
skb_list_walk_safe(skb, skb2, nskb) {
esp_features |= skb->dev->gso_partial_features;
skb_mark_not_on_list(skb2);
xo = xfrm_offload(skb2);
xo->flags |= XFRM_DEV_RESUME;
xfrm_outer_mode_prep(x, skb2);
err = x->type_offload->xmit(x, skb2, esp_features);
if (!err) {
skb2->next = nskb;
} else if (err != -EINPROGRESS) {
XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
skb2->next = nskb;
kfree_skb_list(skb2);
return NULL;
} else {
if (skb == skb2)
skb = nskb;
else
pskb->next = nskb;
continue;
}
skb_push(skb2, skb2->data - skb_mac_header(skb2));
pskb = skb2;
}
/* skb_gso_segment() set skb->prev to the last segment, but async
* crypto may have stolen it above without updating ->prev. Repoint
* it at the last retained segment so validate_xmit_skb_list() does
* not chain onto a segment now owned by the crypto engine.
*/
if (skb)
skb->prev = pskb;
return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
const struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack)
{
int err;
struct dst_entry *dst;
struct net_device *dev;
struct xfrm_dev_offload *xso = &x->xso;
xfrm_address_t *saddr;
xfrm_address_t *daddr;
bool is_packet_offload;
if (xuo->flags &
~(XFRM_OFFLOAD_IPV6 | XFRM_OFFLOAD_INBOUND | XFRM_OFFLOAD_PACKET)) {
NL_SET_ERR_MSG(extack, "Unrecognized flags in offload request");
return -EINVAL;
}
if ((xuo->flags & XFRM_OFFLOAD_INBOUND && x->dir == XFRM_SA_DIR_OUT) ||
(!(xuo->flags & XFRM_OFFLOAD_INBOUND) && x->dir == XFRM_SA_DIR_IN)) {
NL_SET_ERR_MSG(extack, "Mismatched SA and offload direction");
return -EINVAL;
}
if (xuo->flags & XFRM_OFFLOAD_INBOUND && x->if_id) {
NL_SET_ERR_MSG(extack, "XFRM if_id is not supported in RX path");
return -EINVAL;
}
is_packet_offload = xuo->flags & XFRM_OFFLOAD_PACKET;
/* We don't yet support TFC padding. */
if (x->tfcpad) {
NL_SET_ERR_MSG(extack, "TFC padding can't be offloaded");
return -EINVAL;
}
dev = dev_get_by_index(net, xuo->ifindex);
if (!dev) {
struct xfrm_dst_lookup_params params;
if (!(xuo->flags & XFRM_OFFLOAD_INBOUND)) {
saddr = &x->props.saddr;
daddr = &x->id.daddr;
} else {
saddr = &x->id.daddr;
daddr = &x->props.saddr;
}
memset(&params, 0, sizeof(params));
params.net = net;
params.saddr = saddr;
params.daddr = daddr;
params.mark = xfrm_smark_get(0, x);
dst = __xfrm_dst_lookup(x->props.family, &params);
if (IS_ERR(dst))
return (is_packet_offload) ? -EINVAL : 0;
dev = dst->dev;
dev_hold(dev);
dst_release(dst);
}
if (!dev->xfrmdev_ops || !dev->xfrmdev_ops->xdo_dev_state_add) {
xso->dev = NULL;
dev_put(dev);
return (is_packet_offload) ? -EINVAL : 0;
}
if (!is_packet_offload && x->props.flags & XFRM_STATE_ESN &&
!dev->xfrmdev_ops->xdo_dev_state_advance_esn) {
NL_SET_ERR_MSG(extack, "Device doesn't support offload with ESN");
xso->dev = NULL;
dev_put(dev);
return -EINVAL;
}
if (!x->type_offload) {
NL_SET_ERR_MSG(extack, "Type doesn't support offload");
dev_put(dev);
return -EINVAL;
}
xso->dev = dev;
xso->ifindex = dev->ifindex;
netdev_tracker_alloc(dev, &xso->dev_tracker, GFP_ATOMIC);
if (xuo->flags & XFRM_OFFLOAD_INBOUND)
xso->dir = XFRM_DEV_OFFLOAD_IN;
else
xso->dir = XFRM_DEV_OFFLOAD_OUT;
if (is_packet_offload)
xso->type = XFRM_DEV_OFFLOAD_PACKET;
else
xso->type = XFRM_DEV_OFFLOAD_CRYPTO;
err = dev->xfrmdev_ops->xdo_dev_state_add(dev, x, extack);
if (err) {
xso->dev = NULL;
xso->dir = 0;
netdev_put(dev, &xso->dev_tracker);
xso->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
xfrm_unset_type_offload(x);
/* User explicitly requested packet offload mode and configured
* policy in addition to the XFRM state. So be civil to users,
* and return an error instead of taking fallback path.
*/
if ((err != -EOPNOTSUPP && !is_packet_offload) || is_packet_offload) {
NL_SET_ERR_MSG_WEAK(extack, "Device failed to offload this state");
return err;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(xfrm_dev_state_add);
int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp,
struct xfrm_user_offload *xuo, u8 dir,
struct netlink_ext_ack *extack)
{
struct xfrm_dev_offload *xdo = &xp->xdo;
struct net_device *dev;
int err;
if (!xuo->flags || xuo->flags & ~XFRM_OFFLOAD_PACKET) {
/* We support only packet offload mode and it means
* that user must set XFRM_OFFLOAD_PACKET bit.
*/
NL_SET_ERR_MSG(extack, "Unrecognized flags in offload request");
return -EINVAL;
}
dev = dev_get_by_index(net, xuo->ifindex);
if (!dev)
return -EINVAL;
if (!dev->xfrmdev_ops || !dev->xfrmdev_ops->xdo_dev_policy_add) {
xdo->dev = NULL;
dev_put(dev);
NL_SET_ERR_MSG(extack, "Policy offload is not supported");
return -EINVAL;
}
xdo->dev = dev;
netdev_tracker_alloc(dev, &xdo->dev_tracker, GFP_ATOMIC);
xdo->type = XFRM_DEV_OFFLOAD_PACKET;
switch (dir) {
case XFRM_POLICY_IN:
xdo->dir = XFRM_DEV_OFFLOAD_IN;
break;
case XFRM_POLICY_OUT:
xdo->dir = XFRM_DEV_OFFLOAD_OUT;
break;
case XFRM_POLICY_FWD:
xdo->dir = XFRM_DEV_OFFLOAD_FWD;
break;
default:
xdo->dev = NULL;
netdev_put(dev, &xdo->dev_tracker);
NL_SET_ERR_MSG(extack, "Unrecognized offload direction");
return -EINVAL;
}
err = dev->xfrmdev_ops->xdo_dev_policy_add(xp, extack);
if (err) {
xdo->dev = NULL;
xdo->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
xdo->dir = 0;
netdev_put(dev, &xdo->dev_tracker);
NL_SET_ERR_MSG_WEAK(extack, "Device failed to offload this policy");
return err;
}
return 0;
}
EXPORT_SYMBOL_GPL(xfrm_dev_policy_add);
bool xfrm_dev_offload_ok(struct sk_buff *skb, struct xfrm_state *x)
{
int mtu;
struct dst_entry *dst = skb_dst(skb);
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
struct net_device *dev = x->xso.dev;
bool check_tunnel_size;
if (!x->type_offload ||
(x->xso.type == XFRM_DEV_OFFLOAD_UNSPECIFIED && x->encap))
return false;
if ((!dev || dev == xfrm_dst_path(dst)->dev) &&
!xdst->child->xfrm) {
mtu = xfrm_state_mtu(x, xdst->child_mtu_cached);
if (skb->len <= mtu)
goto ok;
if (skb_is_gso(skb) && skb_gso_validate_network_len(skb, mtu))
goto ok;
}
return false;
ok:
if (!dev)
return true;
check_tunnel_size = x->xso.type == XFRM_DEV_OFFLOAD_PACKET &&
x->props.mode == XFRM_MODE_TUNNEL;
switch (skb_dst(skb)->ops->family) {
case AF_INET:
/* Check for IPv4 options */
if (ip_hdr(skb)->ihl != 5)
return false;
if (check_tunnel_size && xfrm4_tunnel_check_size(skb))
return false;
break;
case AF_INET6:
/* Check for IPv6 extensions */
if (ipv6_ext_hdr(ipv6_hdr(skb)->nexthdr))
return false;
if (check_tunnel_size && xfrm6_tunnel_check_size(skb))
return false;
break;
default:
break;
}
if (dev->xfrmdev_ops->xdo_dev_offload_ok)
return dev->xfrmdev_ops->xdo_dev_offload_ok(skb, x);
return true;
}
EXPORT_SYMBOL_GPL(xfrm_dev_offload_ok);
void xfrm_dev_resume(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
int ret = NETDEV_TX_BUSY;
struct netdev_queue *txq;
struct softnet_data *sd;
unsigned long flags;
rcu_read_lock();
txq = netdev_core_pick_tx(dev, skb, NULL);
HARD_TX_LOCK(dev, txq, smp_processor_id());
if (!netif_xmit_frozen_or_stopped(txq))
skb = dev_hard_start_xmit(skb, dev, txq, &ret);
HARD_TX_UNLOCK(dev, txq);
if (!dev_xmit_complete(ret)) {
local_irq_save(flags);
sd = this_cpu_ptr(&softnet_data);
skb_queue_tail(&sd->xfrm_backlog, skb);
raise_softirq_irqoff(NET_TX_SOFTIRQ);
local_irq_restore(flags);
}
rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(xfrm_dev_resume);
void xfrm_dev_backlog(struct softnet_data *sd)
{
struct sk_buff_head *xfrm_backlog = &sd->xfrm_backlog;
struct sk_buff_head list;
struct sk_buff *skb;
if (skb_queue_empty(xfrm_backlog))
return;
__skb_queue_head_init(&list);
spin_lock(&xfrm_backlog->lock);
skb_queue_splice_init(xfrm_backlog, &list);
spin_unlock(&xfrm_backlog->lock);
while (!skb_queue_empty(&list)) {
skb = __skb_dequeue(&list);
xfrm_dev_resume(skb);
}
}
#endif
static int xfrm_api_check(struct net_device *dev)
{
#ifdef CONFIG_XFRM_OFFLOAD
if ((dev->features & NETIF_F_HW_ESP_TX_CSUM) &&
!(dev->features & NETIF_F_HW_ESP))
return NOTIFY_BAD;
if ((dev->features & NETIF_F_HW_ESP) &&
(!(dev->xfrmdev_ops &&
dev->xfrmdev_ops->xdo_dev_state_add &&
dev->xfrmdev_ops->xdo_dev_state_delete)))
return NOTIFY_BAD;
#else
if (dev->features & (NETIF_F_HW_ESP | NETIF_F_HW_ESP_TX_CSUM))
return NOTIFY_BAD;
#endif
return NOTIFY_DONE;
}
static int xfrm_dev_down(struct net_device *dev)
{
if (dev->features & NETIF_F_HW_ESP) {
xfrm_dev_state_flush(dev_net(dev), dev, true);
xfrm_dev_policy_flush(dev_net(dev), dev, true);
}
return NOTIFY_DONE;
}
static int xfrm_dev_unregister(struct net_device *dev)
{
xfrm_dev_state_flush(dev_net(dev), dev, true);
xfrm_dev_policy_flush(dev_net(dev), dev, true);
return NOTIFY_DONE;
}
static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
switch (event) {
case NETDEV_REGISTER:
return xfrm_api_check(dev);
case NETDEV_FEAT_CHANGE:
return xfrm_api_check(dev);
case NETDEV_DOWN:
return xfrm_dev_down(dev);
case NETDEV_UNREGISTER:
return xfrm_dev_unregister(dev);
}
return NOTIFY_DONE;
}
static struct notifier_block xfrm_dev_notifier = {
.notifier_call = xfrm_dev_event,
};
void __init xfrm_dev_init(void)
{
register_netdevice_notifier(&xfrm_dev_notifier);
}
+40
View File
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0
/* xfrm_hash.c: Common hash table code.
*
* Copyright (C) 2006 David S. Miller (davem@davemloft.net)
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/memblock.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/xfrm.h>
#include "xfrm_hash.h"
struct hlist_head *xfrm_hash_alloc(unsigned int sz)
{
struct hlist_head *n;
if (sz <= PAGE_SIZE)
n = kzalloc(sz, GFP_KERNEL);
else if (hashdist)
n = vzalloc(sz);
else
n = (struct hlist_head *)
__get_free_pages(GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO,
get_order(sz));
return n;
}
void xfrm_hash_free(struct hlist_head *n, unsigned int sz)
{
if (sz <= PAGE_SIZE)
kfree(n);
else if (hashdist)
vfree(n);
else
free_pages((unsigned long)n, get_order(sz));
}
+199
View File
@@ -0,0 +1,199 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _XFRM_HASH_H
#define _XFRM_HASH_H
#include <linux/xfrm.h>
#include <linux/socket.h>
#include <linux/jhash.h>
static inline unsigned int __xfrm4_addr_hash(const xfrm_address_t *addr)
{
return ntohl(addr->a4);
}
static inline unsigned int __xfrm6_addr_hash(const xfrm_address_t *addr)
{
return jhash2((__force u32 *)addr->a6, 4, 0);
}
static inline unsigned int __xfrm4_daddr_saddr_hash(const xfrm_address_t *daddr,
const xfrm_address_t *saddr)
{
u32 sum = (__force u32)daddr->a4 + (__force u32)saddr->a4;
return ntohl((__force __be32)sum);
}
static inline unsigned int __xfrm6_daddr_saddr_hash(const xfrm_address_t *daddr,
const xfrm_address_t *saddr)
{
return __xfrm6_addr_hash(daddr) ^ __xfrm6_addr_hash(saddr);
}
static inline u32 __bits2mask32(__u8 bits)
{
u32 mask32 = 0xffffffff;
if (bits == 0)
mask32 = 0;
else if (bits < 32)
mask32 <<= (32 - bits);
return mask32;
}
static inline unsigned int __xfrm4_dpref_spref_hash(const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
__u8 dbits,
__u8 sbits)
{
return jhash_2words(ntohl(daddr->a4) & __bits2mask32(dbits),
ntohl(saddr->a4) & __bits2mask32(sbits),
0);
}
static inline unsigned int __xfrm6_pref_hash(const xfrm_address_t *addr,
__u8 prefixlen)
{
unsigned int pdw;
unsigned int pbi;
u32 initval = 0;
pdw = prefixlen >> 5; /* num of whole u32 in prefix */
pbi = prefixlen & 0x1f; /* num of bits in incomplete u32 in prefix */
if (pbi) {
__be32 mask;
mask = htonl((0xffffffff) << (32 - pbi));
initval = (__force u32)(addr->a6[pdw] & mask);
}
return jhash2((__force u32 *)addr->a6, pdw, initval);
}
static inline unsigned int __xfrm6_dpref_spref_hash(const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
__u8 dbits,
__u8 sbits)
{
return __xfrm6_pref_hash(daddr, dbits) ^
__xfrm6_pref_hash(saddr, sbits);
}
static inline unsigned int __xfrm_dst_hash(const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
u32 reqid, unsigned short family,
unsigned int hmask)
{
unsigned int h = family ^ reqid;
switch (family) {
case AF_INET:
h ^= __xfrm4_daddr_saddr_hash(daddr, saddr);
break;
case AF_INET6:
h ^= __xfrm6_daddr_saddr_hash(daddr, saddr);
break;
}
return (h ^ (h >> 16)) & hmask;
}
static inline unsigned int __xfrm_src_hash(const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
unsigned short family,
unsigned int hmask)
{
unsigned int h = family;
switch (family) {
case AF_INET:
h ^= __xfrm4_daddr_saddr_hash(daddr, saddr);
break;
case AF_INET6:
h ^= __xfrm6_daddr_saddr_hash(daddr, saddr);
break;
}
return (h ^ (h >> 16)) & hmask;
}
static inline unsigned int
__xfrm_spi_hash(const xfrm_address_t *daddr, __be32 spi, u8 proto,
unsigned short family, unsigned int hmask)
{
unsigned int h = (__force u32)spi ^ proto;
switch (family) {
case AF_INET:
h ^= __xfrm4_addr_hash(daddr);
break;
case AF_INET6:
h ^= __xfrm6_addr_hash(daddr);
break;
}
return (h ^ (h >> 10) ^ (h >> 20)) & hmask;
}
static inline unsigned int
__xfrm_seq_hash(u32 seq, unsigned int hmask)
{
unsigned int h = seq;
return (h ^ (h >> 10) ^ (h >> 20)) & hmask;
}
static inline unsigned int __idx_hash(u32 index, unsigned int hmask)
{
return (index ^ (index >> 8)) & hmask;
}
static inline unsigned int __sel_hash(const struct xfrm_selector *sel,
unsigned short family, unsigned int hmask,
u8 dbits, u8 sbits)
{
const xfrm_address_t *daddr = &sel->daddr;
const xfrm_address_t *saddr = &sel->saddr;
unsigned int h = 0;
switch (family) {
case AF_INET:
if (sel->prefixlen_d < dbits ||
sel->prefixlen_s < sbits)
return hmask + 1;
h = __xfrm4_dpref_spref_hash(daddr, saddr, dbits, sbits);
break;
case AF_INET6:
if (sel->prefixlen_d < dbits ||
sel->prefixlen_s < sbits)
return hmask + 1;
h = __xfrm6_dpref_spref_hash(daddr, saddr, dbits, sbits);
break;
}
h ^= (h >> 16);
return h & hmask;
}
static inline unsigned int __addr_hash(const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
unsigned short family,
unsigned int hmask,
u8 dbits, u8 sbits)
{
unsigned int h = 0;
switch (family) {
case AF_INET:
h = __xfrm4_dpref_spref_hash(daddr, saddr, dbits, sbits);
break;
case AF_INET6:
h = __xfrm6_dpref_spref_hash(daddr, saddr, dbits, sbits);
break;
}
h ^= (h >> 16);
return h & hmask;
}
struct hlist_head *xfrm_hash_alloc(unsigned int sz);
void xfrm_hash_free(struct hlist_head *n, unsigned int sz);
#endif /* _XFRM_HASH_H */
+70
View File
@@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/ipv6.h>
#include <net/dsfield.h>
#include <net/xfrm.h>
#ifndef XFRM_INOUT_H
#define XFRM_INOUT_H 1
static inline void xfrm4_extract_header(struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
XFRM_MODE_SKB_CB(skb)->ihl = sizeof(*iph);
XFRM_MODE_SKB_CB(skb)->id = iph->id;
XFRM_MODE_SKB_CB(skb)->frag_off = iph->frag_off;
XFRM_MODE_SKB_CB(skb)->tos = iph->tos;
XFRM_MODE_SKB_CB(skb)->ttl = iph->ttl;
XFRM_MODE_SKB_CB(skb)->optlen = iph->ihl * 4 - sizeof(*iph);
memset(XFRM_MODE_SKB_CB(skb)->flow_lbl, 0,
sizeof(XFRM_MODE_SKB_CB(skb)->flow_lbl));
}
static inline void xfrm6_extract_header(struct sk_buff *skb)
{
#if IS_ENABLED(CONFIG_IPV6)
struct ipv6hdr *iph = ipv6_hdr(skb);
XFRM_MODE_SKB_CB(skb)->ihl = sizeof(*iph);
XFRM_MODE_SKB_CB(skb)->id = 0;
XFRM_MODE_SKB_CB(skb)->frag_off = htons(IP_DF);
XFRM_MODE_SKB_CB(skb)->tos = ipv6_get_dsfield(iph);
XFRM_MODE_SKB_CB(skb)->ttl = iph->hop_limit;
XFRM_MODE_SKB_CB(skb)->optlen = 0;
memcpy(XFRM_MODE_SKB_CB(skb)->flow_lbl, iph->flow_lbl,
sizeof(XFRM_MODE_SKB_CB(skb)->flow_lbl));
#else
WARN_ON_ONCE(1);
#endif
}
static inline void xfrm6_beet_make_header(struct sk_buff *skb)
{
struct ipv6hdr *iph = ipv6_hdr(skb);
iph->version = 6;
memcpy(iph->flow_lbl, XFRM_MODE_SKB_CB(skb)->flow_lbl,
sizeof(iph->flow_lbl));
iph->nexthdr = XFRM_MODE_SKB_CB(skb)->protocol;
ipv6_change_dsfield(iph, 0, XFRM_MODE_SKB_CB(skb)->tos);
iph->hop_limit = XFRM_MODE_SKB_CB(skb)->ttl;
}
static inline void xfrm4_beet_make_header(struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
iph->ihl = 5;
iph->version = 4;
iph->protocol = XFRM_MODE_SKB_CB(skb)->protocol;
iph->tos = XFRM_MODE_SKB_CB(skb)->tos;
iph->id = XFRM_MODE_SKB_CB(skb)->id;
iph->frag_off = XFRM_MODE_SKB_CB(skb)->frag_off;
iph->ttl = XFRM_MODE_SKB_CB(skb)->ttl;
}
#endif
+869
View File
@@ -0,0 +1,869 @@
// SPDX-License-Identifier: GPL-2.0
/*
* xfrm_input.c
*
* Changes:
* YOSHIFUJI Hideaki @USAGI
* Split up af-specific portion
*
*/
#include <linux/bottom_half.h>
#include <linux/cache.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/percpu.h>
#include <net/dst.h>
#include <net/ip.h>
#include <net/xfrm.h>
#include <net/ip_tunnels.h>
#include <net/ip6_tunnel.h>
#include <net/dst_metadata.h>
#include <net/hotdata.h>
#include "xfrm_inout.h"
struct xfrm_trans_tasklet {
struct work_struct work;
spinlock_t queue_lock;
struct sk_buff_head queue;
};
struct xfrm_trans_cb {
union {
struct inet_skb_parm h4;
#if IS_ENABLED(CONFIG_IPV6)
struct inet6_skb_parm h6;
#endif
} header;
int (*finish)(struct net *net, struct sock *sk, struct sk_buff *skb);
struct net *net;
};
#define XFRM_TRANS_SKB_CB(__skb) ((struct xfrm_trans_cb *)&((__skb)->cb[0]))
static DEFINE_SPINLOCK(xfrm_input_afinfo_lock);
static struct xfrm_input_afinfo const __rcu *xfrm_input_afinfo[2][AF_INET6 + 1];
static struct gro_cells gro_cells;
static struct net_device *xfrm_napi_dev;
static DEFINE_PER_CPU(struct xfrm_trans_tasklet, xfrm_trans_tasklet);
int xfrm_input_register_afinfo(const struct xfrm_input_afinfo *afinfo)
{
int err = 0;
if (WARN_ON(afinfo->family > AF_INET6))
return -EAFNOSUPPORT;
spin_lock_bh(&xfrm_input_afinfo_lock);
if (unlikely(xfrm_input_afinfo[afinfo->is_ipip][afinfo->family]))
err = -EEXIST;
else
rcu_assign_pointer(xfrm_input_afinfo[afinfo->is_ipip][afinfo->family], afinfo);
spin_unlock_bh(&xfrm_input_afinfo_lock);
return err;
}
EXPORT_SYMBOL(xfrm_input_register_afinfo);
int xfrm_input_unregister_afinfo(const struct xfrm_input_afinfo *afinfo)
{
int err = 0;
spin_lock_bh(&xfrm_input_afinfo_lock);
if (likely(xfrm_input_afinfo[afinfo->is_ipip][afinfo->family])) {
const struct xfrm_input_afinfo *cur;
cur = rcu_access_pointer(xfrm_input_afinfo[afinfo->is_ipip][afinfo->family]);
if (unlikely(cur != afinfo))
err = -EINVAL;
else
RCU_INIT_POINTER(xfrm_input_afinfo[afinfo->is_ipip][afinfo->family], NULL);
}
spin_unlock_bh(&xfrm_input_afinfo_lock);
synchronize_rcu();
return err;
}
EXPORT_SYMBOL(xfrm_input_unregister_afinfo);
static const struct xfrm_input_afinfo *xfrm_input_get_afinfo(u8 family, bool is_ipip)
{
const struct xfrm_input_afinfo *afinfo;
if (WARN_ON_ONCE(family > AF_INET6))
return NULL;
rcu_read_lock();
afinfo = rcu_dereference(xfrm_input_afinfo[is_ipip][family]);
if (unlikely(!afinfo))
rcu_read_unlock();
return afinfo;
}
static int xfrm_rcv_cb(struct sk_buff *skb, unsigned int family, u8 protocol,
int err)
{
bool is_ipip = (protocol == IPPROTO_IPIP || protocol == IPPROTO_IPV6);
const struct xfrm_input_afinfo *afinfo;
int ret;
afinfo = xfrm_input_get_afinfo(family, is_ipip);
if (!afinfo)
return -EAFNOSUPPORT;
ret = afinfo->callback(skb, protocol, err);
rcu_read_unlock();
return ret;
}
struct sec_path *secpath_set(struct sk_buff *skb)
{
struct sec_path *sp, *tmp = skb_ext_find(skb, SKB_EXT_SEC_PATH);
sp = skb_ext_add(skb, SKB_EXT_SEC_PATH);
if (!sp)
return NULL;
if (tmp) /* reused existing one (was COW'd if needed) */
return sp;
/* allocated new secpath */
memset(sp->ovec, 0, sizeof(sp->ovec));
sp->olen = 0;
sp->len = 0;
sp->verified_cnt = 0;
return sp;
}
EXPORT_SYMBOL(secpath_set);
/* Fetch spi and seq from ipsec header */
int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)
{
int offset, offset_seq;
int hlen;
switch (nexthdr) {
case IPPROTO_AH:
hlen = sizeof(struct ip_auth_hdr);
offset = offsetof(struct ip_auth_hdr, spi);
offset_seq = offsetof(struct ip_auth_hdr, seq_no);
break;
case IPPROTO_ESP:
hlen = sizeof(struct ip_esp_hdr);
offset = offsetof(struct ip_esp_hdr, spi);
offset_seq = offsetof(struct ip_esp_hdr, seq_no);
break;
case IPPROTO_COMP:
if (!pskb_may_pull(skb, sizeof(struct ip_comp_hdr)))
return -EINVAL;
*spi = htonl(ntohs(*(__be16 *)(skb_transport_header(skb) + 2)));
*seq = 0;
return 0;
default:
return 1;
}
if (!pskb_may_pull(skb, hlen))
return -EINVAL;
*spi = *(__be32 *)(skb_transport_header(skb) + offset);
*seq = *(__be32 *)(skb_transport_header(skb) + offset_seq);
return 0;
}
EXPORT_SYMBOL(xfrm_parse_spi);
static int xfrm4_remove_beet_encap(struct xfrm_state *x, struct sk_buff *skb)
{
struct iphdr *iph;
int optlen = 0;
int err = -EINVAL;
skb->protocol = htons(ETH_P_IP);
if (unlikely(XFRM_MODE_SKB_CB(skb)->protocol == IPPROTO_BEETPH)) {
struct ip_beet_phdr *ph;
int phlen;
if (!pskb_may_pull(skb, sizeof(*ph)))
goto out;
ph = (struct ip_beet_phdr *)skb->data;
phlen = sizeof(*ph) + ph->padlen;
optlen = ph->hdrlen * 8 + (IPV4_BEET_PHMAXLEN - phlen);
if (optlen < 0 || optlen & 3 || optlen > 250)
goto out;
XFRM_MODE_SKB_CB(skb)->protocol = ph->nexthdr;
if (!pskb_may_pull(skb, phlen))
goto out;
__skb_pull(skb, phlen);
}
skb_push(skb, sizeof(*iph));
skb_reset_network_header(skb);
skb_mac_header_rebuild(skb);
xfrm4_beet_make_header(skb);
iph = ip_hdr(skb);
iph->ihl += optlen / 4;
iph->tot_len = htons(skb->len);
iph->daddr = x->sel.daddr.a4;
iph->saddr = x->sel.saddr.a4;
iph->check = 0;
iph->check = ip_fast_csum(skb_network_header(skb), iph->ihl);
err = 0;
out:
return err;
}
static void ipip_ecn_decapsulate(struct sk_buff *skb)
{
struct iphdr *inner_iph = ipip_hdr(skb);
if (INET_ECN_is_ce(XFRM_MODE_SKB_CB(skb)->tos))
IP_ECN_set_ce(inner_iph);
}
static int xfrm4_remove_tunnel_encap(struct xfrm_state *x, struct sk_buff *skb)
{
int err = -EINVAL;
skb->protocol = htons(ETH_P_IP);
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto out;
err = skb_unclone(skb, GFP_ATOMIC);
if (err)
goto out;
if (x->props.flags & XFRM_STATE_DECAP_DSCP)
ipv4_copy_dscp(XFRM_MODE_SKB_CB(skb)->tos, ipip_hdr(skb));
if (!(x->props.flags & XFRM_STATE_NOECN))
ipip_ecn_decapsulate(skb);
skb_reset_network_header(skb);
skb_mac_header_rebuild(skb);
if (skb->mac_len)
eth_hdr(skb)->h_proto = skb->protocol;
err = 0;
out:
return err;
}
static void ipip6_ecn_decapsulate(struct sk_buff *skb)
{
struct ipv6hdr *inner_iph = ipipv6_hdr(skb);
if (INET_ECN_is_ce(XFRM_MODE_SKB_CB(skb)->tos))
IP6_ECN_set_ce(skb, inner_iph);
}
static int xfrm6_remove_tunnel_encap(struct xfrm_state *x, struct sk_buff *skb)
{
int err = -EINVAL;
skb->protocol = htons(ETH_P_IPV6);
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
goto out;
err = skb_unclone(skb, GFP_ATOMIC);
if (err)
goto out;
if (x->props.flags & XFRM_STATE_DECAP_DSCP)
ipv6_copy_dscp(XFRM_MODE_SKB_CB(skb)->tos, ipipv6_hdr(skb));
if (!(x->props.flags & XFRM_STATE_NOECN))
ipip6_ecn_decapsulate(skb);
skb_reset_network_header(skb);
skb_mac_header_rebuild(skb);
if (skb->mac_len)
eth_hdr(skb)->h_proto = skb->protocol;
err = 0;
out:
return err;
}
static int xfrm6_remove_beet_encap(struct xfrm_state *x, struct sk_buff *skb)
{
struct ipv6hdr *ip6h;
int size = sizeof(struct ipv6hdr);
int err;
skb->protocol = htons(ETH_P_IPV6);
err = skb_cow_head(skb, size + skb->mac_len);
if (err)
goto out;
__skb_push(skb, size);
skb_reset_network_header(skb);
skb_mac_header_rebuild(skb);
xfrm6_beet_make_header(skb);
ip6h = ipv6_hdr(skb);
ip6h->payload_len = htons(skb->len - size);
ip6h->daddr = x->sel.daddr.in6;
ip6h->saddr = x->sel.saddr.in6;
err = 0;
out:
return err;
}
/* Remove encapsulation header.
*
* The IP header will be moved over the top of the encapsulation
* header.
*
* On entry, the transport header shall point to where the IP header
* should be and the network header shall be set to where the IP
* header currently is. skb->data shall point to the start of the
* payload.
*/
static int
xfrm_inner_mode_encap_remove(struct xfrm_state *x,
struct sk_buff *skb)
{
switch (x->props.mode) {
case XFRM_MODE_BEET:
switch (x->sel.family) {
case AF_INET:
return xfrm4_remove_beet_encap(x, skb);
case AF_INET6:
return xfrm6_remove_beet_encap(x, skb);
}
break;
case XFRM_MODE_TUNNEL:
switch (XFRM_MODE_SKB_CB(skb)->protocol) {
case IPPROTO_IPIP:
return xfrm4_remove_tunnel_encap(x, skb);
case IPPROTO_IPV6:
return xfrm6_remove_tunnel_encap(x, skb);
break;
}
return -EINVAL;
}
WARN_ON_ONCE(1);
return -EOPNOTSUPP;
}
static int xfrm_prepare_input(struct xfrm_state *x, struct sk_buff *skb)
{
switch (x->props.family) {
case AF_INET:
xfrm4_extract_header(skb);
break;
case AF_INET6:
xfrm6_extract_header(skb);
break;
default:
WARN_ON_ONCE(1);
return -EAFNOSUPPORT;
}
return xfrm_inner_mode_encap_remove(x, skb);
}
/* Remove encapsulation header.
*
* The IP header will be moved over the top of the encapsulation header.
*
* On entry, skb_transport_header() shall point to where the IP header
* should be and skb_network_header() shall be set to where the IP header
* currently is. skb->data shall point to the start of the payload.
*/
static int xfrm4_transport_input(struct xfrm_state *x, struct sk_buff *skb)
{
struct xfrm_offload *xo = xfrm_offload(skb);
int ihl = skb->data - skb_transport_header(skb);
if (skb->transport_header != skb->network_header) {
memmove(skb_transport_header(skb),
skb_network_header(skb), ihl);
if (xo)
xo->orig_mac_len =
skb_mac_header_was_set(skb) ? skb_mac_header_len(skb) : 0;
skb->network_header = skb->transport_header;
}
ip_hdr(skb)->tot_len = htons(skb->len + ihl);
skb_reset_transport_header(skb);
return 0;
}
static int xfrm6_transport_input(struct xfrm_state *x, struct sk_buff *skb)
{
#if IS_ENABLED(CONFIG_IPV6)
struct xfrm_offload *xo = xfrm_offload(skb);
int ihl = skb->data - skb_transport_header(skb);
if (skb->transport_header != skb->network_header) {
memmove(skb_transport_header(skb),
skb_network_header(skb), ihl);
if (xo)
xo->orig_mac_len =
skb_mac_header_was_set(skb) ? skb_mac_header_len(skb) : 0;
skb->network_header = skb->transport_header;
}
ipv6_hdr(skb)->payload_len = htons(skb->len + ihl -
sizeof(struct ipv6hdr));
skb_reset_transport_header(skb);
return 0;
#else
WARN_ON_ONCE(1);
return -EAFNOSUPPORT;
#endif
}
static int xfrm_inner_mode_input(struct xfrm_state *x,
struct sk_buff *skb)
{
switch (x->props.mode) {
case XFRM_MODE_BEET:
case XFRM_MODE_TUNNEL:
return xfrm_prepare_input(x, skb);
case XFRM_MODE_TRANSPORT:
if (x->props.family == AF_INET)
return xfrm4_transport_input(x, skb);
if (x->props.family == AF_INET6)
return xfrm6_transport_input(x, skb);
break;
case XFRM_MODE_ROUTEOPTIMIZATION:
WARN_ON_ONCE(1);
break;
default:
if (x->mode_cbs && x->mode_cbs->input)
return x->mode_cbs->input(x, skb);
WARN_ON_ONCE(1);
break;
}
return -EOPNOTSUPP;
}
/* NOTE: encap_type - In addition to the normal (non-negative) values for
* encap_type, a negative value of -1 or -2 can be used to resume/restart this
* function after a previous invocation early terminated for async operation.
*/
int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
{
const struct xfrm_state_afinfo *afinfo;
struct net *net = dev_net(skb->dev);
struct net_device *dev = skb->dev;
int err;
__be32 seq;
__be32 seq_hi;
struct xfrm_state *x = NULL;
xfrm_address_t *daddr;
u32 mark = skb->mark;
unsigned int family = AF_UNSPEC;
int decaps = 0;
int async = 0;
bool xfrm_gro = false;
bool crypto_done = false;
struct xfrm_offload *xo = xfrm_offload(skb);
struct sec_path *sp;
if (encap_type < 0 || (xo && (xo->flags & XFRM_GRO || encap_type == 0 ||
encap_type == UDP_ENCAP_ESPINUDP))) {
x = xfrm_input_state(skb);
if (unlikely(x->km.state != XFRM_STATE_VALID)) {
if (x->km.state == XFRM_STATE_ACQ)
XFRM_INC_STATS(net, LINUX_MIB_XFRMACQUIREERROR);
else
XFRM_INC_STATS(net,
LINUX_MIB_XFRMINSTATEINVALID);
if (encap_type == -1)
dev_put(dev);
goto drop;
}
family = x->props.family;
/* An encap_type of -2 indicates reconstructed inner packet */
if (encap_type == -2)
goto resume_decapped;
/* An encap_type of -1 indicates async resumption. */
if (encap_type == -1) {
async = 1;
seq = XFRM_SKB_CB(skb)->seq.input.low;
spin_lock(&x->lock);
goto resume;
}
/* GRO call */
seq = XFRM_SPI_SKB_CB(skb)->seq;
if (xo && (xo->flags & CRYPTO_DONE)) {
crypto_done = true;
family = XFRM_SPI_SKB_CB(skb)->family;
if (!(xo->status & CRYPTO_SUCCESS)) {
if (xo->status &
(CRYPTO_TRANSPORT_AH_AUTH_FAILED |
CRYPTO_TRANSPORT_ESP_AUTH_FAILED |
CRYPTO_TUNNEL_AH_AUTH_FAILED |
CRYPTO_TUNNEL_ESP_AUTH_FAILED)) {
xfrm_audit_state_icvfail(x, skb,
x->type->proto);
x->stats.integrity_failed++;
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEPROTOERROR);
goto drop;
}
if (xo->status & CRYPTO_INVALID_PROTOCOL) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEPROTOERROR);
goto drop;
}
XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR);
goto drop;
}
if (xfrm_parse_spi(skb, nexthdr, &spi, &seq)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
goto drop;
}
nexthdr = x->type_offload->input_tail(x, skb);
}
goto process;
}
family = XFRM_SPI_SKB_CB(skb)->family;
/* if tunnel is present override skb->mark value with tunnel i_key */
switch (family) {
case AF_INET:
if (XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4)
mark = be32_to_cpu(XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4->parms.i_key);
break;
case AF_INET6:
if (XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6)
mark = be32_to_cpu(XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6->parms.i_key);
break;
}
sp = secpath_set(skb);
if (!sp) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINERROR);
goto drop;
}
seq = 0;
if (!spi && xfrm_parse_spi(skb, nexthdr, &spi, &seq)) {
secpath_reset(skb);
XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
goto drop;
}
daddr = (xfrm_address_t *)(skb_network_header(skb) +
XFRM_SPI_SKB_CB(skb)->daddroff);
do {
sp = skb_sec_path(skb);
if (sp->len == XFRM_MAX_DEPTH) {
secpath_reset(skb);
XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR);
goto drop;
}
x = xfrm_input_state_lookup(net, mark, daddr, spi, nexthdr, family);
if (x == NULL) {
secpath_reset(skb);
XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES);
xfrm_audit_state_notfound(skb, family, spi, seq);
goto drop;
}
if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) {
secpath_reset(skb);
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEDIRERROR);
xfrm_audit_state_notfound(skb, family, spi, seq);
xfrm_state_put(x);
x = NULL;
goto drop;
}
skb->mark = xfrm_smark_get(skb->mark, x);
sp->xvec[sp->len++] = x;
skb_dst_force(skb);
if (!skb_dst(skb)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINERROR);
goto drop;
}
process:
seq_hi = htonl(xfrm_replay_seqhi(x, seq));
XFRM_SKB_CB(skb)->seq.input.low = seq;
XFRM_SKB_CB(skb)->seq.input.hi = seq_hi;
spin_lock(&x->lock);
if (unlikely(x->km.state != XFRM_STATE_VALID)) {
if (x->km.state == XFRM_STATE_ACQ)
XFRM_INC_STATS(net, LINUX_MIB_XFRMACQUIREERROR);
else
XFRM_INC_STATS(net,
LINUX_MIB_XFRMINSTATEINVALID);
goto drop_unlock;
}
if ((x->encap ? x->encap->encap_type : 0) != encap_type) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMISMATCH);
goto drop_unlock;
}
if (xfrm_replay_check(x, skb, seq)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR);
goto drop_unlock;
}
if (xfrm_state_check_expire(x)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEEXPIRED);
goto drop_unlock;
}
if (xfrm_tunnel_check(skb, x, family)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
goto drop_unlock;
}
if (!crypto_done) {
spin_unlock(&x->lock);
dev_hold(dev);
nexthdr = x->type->input(x, skb);
if (nexthdr == -EINPROGRESS) {
if (async)
dev_put(dev);
return 0;
}
dev_put(dev);
spin_lock(&x->lock);
}
resume:
if (nexthdr < 0) {
if (nexthdr == -EBADMSG) {
xfrm_audit_state_icvfail(x, skb,
x->type->proto);
x->stats.integrity_failed++;
}
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEPROTOERROR);
goto drop_unlock;
}
/* only the first xfrm gets the encap type */
encap_type = 0;
if (!crypto_done && xfrm_replay_recheck(x, skb, seq)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATESEQERROR);
goto drop_unlock;
}
xfrm_replay_advance(x, seq);
x->curlft.bytes += skb->len;
x->curlft.packets++;
x->lastused = ktime_get_real_seconds();
spin_unlock(&x->lock);
XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;
err = xfrm_inner_mode_input(x, skb);
if (err == -EINPROGRESS) {
if (async)
dev_put(dev);
return 0;
} else if (err) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
goto drop;
}
resume_decapped:
if (x->outer_mode.flags & XFRM_MODE_FLAG_TUNNEL) {
decaps = 1;
break;
}
/*
* We need the inner address. However, we only get here for
* transport mode so the outer address is identical.
*/
daddr = &x->id.daddr;
family = x->props.family;
err = xfrm_parse_spi(skb, nexthdr, &spi, &seq);
if (err < 0) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
goto drop;
}
crypto_done = false;
} while (!err);
rcu_read_lock();
err = xfrm_rcv_cb(skb, family, x->type->proto, 0);
if (err) {
rcu_read_unlock();
goto drop;
}
nf_reset_ct(skb);
if (decaps) {
sp = skb_sec_path(skb);
if (sp)
sp->olen = 0;
if (skb_valid_dst(skb))
skb_dst_drop(skb);
if (async)
dev_put(dev);
gro_cells_receive(&gro_cells, skb);
rcu_read_unlock();
return 0;
} else {
xo = xfrm_offload(skb);
if (xo)
xfrm_gro = xo->flags & XFRM_GRO;
err = -EAFNOSUPPORT;
afinfo = xfrm_state_afinfo_get_rcu(x->props.family);
if (likely(afinfo))
err = afinfo->transport_finish(skb, xfrm_gro || async);
if (xfrm_gro) {
sp = skb_sec_path(skb);
if (sp)
sp->olen = 0;
if (skb_valid_dst(skb))
skb_dst_drop(skb);
gro_cells_receive(&gro_cells, skb);
}
if (async)
dev_put(dev);
rcu_read_unlock();
return err;
}
drop_unlock:
spin_unlock(&x->lock);
drop:
if (async)
dev_put(dev);
xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1);
kfree_skb(skb);
return 0;
}
EXPORT_SYMBOL(xfrm_input);
int xfrm_input_resume(struct sk_buff *skb, int nexthdr)
{
return xfrm_input(skb, nexthdr, 0, -1);
}
EXPORT_SYMBOL(xfrm_input_resume);
static void xfrm_trans_reinject(struct work_struct *work)
{
struct xfrm_trans_tasklet *trans = container_of(work, struct xfrm_trans_tasklet, work);
struct sk_buff_head queue;
struct sk_buff *skb;
__skb_queue_head_init(&queue);
spin_lock_bh(&trans->queue_lock);
skb_queue_splice_init(&trans->queue, &queue);
spin_unlock_bh(&trans->queue_lock);
local_bh_disable();
while ((skb = __skb_dequeue(&queue))) {
struct net *net = XFRM_TRANS_SKB_CB(skb)->net;
XFRM_TRANS_SKB_CB(skb)->finish(net, NULL, skb);
put_net(net);
}
local_bh_enable();
}
int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb,
int (*finish)(struct net *, struct sock *,
struct sk_buff *))
{
struct xfrm_trans_tasklet *trans;
struct net *hold_net;
trans = this_cpu_ptr(&xfrm_trans_tasklet);
if (skb_queue_len(&trans->queue) >= READ_ONCE(net_hotdata.max_backlog))
return -ENOBUFS;
BUILD_BUG_ON(sizeof(struct xfrm_trans_cb) > sizeof(skb->cb));
hold_net = maybe_get_net(net);
if (!hold_net)
return -ENODEV;
XFRM_TRANS_SKB_CB(skb)->finish = finish;
XFRM_TRANS_SKB_CB(skb)->net = hold_net;
spin_lock_bh(&trans->queue_lock);
__skb_queue_tail(&trans->queue, skb);
spin_unlock_bh(&trans->queue_lock);
schedule_work(&trans->work);
return 0;
}
EXPORT_SYMBOL(xfrm_trans_queue_net);
int xfrm_trans_queue(struct sk_buff *skb,
int (*finish)(struct net *, struct sock *,
struct sk_buff *))
{
return xfrm_trans_queue_net(dev_net(skb->dev), skb, finish);
}
EXPORT_SYMBOL(xfrm_trans_queue);
void __init xfrm_input_init(void)
{
int err;
int i;
xfrm_napi_dev = alloc_netdev_dummy(0);
if (!xfrm_napi_dev)
panic("Failed to allocate XFRM dummy netdev\n");
err = gro_cells_init(&gro_cells, xfrm_napi_dev);
if (err)
gro_cells.cells = NULL;
for_each_possible_cpu(i) {
struct xfrm_trans_tasklet *trans;
trans = &per_cpu(xfrm_trans_tasklet, i);
spin_lock_init(&trans->queue_lock);
__skb_queue_head_init(&trans->queue);
INIT_WORK(&trans->work, xfrm_trans_reinject);
}
}
+110
View File
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Unstable XFRM Helpers for TC-BPF hook
*
* These are called from SCHED_CLS BPF programs. Note that it is
* allowed to break compatibility for these functions since the interface they
* are exposed through to BPF programs is explicitly unstable.
*/
#include <linux/bpf.h>
#include <linux/btf_ids.h>
#include <net/dst_metadata.h>
#include <net/xfrm.h>
/* bpf_xfrm_info - XFRM metadata information
*
* Members:
* @if_id - XFRM if_id:
* Transmit: if_id to be used in policy and state lookups
* Receive: if_id of the state matched for the incoming packet
* @link - Underlying device ifindex:
* Transmit: used as the underlying device in VRF routing
* Receive: the device on which the packet had been received
*/
struct bpf_xfrm_info {
u32 if_id;
int link;
};
__bpf_kfunc_start_defs();
/* bpf_skb_get_xfrm_info - Get XFRM metadata
*
* Parameters:
* @skb_ctx - Pointer to ctx (__sk_buff) in TC program
* Cannot be NULL
* @to - Pointer to memory to which the metadata will be copied
* Cannot be NULL
*/
__bpf_kfunc int bpf_skb_get_xfrm_info(struct __sk_buff *skb_ctx, struct bpf_xfrm_info *to)
{
struct sk_buff *skb = (struct sk_buff *)skb_ctx;
struct xfrm_md_info *info;
info = skb_xfrm_md_info(skb);
if (!info)
return -EINVAL;
to->if_id = info->if_id;
to->link = info->link;
return 0;
}
/* bpf_skb_get_xfrm_info - Set XFRM metadata
*
* Parameters:
* @skb_ctx - Pointer to ctx (__sk_buff) in TC program
* Cannot be NULL
* @from - Pointer to memory from which the metadata will be copied
* Cannot be NULL
*/
__bpf_kfunc int bpf_skb_set_xfrm_info(struct __sk_buff *skb_ctx, const struct bpf_xfrm_info *from)
{
struct sk_buff *skb = (struct sk_buff *)skb_ctx;
struct metadata_dst *md_dst;
struct xfrm_md_info *info;
if (unlikely(skb_metadata_dst(skb)))
return -EINVAL;
if (!xfrm_bpf_md_dst) {
struct metadata_dst __percpu *tmp;
tmp = metadata_dst_alloc_percpu(0, METADATA_XFRM, GFP_ATOMIC);
if (!tmp)
return -ENOMEM;
if (cmpxchg(&xfrm_bpf_md_dst, NULL, tmp))
metadata_dst_free_percpu(tmp);
}
md_dst = this_cpu_ptr(xfrm_bpf_md_dst);
info = &md_dst->u.xfrm_info;
info->if_id = from->if_id;
info->link = from->link;
skb_dst_force(skb);
info->dst_orig = skb_dst(skb);
dst_hold((struct dst_entry *)md_dst);
skb_dst_set(skb, (struct dst_entry *)md_dst);
return 0;
}
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(xfrm_ifc_kfunc_set)
BTF_ID_FLAGS(func, bpf_skb_get_xfrm_info)
BTF_ID_FLAGS(func, bpf_skb_set_xfrm_info)
BTF_KFUNCS_END(xfrm_ifc_kfunc_set)
static const struct btf_kfunc_id_set xfrm_interface_kfunc_set = {
.owner = THIS_MODULE,
.set = &xfrm_ifc_kfunc_set,
};
int __init register_xfrm_interface_bpf(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS,
&xfrm_interface_kfunc_set);
}
File diff suppressed because it is too large Load Diff
+368
View File
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* IP Payload Compression Protocol (IPComp) - RFC3173.
*
* Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
* Copyright (c) 2003-2025 Herbert Xu <herbert@gondor.apana.org.au>
*
* Todo:
* - Tunable compression parameters.
* - Compression stats.
* - Adaptive compression.
*/
#include <crypto/acompress.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/skbuff_ref.h>
#include <linux/slab.h>
#include <net/ipcomp.h>
#include <net/xfrm.h>
#define IPCOMP_SCRATCH_SIZE 65400
struct ipcomp_skb_cb {
struct xfrm_skb_cb xfrm;
struct acomp_req *req;
};
struct ipcomp_data {
u16 threshold;
struct crypto_acomp *tfm;
};
struct ipcomp_req_extra {
struct xfrm_state *x;
struct scatterlist sg[];
};
static inline struct ipcomp_skb_cb *ipcomp_cb(struct sk_buff *skb)
{
struct ipcomp_skb_cb *cb = (void *)skb->cb;
BUILD_BUG_ON(sizeof(*cb) > sizeof(skb->cb));
return cb;
}
static int ipcomp_post_acomp(struct sk_buff *skb, int err, int hlen)
{
struct acomp_req *req = ipcomp_cb(skb)->req;
struct ipcomp_req_extra *extra;
struct scatterlist *dsg;
int len, dlen;
if (unlikely(!req))
return err;
extra = acomp_request_extra(req);
dsg = extra->sg;
if (unlikely(err))
goto out_free_req;
dlen = req->dlen;
pskb_trim_unique(skb, 0);
__skb_put(skb, hlen);
/* Only update truesize on input. */
if (!hlen)
skb->truesize += dlen;
skb->data_len = dlen;
skb->len += dlen;
do {
skb_frag_t *frag;
struct page *page;
frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags;
page = sg_page(dsg);
dsg = sg_next(dsg);
len = PAGE_SIZE;
if (dlen < len)
len = dlen;
skb_frag_fill_page_desc(frag, page, 0, len);
skb_shinfo(skb)->nr_frags++;
} while ((dlen -= len));
out_free_req:
for (; dsg && sg_page(dsg); dsg = sg_next(dsg))
__free_page(sg_page(dsg));
acomp_request_free(req);
return err;
}
static int ipcomp_input_done2(struct sk_buff *skb, int err)
{
struct ip_comp_hdr *ipch = ip_comp_hdr(skb);
const int plen = skb->len;
skb->transport_header = skb->network_header + sizeof(*ipch);
return ipcomp_post_acomp(skb, err, 0) ?:
skb->len < (plen + sizeof(ip_comp_hdr)) ? -EINVAL :
ipch->nexthdr;
}
static void ipcomp_input_done(void *data, int err)
{
struct sk_buff *skb = data;
xfrm_input_resume(skb, ipcomp_input_done2(skb, err));
}
static struct acomp_req *ipcomp_setup_req(struct xfrm_state *x,
struct sk_buff *skb, int minhead,
int dlen)
{
const int dnfrags = min(MAX_SKB_FRAGS, 16);
struct ipcomp_data *ipcd = x->data;
struct ipcomp_req_extra *extra;
struct scatterlist *sg, *dsg;
const int plen = skb->len;
struct crypto_acomp *tfm;
struct acomp_req *req;
int nfrags;
int total;
int err;
int i;
ipcomp_cb(skb)->req = NULL;
do {
struct sk_buff *trailer;
if (skb->len > PAGE_SIZE) {
if (skb_linearize_cow(skb))
return ERR_PTR(-ENOMEM);
nfrags = 1;
break;
}
if (!skb_cloned(skb) && skb_headlen(skb) >= minhead) {
if (!skb_is_nonlinear(skb)) {
nfrags = 1;
break;
} else if (!skb_has_frag_list(skb)) {
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
break;
}
}
nfrags = skb_cow_data(skb, skb_headlen(skb) < minhead ?
minhead - skb_headlen(skb) : 0,
&trailer);
if (nfrags < 0)
return ERR_PTR(nfrags);
} while (0);
tfm = ipcd->tfm;
req = acomp_request_alloc_extra(
tfm, sizeof(*extra) + sizeof(*sg) * (nfrags + dnfrags),
GFP_ATOMIC);
ipcomp_cb(skb)->req = req;
if (!req)
return ERR_PTR(-ENOMEM);
extra = acomp_request_extra(req);
extra->x = x;
dsg = extra->sg;
sg = dsg + dnfrags;
sg_init_table(sg, nfrags);
err = skb_to_sgvec(skb, sg, 0, plen);
if (unlikely(err < 0))
return ERR_PTR(err);
sg_init_table(dsg, dnfrags);
total = 0;
for (i = 0; i < dnfrags && total < dlen; i++) {
struct page *page;
page = alloc_page(GFP_ATOMIC);
if (!page)
break;
sg_set_page(dsg + i, page, PAGE_SIZE, 0);
total += PAGE_SIZE;
}
if (!i)
return ERR_PTR(-ENOMEM);
sg_mark_end(dsg + i - 1);
dlen = min(dlen, total);
acomp_request_set_params(req, sg, dsg, plen, dlen);
return req;
}
static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
{
struct acomp_req *req;
int err;
req = ipcomp_setup_req(x, skb, 0, IPCOMP_SCRATCH_SIZE);
err = PTR_ERR(req);
if (IS_ERR(req))
goto out;
acomp_request_set_callback(req, 0, ipcomp_input_done, skb);
err = crypto_acomp_decompress(req);
if (err == -EINPROGRESS)
return err;
out:
return ipcomp_input_done2(skb, err);
}
int ipcomp_input(struct xfrm_state *x, struct sk_buff *skb)
{
struct ip_comp_hdr *ipch __maybe_unused;
if (!pskb_may_pull(skb, sizeof(*ipch)))
return -EINVAL;
skb->ip_summed = CHECKSUM_NONE;
/* Remove ipcomp header and decompress original payload */
__skb_pull(skb, sizeof(*ipch));
return ipcomp_decompress(x, skb);
}
EXPORT_SYMBOL_GPL(ipcomp_input);
static int ipcomp_output_push(struct sk_buff *skb)
{
skb_push(skb, -skb_network_offset(skb));
return 0;
}
static int ipcomp_output_done2(struct xfrm_state *x, struct sk_buff *skb,
int err)
{
struct ip_comp_hdr *ipch;
err = ipcomp_post_acomp(skb, err, sizeof(*ipch));
if (err)
goto out_ok;
/* Install ipcomp header, convert into ipcomp datagram. */
ipch = ip_comp_hdr(skb);
ipch->nexthdr = *skb_mac_header(skb);
ipch->flags = 0;
ipch->cpi = htons((u16 )ntohl(x->id.spi));
*skb_mac_header(skb) = IPPROTO_COMP;
out_ok:
return ipcomp_output_push(skb);
}
static void ipcomp_output_done(void *data, int err)
{
struct ipcomp_req_extra *extra;
struct sk_buff *skb = data;
struct acomp_req *req;
req = ipcomp_cb(skb)->req;
extra = acomp_request_extra(req);
xfrm_output_resume(skb_to_full_sk(skb), skb,
ipcomp_output_done2(extra->x, skb, err));
}
static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb)
{
struct ip_comp_hdr *ipch __maybe_unused;
struct acomp_req *req;
int err;
req = ipcomp_setup_req(x, skb, sizeof(*ipch),
skb->len - sizeof(*ipch));
err = PTR_ERR(req);
if (IS_ERR(req))
goto out;
acomp_request_set_callback(req, 0, ipcomp_output_done, skb);
err = crypto_acomp_compress(req);
if (err == -EINPROGRESS)
return err;
out:
return ipcomp_output_done2(x, skb, err);
}
int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb)
{
struct ipcomp_data *ipcd = x->data;
if (skb->len < ipcd->threshold) {
/* Don't bother compressing */
return ipcomp_output_push(skb);
}
return ipcomp_compress(x, skb);
}
EXPORT_SYMBOL_GPL(ipcomp_output);
static void ipcomp_free_data(struct ipcomp_data *ipcd)
{
crypto_free_acomp(ipcd->tfm);
}
void ipcomp_destroy(struct xfrm_state *x)
{
struct ipcomp_data *ipcd = x->data;
if (!ipcd)
return;
ipcomp_free_data(ipcd);
kfree(ipcd);
}
EXPORT_SYMBOL_GPL(ipcomp_destroy);
int ipcomp_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
{
int err;
struct ipcomp_data *ipcd;
struct xfrm_algo_desc *calg_desc;
err = -EINVAL;
if (!x->calg) {
NL_SET_ERR_MSG(extack, "Missing required compression algorithm");
goto out;
}
if (x->encap) {
NL_SET_ERR_MSG(extack, "IPComp is not compatible with encapsulation");
goto out;
}
err = -ENOMEM;
ipcd = kzalloc_obj(*ipcd);
if (!ipcd)
goto out;
ipcd->tfm = crypto_alloc_acomp(x->calg->alg_name, 0, 0);
if (IS_ERR(ipcd->tfm))
goto error;
calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
BUG_ON(!calg_desc);
ipcd->threshold = calg_desc->uinfo.comp.threshold;
x->data = ipcd;
err = 0;
out:
return err;
error:
ipcomp_free_data(ipcd);
kfree(ipcd);
goto out;
}
EXPORT_SYMBOL_GPL(ipcomp_init_state);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
File diff suppressed because it is too large Load Diff
+340
View File
@@ -0,0 +1,340 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* xfrm_nat_keepalive.c
*
* (c) 2024 Eyal Birger <eyal.birger@gmail.com>
*/
#include <net/inet_common.h>
#include <net/ip6_checksum.h>
#include <net/xfrm.h>
static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv4) = {
.bh_lock = INIT_LOCAL_LOCK(bh_lock),
};
#if IS_ENABLED(CONFIG_IPV6)
static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv6) = {
.bh_lock = INIT_LOCAL_LOCK(bh_lock),
};
#endif
struct nat_keepalive {
struct net *net;
u16 family;
xfrm_address_t saddr;
xfrm_address_t daddr;
__be16 encap_sport;
__be16 encap_dport;
__u32 smark;
};
static void nat_keepalive_init(struct nat_keepalive *ka, struct xfrm_state *x)
{
ka->net = xs_net(x);
ka->family = x->props.family;
ka->saddr = x->props.saddr;
ka->daddr = x->id.daddr;
ka->encap_sport = x->encap->encap_sport;
ka->encap_dport = x->encap->encap_dport;
ka->smark = xfrm_smark_get(0, x);
}
static int nat_keepalive_send_ipv4(struct sk_buff *skb,
struct nat_keepalive *ka)
{
struct net *net = ka->net;
struct flowi4 fl4;
struct rtable *rt;
struct sock *sk;
__u8 tos = 0;
int err;
flowi4_init_output(&fl4, 0 /* oif */, skb->mark, tos,
RT_SCOPE_UNIVERSE, IPPROTO_UDP, 0,
ka->daddr.a4, ka->saddr.a4, ka->encap_dport,
ka->encap_sport, sock_net_uid(net, NULL));
rt = ip_route_output_key(net, &fl4);
if (IS_ERR(rt)) {
kfree_skb(skb);
return PTR_ERR(rt);
}
skb_dst_set(skb, &rt->dst);
local_lock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
sk = this_cpu_read(nat_keepalive_sk_ipv4.sock);
sock_net_set(sk, net);
err = ip_build_and_send_pkt(skb, sk, fl4.saddr, fl4.daddr, NULL, tos);
sock_net_set(sk, &init_net);
local_unlock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
return err;
}
#if IS_ENABLED(CONFIG_IPV6)
static int nat_keepalive_send_ipv6(struct sk_buff *skb,
struct nat_keepalive *ka,
struct udphdr *uh)
{
struct net *net = ka->net;
struct dst_entry *dst;
struct flowi6 fl6;
struct sock *sk;
__wsum csum;
int err;
csum = skb_checksum(skb, 0, skb->len, 0);
uh->check = csum_ipv6_magic(&ka->saddr.in6, &ka->daddr.in6,
skb->len, IPPROTO_UDP, csum);
if (uh->check == 0)
uh->check = CSUM_MANGLED_0;
memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_mark = skb->mark;
fl6.saddr = ka->saddr.in6;
fl6.daddr = ka->daddr.in6;
fl6.flowi6_proto = IPPROTO_UDP;
fl6.fl6_sport = ka->encap_sport;
fl6.fl6_dport = ka->encap_dport;
local_lock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
sk = this_cpu_read(nat_keepalive_sk_ipv6.sock);
sock_net_set(sk, net);
dst = ip6_dst_lookup_flow(net, sk, &fl6, NULL);
if (IS_ERR(dst)) {
local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
kfree_skb(skb);
return PTR_ERR(dst);
}
skb_dst_set(skb, dst);
err = ip6_xmit(sk, skb, &fl6, skb->mark, NULL, 0, 0);
sock_net_set(sk, &init_net);
local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
return err;
}
#endif
static void nat_keepalive_send(struct nat_keepalive *ka)
{
const int nat_ka_hdrs_len = max(sizeof(struct iphdr),
sizeof(struct ipv6hdr)) +
sizeof(struct udphdr);
const u8 nat_ka_payload = 0xFF;
struct sk_buff *skb;
struct udphdr *uh;
skb = alloc_skb(nat_ka_hdrs_len + sizeof(nat_ka_payload), GFP_ATOMIC);
if (unlikely(!skb))
return;
skb_reserve(skb, nat_ka_hdrs_len);
skb_put_u8(skb, nat_ka_payload);
uh = skb_push(skb, sizeof(*uh));
uh->source = ka->encap_sport;
uh->dest = ka->encap_dport;
uh->len = htons(skb->len);
uh->check = 0;
skb->mark = ka->smark;
switch (ka->family) {
case AF_INET:
nat_keepalive_send_ipv4(skb, ka);
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
nat_keepalive_send_ipv6(skb, ka, uh);
break;
#endif
default:
kfree_skb(skb);
break;
}
}
enum {
NAT_KEEPALIVE_BATCH_SIZE = 16,
NAT_KEEPALIVE_BATCH_FULL = 1,
};
struct nat_keepalive_work_ctx {
struct xfrm_state *batch[NAT_KEEPALIVE_BATCH_SIZE];
unsigned int nr;
time64_t next_run;
time64_t now;
};
static int nat_keepalive_work_collect(struct xfrm_state *x, int count, void *ptr)
{
struct nat_keepalive_work_ctx *ctx = ptr;
if (!READ_ONCE(x->nat_keepalive_interval))
return 0;
if (ctx->nr == ARRAY_SIZE(ctx->batch))
return NAT_KEEPALIVE_BATCH_FULL;
xfrm_state_hold(x);
ctx->batch[ctx->nr++] = x;
return 0;
}
static void nat_keepalive_work_single(struct xfrm_state *x,
struct nat_keepalive_work_ctx *ctx)
{
bool send_keepalive = false;
struct nat_keepalive ka;
time64_t next_run = 0;
u32 interval;
int delta;
spin_lock_bh(&x->lock);
if (x->km.state == XFRM_STATE_DEAD)
goto out;
interval = x->nat_keepalive_interval;
if (!interval)
goto out;
delta = (int)(ctx->now - x->lastused);
if (delta < interval) {
x->nat_keepalive_expiration = ctx->now + interval - delta;
next_run = x->nat_keepalive_expiration;
} else if (x->nat_keepalive_expiration > ctx->now) {
next_run = x->nat_keepalive_expiration;
} else {
next_run = ctx->now + interval;
nat_keepalive_init(&ka, x);
send_keepalive = true;
}
out:
spin_unlock_bh(&x->lock);
if (send_keepalive)
nat_keepalive_send(&ka);
if (next_run && (!ctx->next_run || next_run < ctx->next_run))
ctx->next_run = next_run;
}
static void nat_keepalive_work(struct work_struct *work)
{
struct nat_keepalive_work_ctx ctx;
struct xfrm_state_walk walk;
struct net *net;
int err, i;
ctx.next_run = 0;
ctx.now = ktime_get_real_seconds();
net = container_of(work, struct net, xfrm.nat_keepalive_work.work);
xfrm_state_walk_init(&walk, IPPROTO_ESP, NULL);
do {
ctx.nr = 0;
err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx);
local_bh_disable();
for (i = 0; i < ctx.nr; i++) {
nat_keepalive_work_single(ctx.batch[i], &ctx);
xfrm_state_put(ctx.batch[i]);
}
local_bh_enable();
} while (err == NAT_KEEPALIVE_BATCH_FULL);
xfrm_state_walk_done(&walk, net);
if (ctx.next_run)
schedule_delayed_work(&net->xfrm.nat_keepalive_work,
(ctx.next_run - ctx.now) * HZ);
}
static int nat_keepalive_sk_init(struct sock_bh_locked __percpu *socks,
unsigned short family)
{
struct sock *sk;
int err, i;
for_each_possible_cpu(i) {
err = inet_ctl_sock_create(&sk, family, SOCK_RAW, IPPROTO_UDP,
&init_net);
if (err < 0)
goto err;
per_cpu_ptr(socks, i)->sock = sk;
}
return 0;
err:
for_each_possible_cpu(i)
inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
return err;
}
static void nat_keepalive_sk_fini(struct sock_bh_locked __percpu *socks)
{
int i;
for_each_possible_cpu(i)
inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
}
void xfrm_nat_keepalive_state_updated(struct xfrm_state *x)
{
struct net *net;
if (!x->nat_keepalive_interval)
return;
net = xs_net(x);
schedule_delayed_work(&net->xfrm.nat_keepalive_work, 0);
}
int __net_init xfrm_nat_keepalive_net_init(struct net *net)
{
INIT_DELAYED_WORK(&net->xfrm.nat_keepalive_work, nat_keepalive_work);
return 0;
}
int xfrm_nat_keepalive_net_fini(struct net *net)
{
disable_delayed_work_sync(&net->xfrm.nat_keepalive_work);
return 0;
}
int xfrm_nat_keepalive_init(unsigned short family)
{
int err = -EAFNOSUPPORT;
switch (family) {
case AF_INET:
err = nat_keepalive_sk_init(&nat_keepalive_sk_ipv4, PF_INET);
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
err = nat_keepalive_sk_init(&nat_keepalive_sk_ipv6, PF_INET6);
break;
#endif
}
if (err)
pr_err("xfrm nat keepalive init: failed to init err:%d\n", err);
return err;
}
EXPORT_SYMBOL_GPL(xfrm_nat_keepalive_init);
void xfrm_nat_keepalive_fini(unsigned short family)
{
switch (family) {
case AF_INET:
nat_keepalive_sk_fini(&nat_keepalive_sk_ipv4);
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
nat_keepalive_sk_fini(&nat_keepalive_sk_ipv6);
break;
#endif
}
}
EXPORT_SYMBOL_GPL(xfrm_nat_keepalive_fini);
+973
View File
@@ -0,0 +1,973 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* xfrm_output.c - Common IPsec encapsulation code.
*
* Copyright (c) 2007 Herbert Xu <herbert@gondor.apana.org.au>
*/
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <net/dst.h>
#include <net/gso.h>
#include <net/icmp.h>
#include <net/inet_ecn.h>
#include <net/xfrm.h>
#if IS_ENABLED(CONFIG_IPV6)
#include <net/ip6_route.h>
#endif
#include "xfrm_inout.h"
static int xfrm_output2(struct net *net, struct sock *sk, struct sk_buff *skb);
static int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb);
static int xfrm_skb_check_space(struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
int nhead = dst->header_len + LL_RESERVED_SPACE(dst->dev)
- skb_headroom(skb);
int ntail = dst->dev->needed_tailroom - skb_tailroom(skb);
if (nhead <= 0) {
if (ntail <= 0)
return 0;
nhead = 0;
} else if (ntail < 0)
ntail = 0;
return pskb_expand_head(skb, nhead, ntail, GFP_ATOMIC);
}
/* Children define the path of the packet through the
* Linux networking. Thus, destinations are stackable.
*/
static struct dst_entry *skb_dst_pop(struct sk_buff *skb)
{
struct dst_entry *child = dst_clone(xfrm_dst_child(skb_dst(skb)));
skb_dst_drop(skb);
return child;
}
/* Add encapsulation header.
*
* The IP header will be moved forward to make space for the encapsulation
* header.
*/
static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
int ihl = iph->ihl * 4;
if (!skb->inner_protocol)
skb_set_inner_transport_header(skb,
skb_transport_offset(skb));
skb_set_network_header(skb, -x->props.header_len);
skb->mac_header = skb->network_header +
offsetof(struct iphdr, protocol);
skb->transport_header = skb->network_header + ihl;
__skb_pull(skb, ihl);
memmove(skb_network_header(skb), iph, ihl);
return 0;
}
#if IS_ENABLED(CONFIG_IPV6_MIP6)
static int mip6_rthdr_offset(struct sk_buff *skb, u8 **nexthdr, int type)
{
const unsigned char *nh = skb_network_header(skb);
unsigned int offset = sizeof(struct ipv6hdr);
unsigned int packet_len;
int found_rhdr = 0;
packet_len = skb_tail_pointer(skb) - nh;
*nexthdr = &ipv6_hdr(skb)->nexthdr;
while (offset <= packet_len) {
struct ipv6_opt_hdr *exthdr;
switch (**nexthdr) {
case NEXTHDR_HOP:
break;
case NEXTHDR_ROUTING:
if (type == IPPROTO_ROUTING && offset + 3 <= packet_len) {
struct ipv6_rt_hdr *rt;
rt = (struct ipv6_rt_hdr *)(nh + offset);
if (rt->type != 0)
return offset;
}
found_rhdr = 1;
break;
case NEXTHDR_DEST:
/* HAO MUST NOT appear more than once.
* XXX: It is better to try to find by the end of
* XXX: packet if HAO exists.
*/
if (ipv6_find_tlv(skb, offset, IPV6_TLV_HAO) >= 0) {
net_dbg_ratelimited("mip6: hao exists already, override\n");
return offset;
}
if (found_rhdr)
return offset;
break;
default:
return offset;
}
if (offset + sizeof(struct ipv6_opt_hdr) > packet_len)
return -EINVAL;
exthdr = (struct ipv6_opt_hdr *)(skb_network_header(skb) +
offset);
offset += ipv6_optlen(exthdr);
if (offset > IPV6_MAXPLEN)
return -EINVAL;
*nexthdr = &exthdr->nexthdr;
}
return -EINVAL;
}
#endif
#if IS_ENABLED(CONFIG_IPV6)
static int xfrm6_hdr_offset(struct xfrm_state *x, struct sk_buff *skb, u8 **prevhdr)
{
switch (x->type->proto) {
#if IS_ENABLED(CONFIG_IPV6_MIP6)
case IPPROTO_DSTOPTS:
case IPPROTO_ROUTING:
return mip6_rthdr_offset(skb, prevhdr, x->type->proto);
#endif
default:
break;
}
return ip6_find_1stfragopt(skb, prevhdr);
}
#endif
/* Add encapsulation header.
*
* The IP header and mutable extension headers will be moved forward to make
* space for the encapsulation header.
*/
static int xfrm6_transport_output(struct xfrm_state *x, struct sk_buff *skb)
{
#if IS_ENABLED(CONFIG_IPV6)
struct ipv6hdr *iph;
u8 *prevhdr;
int hdr_len;
iph = ipv6_hdr(skb);
if (!skb->inner_protocol)
skb_set_inner_transport_header(skb,
skb_transport_offset(skb));
hdr_len = xfrm6_hdr_offset(x, skb, &prevhdr);
if (hdr_len < 0)
return hdr_len;
skb_set_mac_header(skb,
(prevhdr - x->props.header_len) - skb->data);
skb_set_network_header(skb, -x->props.header_len);
skb->transport_header = skb->network_header + hdr_len;
__skb_pull(skb, hdr_len);
memmove(ipv6_hdr(skb), iph, hdr_len);
return 0;
#else
WARN_ON_ONCE(1);
return -EAFNOSUPPORT;
#endif
}
/* Add route optimization header space.
*
* The IP header and mutable extension headers will be moved forward to make
* space for the route optimization header.
*/
static int xfrm6_ro_output(struct xfrm_state *x, struct sk_buff *skb)
{
#if IS_ENABLED(CONFIG_IPV6)
struct ipv6hdr *iph;
u8 *prevhdr;
int hdr_len;
iph = ipv6_hdr(skb);
hdr_len = xfrm6_hdr_offset(x, skb, &prevhdr);
if (hdr_len < 0)
return hdr_len;
skb_set_mac_header(skb,
(prevhdr - x->props.header_len) - skb->data);
skb_set_network_header(skb, -x->props.header_len);
skb->transport_header = skb->network_header + hdr_len;
__skb_pull(skb, hdr_len);
memmove(ipv6_hdr(skb), iph, hdr_len);
return 0;
#else
WARN_ON_ONCE(1);
return -EAFNOSUPPORT;
#endif
}
/* Add encapsulation header.
*
* The top IP header will be constructed per draft-nikander-esp-beet-mode-06.txt.
*/
static int xfrm4_beet_encap_add(struct xfrm_state *x, struct sk_buff *skb)
{
struct ip_beet_phdr *ph;
struct iphdr *top_iph;
int hdrlen, optlen;
hdrlen = 0;
optlen = XFRM_MODE_SKB_CB(skb)->optlen;
if (unlikely(optlen))
hdrlen += IPV4_BEET_PHMAXLEN - (optlen & 4);
skb_set_network_header(skb, -x->props.header_len - hdrlen +
(XFRM_MODE_SKB_CB(skb)->ihl - sizeof(*top_iph)));
if (x->sel.family != AF_INET6)
skb->network_header += IPV4_BEET_PHMAXLEN;
skb->mac_header = skb->network_header +
offsetof(struct iphdr, protocol);
skb->transport_header = skb->network_header + sizeof(*top_iph);
xfrm4_beet_make_header(skb);
ph = __skb_pull(skb, XFRM_MODE_SKB_CB(skb)->ihl - hdrlen);
top_iph = ip_hdr(skb);
if (unlikely(optlen)) {
if (WARN_ON(optlen < 0))
return -EINVAL;
ph->padlen = 4 - (optlen & 4);
ph->hdrlen = optlen / 8;
ph->nexthdr = top_iph->protocol;
if (ph->padlen)
memset(ph + 1, IPOPT_NOP, ph->padlen);
top_iph->protocol = IPPROTO_BEETPH;
top_iph->ihl = sizeof(struct iphdr) / 4;
}
top_iph->saddr = x->props.saddr.a4;
top_iph->daddr = x->id.daddr.a4;
return 0;
}
/* Add encapsulation header.
*
* The top IP header will be constructed per RFC 2401.
*/
static int xfrm4_tunnel_encap_add(struct xfrm_state *x, struct sk_buff *skb)
{
bool small_ipv6 = (skb->protocol == htons(ETH_P_IPV6)) && (skb->len <= IPV6_MIN_MTU);
struct dst_entry *dst = skb_dst(skb);
struct iphdr *top_iph;
int flags;
if (!skb->inner_protocol) {
skb_set_inner_network_header(skb, skb_network_offset(skb));
skb_set_inner_transport_header(skb, skb_transport_offset(skb));
}
skb_set_network_header(skb, -x->props.header_len);
skb->mac_header = skb->network_header +
offsetof(struct iphdr, protocol);
skb->transport_header = skb->network_header + sizeof(*top_iph);
top_iph = ip_hdr(skb);
top_iph->ihl = 5;
top_iph->version = 4;
top_iph->protocol = xfrm_af2proto(skb_dst(skb)->ops->family);
/* DS disclosing depends on XFRM_SA_XFLAG_DONT_ENCAP_DSCP */
if (x->props.extra_flags & XFRM_SA_XFLAG_DONT_ENCAP_DSCP)
top_iph->tos = 0;
else
top_iph->tos = XFRM_MODE_SKB_CB(skb)->tos;
top_iph->tos = INET_ECN_encapsulate(top_iph->tos,
XFRM_MODE_SKB_CB(skb)->tos);
flags = x->props.flags;
if (flags & XFRM_STATE_NOECN)
IP_ECN_clear(top_iph);
top_iph->frag_off = (flags & XFRM_STATE_NOPMTUDISC) || small_ipv6 ?
0 : (XFRM_MODE_SKB_CB(skb)->frag_off & htons(IP_DF));
top_iph->ttl = ip4_dst_hoplimit(xfrm_dst_child(dst));
top_iph->saddr = x->props.saddr.a4;
top_iph->daddr = x->id.daddr.a4;
ip_select_ident(dev_net(dst->dev), skb, NULL);
return 0;
}
#if IS_ENABLED(CONFIG_IPV6)
static int xfrm6_tunnel_encap_add(struct xfrm_state *x, struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct ipv6hdr *top_iph;
int dsfield;
if (!skb->inner_protocol) {
skb_set_inner_network_header(skb, skb_network_offset(skb));
skb_set_inner_transport_header(skb, skb_transport_offset(skb));
}
skb_set_network_header(skb, -x->props.header_len);
skb->mac_header = skb->network_header +
offsetof(struct ipv6hdr, nexthdr);
skb->transport_header = skb->network_header + sizeof(*top_iph);
top_iph = ipv6_hdr(skb);
top_iph->version = 6;
memcpy(top_iph->flow_lbl, XFRM_MODE_SKB_CB(skb)->flow_lbl,
sizeof(top_iph->flow_lbl));
top_iph->nexthdr = xfrm_af2proto(skb_dst(skb)->ops->family);
if (x->props.extra_flags & XFRM_SA_XFLAG_DONT_ENCAP_DSCP)
dsfield = 0;
else
dsfield = XFRM_MODE_SKB_CB(skb)->tos;
dsfield = INET_ECN_encapsulate(dsfield, XFRM_MODE_SKB_CB(skb)->tos);
if (x->props.flags & XFRM_STATE_NOECN)
dsfield &= ~INET_ECN_MASK;
ipv6_change_dsfield(top_iph, 0, dsfield);
top_iph->hop_limit = ip6_dst_hoplimit(xfrm_dst_child(dst));
top_iph->saddr = *(struct in6_addr *)&x->props.saddr;
top_iph->daddr = *(struct in6_addr *)&x->id.daddr;
return 0;
}
static int xfrm6_beet_encap_add(struct xfrm_state *x, struct sk_buff *skb)
{
struct ipv6hdr *top_iph;
struct ip_beet_phdr *ph;
int optlen, hdr_len;
hdr_len = 0;
optlen = XFRM_MODE_SKB_CB(skb)->optlen;
if (unlikely(optlen))
hdr_len += IPV4_BEET_PHMAXLEN - (optlen & 4);
skb_set_network_header(skb, -x->props.header_len - hdr_len);
if (x->sel.family != AF_INET6)
skb->network_header += IPV4_BEET_PHMAXLEN;
skb->mac_header = skb->network_header +
offsetof(struct ipv6hdr, nexthdr);
skb->transport_header = skb->network_header + sizeof(*top_iph);
ph = __skb_pull(skb, XFRM_MODE_SKB_CB(skb)->ihl - hdr_len);
xfrm6_beet_make_header(skb);
top_iph = ipv6_hdr(skb);
if (unlikely(optlen)) {
if (WARN_ON(optlen < 0))
return -EINVAL;
ph->padlen = 4 - (optlen & 4);
ph->hdrlen = optlen / 8;
ph->nexthdr = top_iph->nexthdr;
if (ph->padlen)
memset(ph + 1, IPOPT_NOP, ph->padlen);
top_iph->nexthdr = IPPROTO_BEETPH;
}
top_iph->saddr = *(struct in6_addr *)&x->props.saddr;
top_iph->daddr = *(struct in6_addr *)&x->id.daddr;
return 0;
}
#endif
/* Add encapsulation header.
*
* On exit, the transport header will be set to the start of the
* encapsulation header to be filled in by x->type->output and the mac
* header will be set to the nextheader (protocol for IPv4) field of the
* extension header directly preceding the encapsulation header, or in
* its absence, that of the top IP header.
* The value of the network header will always point to the top IP header
* while skb->data will point to the payload.
*/
static int xfrm4_prepare_output(struct xfrm_state *x, struct sk_buff *skb)
{
int err;
err = xfrm_inner_extract_output(x, skb);
if (err)
return err;
IPCB(skb)->flags |= IPSKB_XFRM_TUNNEL_SIZE;
skb->protocol = htons(ETH_P_IP);
switch (x->props.mode) {
case XFRM_MODE_BEET:
return xfrm4_beet_encap_add(x, skb);
case XFRM_MODE_TUNNEL:
return xfrm4_tunnel_encap_add(x, skb);
}
WARN_ON_ONCE(1);
return -EOPNOTSUPP;
}
static int xfrm6_prepare_output(struct xfrm_state *x, struct sk_buff *skb)
{
#if IS_ENABLED(CONFIG_IPV6)
int err;
err = xfrm_inner_extract_output(x, skb);
if (err)
return err;
skb->ignore_df = 1;
skb->protocol = htons(ETH_P_IPV6);
switch (x->props.mode) {
case XFRM_MODE_BEET:
return xfrm6_beet_encap_add(x, skb);
case XFRM_MODE_TUNNEL:
return xfrm6_tunnel_encap_add(x, skb);
default:
WARN_ON_ONCE(1);
return -EOPNOTSUPP;
}
#endif
WARN_ON_ONCE(1);
return -EAFNOSUPPORT;
}
static int xfrm_outer_mode_output(struct xfrm_state *x, struct sk_buff *skb)
{
switch (x->props.mode) {
case XFRM_MODE_BEET:
case XFRM_MODE_TUNNEL:
if (x->props.family == AF_INET)
return xfrm4_prepare_output(x, skb);
if (x->props.family == AF_INET6)
return xfrm6_prepare_output(x, skb);
break;
case XFRM_MODE_TRANSPORT:
if (x->props.family == AF_INET)
return xfrm4_transport_output(x, skb);
if (x->props.family == AF_INET6)
return xfrm6_transport_output(x, skb);
break;
case XFRM_MODE_ROUTEOPTIMIZATION:
if (x->props.family == AF_INET6)
return xfrm6_ro_output(x, skb);
WARN_ON_ONCE(1);
break;
default:
if (x->mode_cbs && x->mode_cbs->prepare_output)
return x->mode_cbs->prepare_output(x, skb);
WARN_ON_ONCE(1);
break;
}
return -EOPNOTSUPP;
}
#if IS_ENABLED(CONFIG_NET_PKTGEN)
int pktgen_xfrm_outer_mode_output(struct xfrm_state *x, struct sk_buff *skb)
{
return xfrm_outer_mode_output(x, skb);
}
EXPORT_SYMBOL_GPL(pktgen_xfrm_outer_mode_output);
#endif
static int xfrm_output_one(struct sk_buff *skb, int err)
{
struct dst_entry *dst = skb_dst(skb);
struct xfrm_state *x = dst->xfrm;
struct net *net = xs_net(x);
if (err <= 0 || x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
goto resume;
do {
err = xfrm_skb_check_space(skb);
if (err) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
goto error_nolock;
}
skb->mark = xfrm_smark_get(skb->mark, x);
err = xfrm_outer_mode_output(x, skb);
if (err) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEMODEERROR);
goto error_nolock;
}
spin_lock_bh(&x->lock);
if (unlikely(x->km.state != XFRM_STATE_VALID)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEINVALID);
err = -EINVAL;
goto error;
}
err = xfrm_state_check_expire(x);
if (err) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEEXPIRED);
goto error;
}
err = xfrm_replay_overflow(x, skb);
if (err) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATESEQERROR);
goto error;
}
x->curlft.bytes += skb->len;
x->curlft.packets++;
x->lastused = ktime_get_real_seconds();
spin_unlock_bh(&x->lock);
skb_dst_force(skb);
if (!skb_dst(skb)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
err = -EHOSTUNREACH;
goto error_nolock;
}
if (xfrm_offload(skb)) {
x->type_offload->encap(x, skb);
} else {
/* Inner headers are invalid now. */
skb->encapsulation = 0;
err = x->type->output(x, skb);
if (err == -EINPROGRESS)
goto out;
}
resume:
if (err) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEPROTOERROR);
goto error_nolock;
}
dst = skb_dst_pop(skb);
if (!dst) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
err = -EHOSTUNREACH;
goto error_nolock;
}
skb_dst_set(skb, dst);
x = dst->xfrm;
} while (x && !(x->outer_mode.flags & XFRM_MODE_FLAG_TUNNEL));
return 0;
error:
spin_unlock_bh(&x->lock);
error_nolock:
kfree_skb(skb);
out:
return err;
}
int xfrm_output_resume(struct sock *sk, struct sk_buff *skb, int err)
{
struct net *net = xs_net(skb_dst(skb)->xfrm);
while (likely((err = xfrm_output_one(skb, err)) == 0)) {
nf_reset_ct(skb);
err = skb_dst(skb)->ops->local_out(net, sk, skb);
if (unlikely(err != 1))
goto out;
if (!skb_dst(skb)->xfrm)
return dst_output(net, sk, skb);
err = nf_hook(skb_dst(skb)->ops->family,
NF_INET_POST_ROUTING, net, sk, skb,
NULL, skb_dst(skb)->dev, xfrm_output2);
if (unlikely(err != 1))
goto out;
}
if (err == -EINPROGRESS)
err = 0;
out:
return err;
}
EXPORT_SYMBOL_GPL(xfrm_output_resume);
static int xfrm_dev_direct_output(struct sock *sk, struct xfrm_state *x,
struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct net *net = xs_net(x);
int err;
dst = skb_dst_pop(skb);
if (!dst) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
kfree_skb(skb);
return -EHOSTUNREACH;
}
skb_dst_set(skb, dst);
nf_reset_ct(skb);
err = skb_dst(skb)->ops->local_out(net, sk, skb);
if (unlikely(err != 1)) {
kfree_skb(skb);
return err;
}
/* In transport mode, network destination is
* directly reachable, while in tunnel mode,
* inner packet network may not be. In packet
* offload type, HW is responsible for hard
* header packet mangling so directly xmit skb
* to netdevice.
*/
skb->dev = x->xso.dev;
__skb_push(skb, skb->dev->hard_header_len);
return dev_queue_xmit(skb);
}
static int xfrm_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{
return xfrm_output_resume(sk, skb, 1);
}
static int xfrm_output_gso(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct sk_buff *segs, *nskb;
BUILD_BUG_ON(sizeof(*IPCB(skb)) > SKB_GSO_CB_OFFSET);
BUILD_BUG_ON(sizeof(*IP6CB(skb)) > SKB_GSO_CB_OFFSET);
segs = skb_gso_segment(skb, 0);
kfree_skb(skb);
if (IS_ERR(segs))
return PTR_ERR(segs);
if (segs == NULL)
return -EINVAL;
skb_list_walk_safe(segs, segs, nskb) {
int err;
skb_mark_not_on_list(segs);
err = xfrm_output2(net, sk, segs);
if (unlikely(err)) {
kfree_skb_list(nskb);
return err;
}
}
return 0;
}
/* For partial checksum offload, the outer header checksum is calculated
* by software and the inner header checksum is calculated by hardware.
* This requires hardware to know the inner packet type to calculate
* the inner header checksum. Save inner ip protocol here to avoid
* traversing the packet in the vendor's xmit code.
* For IPsec tunnel mode save the ip protocol from the IP header of the
* plain text packet. Otherwise If the encap type is IPIP, just save
* skb->inner_ipproto in any other case get the ip protocol from the IP
* header.
*/
static void xfrm_get_inner_ipproto(struct sk_buff *skb, struct xfrm_state *x)
{
struct xfrm_offload *xo = xfrm_offload(skb);
const struct ethhdr *eth;
if (!xo)
return;
if (x->outer_mode.encap == XFRM_MODE_TUNNEL) {
switch (skb_dst(skb)->ops->family) {
case AF_INET:
xo->inner_ipproto = ip_hdr(skb)->protocol;
break;
case AF_INET6:
xo->inner_ipproto = ipv6_hdr(skb)->nexthdr;
break;
default:
break;
}
return;
}
if (x->outer_mode.encap == XFRM_MODE_IPTFS) {
xo->inner_ipproto = IPPROTO_AGGFRAG;
return;
}
/* non-Tunnel Mode */
if (!skb->encapsulation)
return;
if (skb->inner_protocol_type == ENCAP_TYPE_IPPROTO) {
xo->inner_ipproto = skb->inner_ipproto;
return;
}
if (skb->inner_protocol_type != ENCAP_TYPE_ETHER)
return;
eth = (struct ethhdr *)skb_inner_mac_header(skb);
switch (ntohs(eth->h_proto)) {
case ETH_P_IPV6:
xo->inner_ipproto = inner_ipv6_hdr(skb)->nexthdr;
break;
case ETH_P_IP:
xo->inner_ipproto = inner_ip_hdr(skb)->protocol;
break;
}
}
int xfrm_output(struct sock *sk, struct sk_buff *skb)
{
struct net *net = dev_net(skb_dst(skb)->dev);
struct xfrm_state *x = skb_dst(skb)->xfrm;
int family;
int err;
family = (x->xso.type != XFRM_DEV_OFFLOAD_PACKET) ? x->outer_mode.family
: skb_dst(skb)->ops->family;
switch (family) {
case AF_INET:
memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED;
break;
case AF_INET6:
memset(IP6CB(skb), 0, sizeof(*IP6CB(skb)));
IP6CB(skb)->flags |= IP6SKB_XFRM_TRANSFORMED;
break;
}
if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET) {
if (!xfrm_dev_offload_ok(skb, x)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
kfree_skb(skb);
return -EHOSTUNREACH;
}
/* Exclusive direct xmit for tunnel mode, as
* some filtering or matching rules may apply
* in transport mode.
* Locally generated packets also require
* the normal XFRM path for L2 header setup,
* as the hardware needs the L2 header to match
* for encryption, so skip direct output as well.
*/
if (x->props.mode == XFRM_MODE_TUNNEL && !skb->sk)
return xfrm_dev_direct_output(sk, x, skb);
return xfrm_output_resume(sk, skb, 0);
}
secpath_reset(skb);
if (xfrm_dev_offload_ok(skb, x)) {
struct sec_path *sp;
sp = secpath_set(skb);
if (!sp) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
kfree_skb(skb);
return -ENOMEM;
}
sp->olen++;
sp->xvec[sp->len++] = x;
xfrm_state_hold(x);
xfrm_get_inner_ipproto(skb, x);
skb->encapsulation = 1;
if (skb_is_gso(skb)) {
if (skb->inner_protocol && x->props.mode == XFRM_MODE_TUNNEL)
return xfrm_output_gso(net, sk, skb);
skb_shinfo(skb)->gso_type |= SKB_GSO_ESP;
goto out;
}
if (x->xso.dev && x->xso.dev->features & NETIF_F_HW_ESP_TX_CSUM)
goto out;
} else {
if (skb_is_gso(skb))
return xfrm_output_gso(net, sk, skb);
}
if (skb->ip_summed == CHECKSUM_PARTIAL) {
err = skb_checksum_help(skb);
if (err) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
kfree_skb(skb);
return err;
}
}
out:
return xfrm_output2(net, sk, skb);
}
EXPORT_SYMBOL_GPL(xfrm_output);
int xfrm4_tunnel_check_size(struct sk_buff *skb)
{
int mtu, ret = 0;
if (IPCB(skb)->flags & IPSKB_XFRM_TUNNEL_SIZE)
goto out;
if (!(ip_hdr(skb)->frag_off & htons(IP_DF)) || skb->ignore_df)
goto out;
mtu = dst_mtu(skb_dst(skb));
if ((!skb_is_gso(skb) && skb->len > mtu) ||
(skb_is_gso(skb) &&
!skb_gso_validate_network_len(skb, ip_skb_dst_mtu(skb->sk, skb)))) {
skb->protocol = htons(ETH_P_IP);
if (skb->sk && sk_fullsock(skb->sk))
xfrm_local_error(skb, mtu);
else
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_FRAG_NEEDED, htonl(mtu));
ret = -EMSGSIZE;
}
out:
return ret;
}
EXPORT_SYMBOL_GPL(xfrm4_tunnel_check_size);
static int xfrm4_extract_output(struct xfrm_state *x, struct sk_buff *skb)
{
int err;
if (x->outer_mode.encap == XFRM_MODE_BEET &&
ip_is_fragment(ip_hdr(skb))) {
net_warn_ratelimited("BEET mode doesn't support inner IPv4 fragments\n");
return -EAFNOSUPPORT;
}
err = xfrm4_tunnel_check_size(skb);
if (err)
return err;
XFRM_MODE_SKB_CB(skb)->protocol = ip_hdr(skb)->protocol;
xfrm4_extract_header(skb);
return 0;
}
#if IS_ENABLED(CONFIG_IPV6)
int xfrm6_tunnel_check_size(struct sk_buff *skb)
{
int mtu, ret = 0;
struct dst_entry *dst = skb_dst(skb);
struct sock *sk = skb_to_full_sk(skb);
if (skb->ignore_df)
goto out;
mtu = dst_mtu(dst);
if (mtu < IPV6_MIN_MTU)
mtu = IPV6_MIN_MTU;
if ((!skb_is_gso(skb) && skb->len > mtu) ||
(skb_is_gso(skb) &&
!skb_gso_validate_network_len(skb, ip6_skb_dst_mtu(skb)))) {
skb->dev = dst->dev;
skb->protocol = htons(ETH_P_IPV6);
if (xfrm6_local_dontfrag(sk))
xfrm6_local_rxpmtu(skb, mtu);
else if (sk)
xfrm_local_error(skb, mtu);
else
icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
ret = -EMSGSIZE;
}
out:
return ret;
}
EXPORT_SYMBOL_GPL(xfrm6_tunnel_check_size);
#endif
static int xfrm6_extract_output(struct xfrm_state *x, struct sk_buff *skb)
{
#if IS_ENABLED(CONFIG_IPV6)
int err;
err = xfrm6_tunnel_check_size(skb);
if (err)
return err;
XFRM_MODE_SKB_CB(skb)->protocol = ipv6_hdr(skb)->nexthdr;
xfrm6_extract_header(skb);
return 0;
#else
WARN_ON_ONCE(1);
return -EAFNOSUPPORT;
#endif
}
static int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb)
{
switch (skb->protocol) {
case htons(ETH_P_IP):
return xfrm4_extract_output(x, skb);
case htons(ETH_P_IPV6):
return xfrm6_extract_output(x, skb);
}
return -EAFNOSUPPORT;
}
void xfrm_local_error(struct sk_buff *skb, int mtu)
{
unsigned int proto;
struct xfrm_state_afinfo *afinfo;
if (skb->protocol == htons(ETH_P_IP))
proto = AF_INET;
else if (skb->protocol == htons(ETH_P_IPV6) &&
skb->sk->sk_family == AF_INET6)
proto = AF_INET6;
else
return;
afinfo = xfrm_state_get_afinfo(proto);
if (afinfo) {
afinfo->local_error(skb, mtu);
rcu_read_unlock();
}
}
EXPORT_SYMBOL_GPL(xfrm_local_error);
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* xfrm_proc.c
*
* Copyright (C)2006-2007 USAGI/WIDE Project
*
* Authors: Masahide NAKAMURA <nakam@linux-ipv6.org>
*/
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/export.h>
#include <net/snmp.h>
#include <net/xfrm.h>
static const struct snmp_mib xfrm_mib_list[] = {
SNMP_MIB_ITEM("XfrmInError", LINUX_MIB_XFRMINERROR),
SNMP_MIB_ITEM("XfrmInBufferError", LINUX_MIB_XFRMINBUFFERERROR),
SNMP_MIB_ITEM("XfrmInHdrError", LINUX_MIB_XFRMINHDRERROR),
SNMP_MIB_ITEM("XfrmInNoStates", LINUX_MIB_XFRMINNOSTATES),
SNMP_MIB_ITEM("XfrmInStateProtoError", LINUX_MIB_XFRMINSTATEPROTOERROR),
SNMP_MIB_ITEM("XfrmInStateModeError", LINUX_MIB_XFRMINSTATEMODEERROR),
SNMP_MIB_ITEM("XfrmInStateSeqError", LINUX_MIB_XFRMINSTATESEQERROR),
SNMP_MIB_ITEM("XfrmInStateExpired", LINUX_MIB_XFRMINSTATEEXPIRED),
SNMP_MIB_ITEM("XfrmInStateMismatch", LINUX_MIB_XFRMINSTATEMISMATCH),
SNMP_MIB_ITEM("XfrmInStateInvalid", LINUX_MIB_XFRMINSTATEINVALID),
SNMP_MIB_ITEM("XfrmInTmplMismatch", LINUX_MIB_XFRMINTMPLMISMATCH),
SNMP_MIB_ITEM("XfrmInNoPols", LINUX_MIB_XFRMINNOPOLS),
SNMP_MIB_ITEM("XfrmInPolBlock", LINUX_MIB_XFRMINPOLBLOCK),
SNMP_MIB_ITEM("XfrmInPolError", LINUX_MIB_XFRMINPOLERROR),
SNMP_MIB_ITEM("XfrmOutError", LINUX_MIB_XFRMOUTERROR),
SNMP_MIB_ITEM("XfrmOutBundleGenError", LINUX_MIB_XFRMOUTBUNDLEGENERROR),
SNMP_MIB_ITEM("XfrmOutBundleCheckError", LINUX_MIB_XFRMOUTBUNDLECHECKERROR),
SNMP_MIB_ITEM("XfrmOutNoStates", LINUX_MIB_XFRMOUTNOSTATES),
SNMP_MIB_ITEM("XfrmOutStateProtoError", LINUX_MIB_XFRMOUTSTATEPROTOERROR),
SNMP_MIB_ITEM("XfrmOutStateModeError", LINUX_MIB_XFRMOUTSTATEMODEERROR),
SNMP_MIB_ITEM("XfrmOutStateSeqError", LINUX_MIB_XFRMOUTSTATESEQERROR),
SNMP_MIB_ITEM("XfrmOutStateExpired", LINUX_MIB_XFRMOUTSTATEEXPIRED),
SNMP_MIB_ITEM("XfrmOutPolBlock", LINUX_MIB_XFRMOUTPOLBLOCK),
SNMP_MIB_ITEM("XfrmOutPolDead", LINUX_MIB_XFRMOUTPOLDEAD),
SNMP_MIB_ITEM("XfrmOutPolError", LINUX_MIB_XFRMOUTPOLERROR),
SNMP_MIB_ITEM("XfrmFwdHdrError", LINUX_MIB_XFRMFWDHDRERROR),
SNMP_MIB_ITEM("XfrmOutStateInvalid", LINUX_MIB_XFRMOUTSTATEINVALID),
SNMP_MIB_ITEM("XfrmAcquireError", LINUX_MIB_XFRMACQUIREERROR),
SNMP_MIB_ITEM("XfrmOutStateDirError", LINUX_MIB_XFRMOUTSTATEDIRERROR),
SNMP_MIB_ITEM("XfrmInStateDirError", LINUX_MIB_XFRMINSTATEDIRERROR),
SNMP_MIB_ITEM("XfrmInIptfsError", LINUX_MIB_XFRMINIPTFSERROR),
SNMP_MIB_ITEM("XfrmOutNoQueueSpace", LINUX_MIB_XFRMOUTNOQSPACE),
};
static int xfrm_statistics_seq_show(struct seq_file *seq, void *v)
{
unsigned long buff[ARRAY_SIZE(xfrm_mib_list)];
const int cnt = ARRAY_SIZE(xfrm_mib_list);
struct net *net = seq->private;
int i;
memset(buff, 0, sizeof(buff));
xfrm_state_update_stats(net);
snmp_get_cpu_field_batch_cnt(buff, xfrm_mib_list, cnt,
net->mib.xfrm_statistics);
for (i = 0; i < cnt; i++)
seq_printf(seq, "%-24s\t%lu\n", xfrm_mib_list[i].name,
buff[i]);
return 0;
}
int __net_init xfrm_proc_init(struct net *net)
{
if (!proc_create_net_single("xfrm_stat", 0444, net->proc_net,
xfrm_statistics_seq_show, NULL))
return -ENOMEM;
return 0;
}
void xfrm_proc_fini(struct net *net)
{
remove_proc_entry("xfrm_stat", net->proc_net);
}
+799
View File
@@ -0,0 +1,799 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* xfrm_replay.c - xfrm replay detection, derived from xfrm_state.c.
*
* Copyright (C) 2010 secunet Security Networks AG
* Copyright (C) 2010 Steffen Klassert <steffen.klassert@secunet.com>
*/
#include <linux/export.h>
#include <net/xfrm.h>
u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq)
{
u32 seq, seq_hi, bottom;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
if (!(x->props.flags & XFRM_STATE_ESN))
return 0;
seq = ntohl(net_seq);
seq_hi = replay_esn->seq_hi;
bottom = replay_esn->seq - replay_esn->replay_window + 1;
if (likely(replay_esn->seq >= replay_esn->replay_window - 1)) {
/* A. same subspace */
if (unlikely(seq < bottom))
seq_hi++;
} else {
/* B. window spans two subspaces */
if (unlikely(seq >= bottom))
seq_hi--;
}
return seq_hi;
}
EXPORT_SYMBOL(xfrm_replay_seqhi);
static void xfrm_replay_notify_bmp(struct xfrm_state *x, int event);
static void xfrm_replay_notify_esn(struct xfrm_state *x, int event);
void xfrm_replay_notify(struct xfrm_state *x, int event)
{
struct km_event c;
/* we send notify messages in case
* 1. we updated on of the sequence numbers, and the seqno difference
* is at least x->replay_maxdiff, in this case we also update the
* timeout of our timer function
* 2. if x->replay_maxage has elapsed since last update,
* and there were changes
*
* The state structure must be locked!
*/
switch (x->repl_mode) {
case XFRM_REPLAY_MODE_LEGACY:
break;
case XFRM_REPLAY_MODE_BMP:
xfrm_replay_notify_bmp(x, event);
return;
case XFRM_REPLAY_MODE_ESN:
xfrm_replay_notify_esn(x, event);
return;
}
switch (event) {
case XFRM_REPLAY_UPDATE:
if (!x->replay_maxdiff ||
((x->replay.seq - x->preplay.seq < x->replay_maxdiff) &&
(x->replay.oseq - x->preplay.oseq < x->replay_maxdiff))) {
if (x->xflags & XFRM_TIME_DEFER)
event = XFRM_REPLAY_TIMEOUT;
else
return;
}
break;
case XFRM_REPLAY_TIMEOUT:
if (memcmp(&x->replay, &x->preplay,
sizeof(struct xfrm_replay_state)) == 0) {
x->xflags |= XFRM_TIME_DEFER;
return;
}
break;
}
memcpy(&x->preplay, &x->replay, sizeof(struct xfrm_replay_state));
c.event = XFRM_MSG_NEWAE;
c.data.aevent = event;
km_state_notify(x, &c);
if (x->replay_maxage &&
!mod_timer(&x->rtimer, jiffies + x->replay_maxage))
x->xflags &= ~XFRM_TIME_DEFER;
}
static int __xfrm_replay_overflow(struct xfrm_state *x, struct sk_buff *skb)
{
int err = 0;
struct net *net = xs_net(x);
if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
XFRM_SKB_CB(skb)->seq.output.low = ++x->replay.oseq;
XFRM_SKB_CB(skb)->seq.output.hi = 0;
if (unlikely(x->replay.oseq == 0) &&
!(x->props.extra_flags & XFRM_SA_XFLAG_OSEQ_MAY_WRAP)) {
x->replay.oseq--;
xfrm_audit_state_replay_overflow(x, skb);
err = -EOVERFLOW;
return err;
}
if (xfrm_aevent_is_on(net))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
return err;
}
static int xfrm_replay_check_legacy(struct xfrm_state *x,
struct sk_buff *skb, __be32 net_seq)
{
u32 diff;
u32 seq = ntohl(net_seq);
if (!x->props.replay_window)
return 0;
if (unlikely(seq == 0))
goto err;
if (likely(seq > x->replay.seq))
return 0;
diff = x->replay.seq - seq;
if (diff >= x->props.replay_window) {
x->stats.replay_window++;
goto err;
}
if (x->replay.bitmap & (1U << diff)) {
x->stats.replay++;
goto err;
}
return 0;
err:
xfrm_audit_state_replay(x, skb, net_seq);
return -EINVAL;
}
static void xfrm_replay_advance_bmp(struct xfrm_state *x, __be32 net_seq);
static void xfrm_replay_advance_esn(struct xfrm_state *x, __be32 net_seq);
void xfrm_replay_advance(struct xfrm_state *x, __be32 net_seq)
{
u32 diff, seq;
switch (x->repl_mode) {
case XFRM_REPLAY_MODE_LEGACY:
break;
case XFRM_REPLAY_MODE_BMP:
return xfrm_replay_advance_bmp(x, net_seq);
case XFRM_REPLAY_MODE_ESN:
return xfrm_replay_advance_esn(x, net_seq);
}
if (!x->props.replay_window)
return;
seq = ntohl(net_seq);
if (seq > x->replay.seq) {
diff = seq - x->replay.seq;
if (diff < x->props.replay_window)
x->replay.bitmap = ((x->replay.bitmap) << diff) | 1;
else
x->replay.bitmap = 1;
x->replay.seq = seq;
} else {
diff = x->replay.seq - seq;
x->replay.bitmap |= (1U << diff);
}
if (xfrm_aevent_is_on(xs_net(x)))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
static int xfrm_replay_overflow_bmp(struct xfrm_state *x, struct sk_buff *skb)
{
int err = 0;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
struct net *net = xs_net(x);
if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
XFRM_SKB_CB(skb)->seq.output.low = ++replay_esn->oseq;
XFRM_SKB_CB(skb)->seq.output.hi = 0;
if (unlikely(replay_esn->oseq == 0) &&
!(x->props.extra_flags & XFRM_SA_XFLAG_OSEQ_MAY_WRAP)) {
replay_esn->oseq--;
xfrm_audit_state_replay_overflow(x, skb);
err = -EOVERFLOW;
return err;
}
if (xfrm_aevent_is_on(net))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
return err;
}
static int xfrm_replay_check_bmp(struct xfrm_state *x,
struct sk_buff *skb, __be32 net_seq)
{
unsigned int bitnr, nr;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
u32 pos;
u32 seq = ntohl(net_seq);
u32 diff = replay_esn->seq - seq;
if (!replay_esn->replay_window)
return 0;
if (unlikely(seq == 0))
goto err;
if (likely(seq > replay_esn->seq))
return 0;
if (diff >= replay_esn->replay_window) {
x->stats.replay_window++;
goto err;
}
pos = (replay_esn->seq - 1) % replay_esn->replay_window;
if (pos >= diff)
bitnr = (pos - diff) % replay_esn->replay_window;
else
bitnr = replay_esn->replay_window - (diff - pos);
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
if (replay_esn->bmp[nr] & (1U << bitnr))
goto err_replay;
return 0;
err_replay:
x->stats.replay++;
err:
xfrm_audit_state_replay(x, skb, net_seq);
return -EINVAL;
}
static void xfrm_replay_advance_bmp(struct xfrm_state *x, __be32 net_seq)
{
unsigned int bitnr, nr, i;
u32 diff;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
u32 seq = ntohl(net_seq);
u32 pos;
if (!replay_esn->replay_window)
return;
pos = (replay_esn->seq - 1) % replay_esn->replay_window;
if (seq > replay_esn->seq) {
diff = seq - replay_esn->seq;
if (diff < replay_esn->replay_window) {
for (i = 1; i < diff; i++) {
bitnr = (pos + i) % replay_esn->replay_window;
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] &= ~(1U << bitnr);
}
} else {
nr = (replay_esn->replay_window - 1) >> 5;
for (i = 0; i <= nr; i++)
replay_esn->bmp[i] = 0;
}
bitnr = (pos + diff) % replay_esn->replay_window;
replay_esn->seq = seq;
} else {
diff = replay_esn->seq - seq;
if (pos >= diff)
bitnr = (pos - diff) % replay_esn->replay_window;
else
bitnr = replay_esn->replay_window - (diff - pos);
}
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] |= (1U << bitnr);
if (xfrm_aevent_is_on(xs_net(x)))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
static void xfrm_replay_notify_bmp(struct xfrm_state *x, int event)
{
struct km_event c;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
struct xfrm_replay_state_esn *preplay_esn = x->preplay_esn;
/* we send notify messages in case
* 1. we updated on of the sequence numbers, and the seqno difference
* is at least x->replay_maxdiff, in this case we also update the
* timeout of our timer function
* 2. if x->replay_maxage has elapsed since last update,
* and there were changes
*
* The state structure must be locked!
*/
switch (event) {
case XFRM_REPLAY_UPDATE:
if (!x->replay_maxdiff ||
((replay_esn->seq - preplay_esn->seq < x->replay_maxdiff) &&
(replay_esn->oseq - preplay_esn->oseq
< x->replay_maxdiff))) {
if (x->xflags & XFRM_TIME_DEFER)
event = XFRM_REPLAY_TIMEOUT;
else
return;
}
break;
case XFRM_REPLAY_TIMEOUT:
if (memcmp(x->replay_esn, x->preplay_esn,
xfrm_replay_state_esn_len(replay_esn)) == 0) {
x->xflags |= XFRM_TIME_DEFER;
return;
}
break;
}
memcpy(x->preplay_esn, x->replay_esn,
xfrm_replay_state_esn_len(replay_esn));
c.event = XFRM_MSG_NEWAE;
c.data.aevent = event;
km_state_notify(x, &c);
if (x->replay_maxage &&
!mod_timer(&x->rtimer, jiffies + x->replay_maxage))
x->xflags &= ~XFRM_TIME_DEFER;
}
static void xfrm_replay_notify_esn(struct xfrm_state *x, int event)
{
u32 seq_diff, oseq_diff;
struct km_event c;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
struct xfrm_replay_state_esn *preplay_esn = x->preplay_esn;
/* we send notify messages in case
* 1. we updated on of the sequence numbers, and the seqno difference
* is at least x->replay_maxdiff, in this case we also update the
* timeout of our timer function
* 2. if x->replay_maxage has elapsed since last update,
* and there were changes
*
* The state structure must be locked!
*/
switch (event) {
case XFRM_REPLAY_UPDATE:
if (x->replay_maxdiff) {
if (replay_esn->seq_hi == preplay_esn->seq_hi)
seq_diff = replay_esn->seq - preplay_esn->seq;
else
seq_diff = ~preplay_esn->seq + replay_esn->seq
+ 1;
if (replay_esn->oseq_hi == preplay_esn->oseq_hi)
oseq_diff = replay_esn->oseq
- preplay_esn->oseq;
else
oseq_diff = ~preplay_esn->oseq
+ replay_esn->oseq + 1;
if (seq_diff >= x->replay_maxdiff ||
oseq_diff >= x->replay_maxdiff)
break;
}
if (x->xflags & XFRM_TIME_DEFER)
event = XFRM_REPLAY_TIMEOUT;
else
return;
break;
case XFRM_REPLAY_TIMEOUT:
if (memcmp(x->replay_esn, x->preplay_esn,
xfrm_replay_state_esn_len(replay_esn)) == 0) {
x->xflags |= XFRM_TIME_DEFER;
return;
}
break;
}
memcpy(x->preplay_esn, x->replay_esn,
xfrm_replay_state_esn_len(replay_esn));
c.event = XFRM_MSG_NEWAE;
c.data.aevent = event;
km_state_notify(x, &c);
if (x->replay_maxage &&
!mod_timer(&x->rtimer, jiffies + x->replay_maxage))
x->xflags &= ~XFRM_TIME_DEFER;
}
static int xfrm_replay_overflow_esn(struct xfrm_state *x, struct sk_buff *skb)
{
int err = 0;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
struct net *net = xs_net(x);
if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
XFRM_SKB_CB(skb)->seq.output.low = ++replay_esn->oseq;
XFRM_SKB_CB(skb)->seq.output.hi = replay_esn->oseq_hi;
if (unlikely(replay_esn->oseq == 0)) {
XFRM_SKB_CB(skb)->seq.output.hi = ++replay_esn->oseq_hi;
if (replay_esn->oseq_hi == 0) {
replay_esn->oseq--;
replay_esn->oseq_hi--;
xfrm_audit_state_replay_overflow(x, skb);
err = -EOVERFLOW;
return err;
}
}
if (xfrm_aevent_is_on(net))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
return err;
}
static int xfrm_replay_check_esn(struct xfrm_state *x,
struct sk_buff *skb, __be32 net_seq)
{
unsigned int bitnr, nr;
u32 diff;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
u32 pos;
u32 seq = ntohl(net_seq);
u32 wsize = replay_esn->replay_window;
u32 top = replay_esn->seq;
u32 bottom = top - wsize + 1;
if (!wsize)
return 0;
if (unlikely(seq == 0 && replay_esn->seq_hi == 0 &&
(replay_esn->seq < replay_esn->replay_window - 1)))
goto err;
diff = top - seq;
if (likely(top >= wsize - 1)) {
/* A. same subspace */
if (likely(seq > top) || seq < bottom)
return 0;
} else {
/* B. window spans two subspaces */
if (likely(seq > top && seq < bottom))
return 0;
if (seq >= bottom)
diff = ~seq + top + 1;
}
if (diff >= replay_esn->replay_window) {
x->stats.replay_window++;
goto err;
}
pos = (replay_esn->seq - 1) % replay_esn->replay_window;
if (pos >= diff)
bitnr = (pos - diff) % replay_esn->replay_window;
else
bitnr = replay_esn->replay_window - (diff - pos);
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
if (replay_esn->bmp[nr] & (1U << bitnr))
goto err_replay;
return 0;
err_replay:
x->stats.replay++;
err:
xfrm_audit_state_replay(x, skb, net_seq);
return -EINVAL;
}
int xfrm_replay_check(struct xfrm_state *x,
struct sk_buff *skb, __be32 net_seq)
{
switch (x->repl_mode) {
case XFRM_REPLAY_MODE_LEGACY:
break;
case XFRM_REPLAY_MODE_BMP:
return xfrm_replay_check_bmp(x, skb, net_seq);
case XFRM_REPLAY_MODE_ESN:
return xfrm_replay_check_esn(x, skb, net_seq);
}
return xfrm_replay_check_legacy(x, skb, net_seq);
}
static int xfrm_replay_recheck_esn(struct xfrm_state *x,
struct sk_buff *skb, __be32 net_seq)
{
if (unlikely(XFRM_SKB_CB(skb)->seq.input.hi !=
htonl(xfrm_replay_seqhi(x, net_seq)))) {
x->stats.replay_window++;
return -EINVAL;
}
return xfrm_replay_check_esn(x, skb, net_seq);
}
int xfrm_replay_recheck(struct xfrm_state *x,
struct sk_buff *skb, __be32 net_seq)
{
switch (x->repl_mode) {
case XFRM_REPLAY_MODE_LEGACY:
break;
case XFRM_REPLAY_MODE_BMP:
/* no special recheck treatment */
return xfrm_replay_check_bmp(x, skb, net_seq);
case XFRM_REPLAY_MODE_ESN:
return xfrm_replay_recheck_esn(x, skb, net_seq);
}
return xfrm_replay_check_legacy(x, skb, net_seq);
}
static void xfrm_replay_advance_esn(struct xfrm_state *x, __be32 net_seq)
{
unsigned int bitnr, nr, i;
int wrap;
u32 diff, pos, seq, seq_hi;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
if (!replay_esn->replay_window)
return;
seq = ntohl(net_seq);
pos = (replay_esn->seq - 1) % replay_esn->replay_window;
seq_hi = xfrm_replay_seqhi(x, net_seq);
wrap = seq_hi - replay_esn->seq_hi;
if ((!wrap && seq > replay_esn->seq) || wrap > 0) {
if (likely(!wrap))
diff = seq - replay_esn->seq;
else
diff = ~replay_esn->seq + seq + 1;
if (diff < replay_esn->replay_window) {
for (i = 1; i < diff; i++) {
bitnr = (pos + i) % replay_esn->replay_window;
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] &= ~(1U << bitnr);
}
} else {
nr = (replay_esn->replay_window - 1) >> 5;
for (i = 0; i <= nr; i++)
replay_esn->bmp[i] = 0;
}
bitnr = (pos + diff) % replay_esn->replay_window;
replay_esn->seq = seq;
if (unlikely(wrap > 0))
replay_esn->seq_hi++;
} else {
diff = replay_esn->seq - seq;
if (pos >= diff)
bitnr = (pos - diff) % replay_esn->replay_window;
else
bitnr = replay_esn->replay_window - (diff - pos);
}
xfrm_dev_state_advance_esn(x);
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] |= (1U << bitnr);
if (xfrm_aevent_is_on(xs_net(x)))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
#ifdef CONFIG_XFRM_OFFLOAD
static int xfrm_replay_overflow_offload(struct xfrm_state *x, struct sk_buff *skb)
{
int err = 0;
struct net *net = xs_net(x);
struct xfrm_offload *xo = xfrm_offload(skb);
__u32 oseq = x->replay.oseq;
if (!xo)
return __xfrm_replay_overflow(x, skb);
if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
if (!skb_is_gso(skb)) {
XFRM_SKB_CB(skb)->seq.output.low = ++oseq;
xo->seq.low = oseq;
} else {
XFRM_SKB_CB(skb)->seq.output.low = oseq + 1;
xo->seq.low = oseq + 1;
oseq += skb_shinfo(skb)->gso_segs;
}
XFRM_SKB_CB(skb)->seq.output.hi = 0;
xo->seq.hi = 0;
if (unlikely(oseq < x->replay.oseq) &&
!(x->props.extra_flags & XFRM_SA_XFLAG_OSEQ_MAY_WRAP)) {
xfrm_audit_state_replay_overflow(x, skb);
err = -EOVERFLOW;
return err;
}
x->replay.oseq = oseq;
if (xfrm_aevent_is_on(net))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
return err;
}
static int xfrm_replay_overflow_offload_bmp(struct xfrm_state *x, struct sk_buff *skb)
{
int err = 0;
struct xfrm_offload *xo = xfrm_offload(skb);
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
struct net *net = xs_net(x);
__u32 oseq = replay_esn->oseq;
if (!xo)
return xfrm_replay_overflow_bmp(x, skb);
if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
if (!skb_is_gso(skb)) {
XFRM_SKB_CB(skb)->seq.output.low = ++oseq;
xo->seq.low = oseq;
} else {
XFRM_SKB_CB(skb)->seq.output.low = oseq + 1;
xo->seq.low = oseq + 1;
oseq += skb_shinfo(skb)->gso_segs;
}
XFRM_SKB_CB(skb)->seq.output.hi = 0;
xo->seq.hi = 0;
if (unlikely(oseq < replay_esn->oseq) &&
!(x->props.extra_flags & XFRM_SA_XFLAG_OSEQ_MAY_WRAP)) {
xfrm_audit_state_replay_overflow(x, skb);
err = -EOVERFLOW;
return err;
} else {
replay_esn->oseq = oseq;
}
if (xfrm_aevent_is_on(net))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
return err;
}
static int xfrm_replay_overflow_offload_esn(struct xfrm_state *x, struct sk_buff *skb)
{
int err = 0;
struct xfrm_offload *xo = xfrm_offload(skb);
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
struct net *net = xs_net(x);
__u32 oseq = replay_esn->oseq;
__u32 oseq_hi = replay_esn->oseq_hi;
if (!xo)
return xfrm_replay_overflow_esn(x, skb);
if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
if (!skb_is_gso(skb)) {
XFRM_SKB_CB(skb)->seq.output.low = ++oseq;
XFRM_SKB_CB(skb)->seq.output.hi = oseq_hi;
xo->seq.low = oseq;
xo->seq.hi = oseq_hi;
} else {
XFRM_SKB_CB(skb)->seq.output.low = oseq + 1;
XFRM_SKB_CB(skb)->seq.output.hi = oseq_hi;
xo->seq.low = oseq + 1;
xo->seq.hi = oseq_hi;
oseq += skb_shinfo(skb)->gso_segs;
}
if (unlikely(oseq < replay_esn->oseq)) {
replay_esn->oseq_hi = ++oseq_hi;
if (xo->seq.low < replay_esn->oseq) {
XFRM_SKB_CB(skb)->seq.output.hi = oseq_hi;
xo->seq.hi = oseq_hi;
}
if (replay_esn->oseq_hi == 0) {
replay_esn->oseq--;
replay_esn->oseq_hi--;
xfrm_audit_state_replay_overflow(x, skb);
err = -EOVERFLOW;
return err;
}
}
replay_esn->oseq = oseq;
xfrm_dev_state_advance_esn(x);
if (xfrm_aevent_is_on(net))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
return err;
}
int xfrm_replay_overflow(struct xfrm_state *x, struct sk_buff *skb)
{
switch (x->repl_mode) {
case XFRM_REPLAY_MODE_LEGACY:
break;
case XFRM_REPLAY_MODE_BMP:
return xfrm_replay_overflow_offload_bmp(x, skb);
case XFRM_REPLAY_MODE_ESN:
return xfrm_replay_overflow_offload_esn(x, skb);
}
return xfrm_replay_overflow_offload(x, skb);
}
#else
int xfrm_replay_overflow(struct xfrm_state *x, struct sk_buff *skb)
{
switch (x->repl_mode) {
case XFRM_REPLAY_MODE_LEGACY:
break;
case XFRM_REPLAY_MODE_BMP:
return xfrm_replay_overflow_bmp(x, skb);
case XFRM_REPLAY_MODE_ESN:
return xfrm_replay_overflow_esn(x, skb);
}
return __xfrm_replay_overflow(x, skb);
}
#endif
int xfrm_init_replay(struct xfrm_state *x, struct netlink_ext_ack *extack)
{
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
if (replay_esn) {
if (replay_esn->replay_window >
replay_esn->bmp_len * sizeof(__u32) * 8) {
NL_SET_ERR_MSG(extack, "ESN replay window is too large for the chosen bitmap size");
return -EINVAL;
}
if (x->props.flags & XFRM_STATE_ESN) {
if (replay_esn->replay_window == 0 &&
(!x->dir || x->dir == XFRM_SA_DIR_IN)) {
NL_SET_ERR_MSG(extack, "ESN replay window must be > 0");
return -EINVAL;
}
x->repl_mode = XFRM_REPLAY_MODE_ESN;
} else {
x->repl_mode = XFRM_REPLAY_MODE_BMP;
}
} else {
x->repl_mode = XFRM_REPLAY_MODE_LEGACY;
}
return 0;
}
EXPORT_SYMBOL(xfrm_init_replay);
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Unstable XFRM state BPF helpers.
*
* Note that it is allowed to break compatibility for these functions since the
* interface they are exposed through to BPF programs is explicitly unstable.
*/
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/btf_ids.h>
#include <net/xdp.h>
#include <net/xfrm.h>
/* bpf_xfrm_state_opts - Options for XFRM state lookup helpers
*
* Members:
* @error - Out parameter, set for any errors encountered
* Values:
* -EINVAL - netns_id is less than -1
* -EINVAL - opts__sz isn't BPF_XFRM_STATE_OPTS_SZ
* -ENONET - No network namespace found for netns_id
* -ENOENT - No xfrm_state found
* @netns_id - Specify the network namespace for lookup
* Values:
* BPF_F_CURRENT_NETNS (-1)
* Use namespace associated with ctx
* [0, S32_MAX]
* Network Namespace ID
* @mark - XFRM mark to match on
* @daddr - Destination address to match on
* @spi - Security parameter index to match on
* @proto - IP protocol to match on (eg. IPPROTO_ESP)
* @family - Protocol family to match on (AF_INET/AF_INET6)
*/
struct bpf_xfrm_state_opts {
s32 error;
s32 netns_id;
u32 mark;
xfrm_address_t daddr;
__be32 spi;
u8 proto;
u16 family;
};
enum {
BPF_XFRM_STATE_OPTS_SZ = sizeof(struct bpf_xfrm_state_opts),
};
__bpf_kfunc_start_defs();
/* bpf_xdp_get_xfrm_state - Get XFRM state
*
* A `struct xfrm_state *`, if found, must be released with a corresponding
* bpf_xdp_xfrm_state_release.
*
* Parameters:
* @ctx - Pointer to ctx (xdp_md) in XDP program
* Cannot be NULL
* @opts - Options for lookup (documented above)
* Cannot be NULL
* @opts__sz - Length of the bpf_xfrm_state_opts structure
* Must be BPF_XFRM_STATE_OPTS_SZ
*/
__bpf_kfunc struct xfrm_state *
bpf_xdp_get_xfrm_state(struct xdp_md *ctx, struct bpf_xfrm_state_opts *opts, u32 opts__sz)
{
struct xdp_buff *xdp = (struct xdp_buff *)ctx;
struct net *net = dev_net(xdp->rxq->dev);
struct xfrm_state *x;
if (opts__sz < sizeof(opts->error))
return NULL;
if (opts__sz != BPF_XFRM_STATE_OPTS_SZ) {
opts->error = -EINVAL;
return NULL;
}
if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS)) {
opts->error = -EINVAL;
return NULL;
}
if (opts->netns_id >= 0) {
net = get_net_ns_by_id(net, opts->netns_id);
if (unlikely(!net)) {
opts->error = -ENONET;
return NULL;
}
}
x = xfrm_state_lookup(net, opts->mark, &opts->daddr, opts->spi,
opts->proto, opts->family);
if (opts->netns_id >= 0)
put_net(net);
if (!x)
opts->error = -ENOENT;
return x;
}
/* bpf_xdp_xfrm_state_release - Release acquired xfrm_state object
*
* This must be invoked for referenced PTR_TO_BTF_ID, and the verifier rejects
* the program if any references remain in the program in all of the explored
* states.
*
* Parameters:
* @x - Pointer to referenced xfrm_state object, obtained using
* bpf_xdp_get_xfrm_state.
*/
__bpf_kfunc void bpf_xdp_xfrm_state_release(struct xfrm_state *x)
{
xfrm_state_put(x);
}
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(xfrm_state_kfunc_set)
BTF_ID_FLAGS(func, bpf_xdp_get_xfrm_state, KF_RET_NULL | KF_ACQUIRE)
BTF_ID_FLAGS(func, bpf_xdp_xfrm_state_release, KF_RELEASE)
BTF_KFUNCS_END(xfrm_state_kfunc_set)
static const struct btf_kfunc_id_set xfrm_state_xdp_kfunc_set = {
.owner = THIS_MODULE,
.set = &xfrm_state_kfunc_set,
};
int __init register_xfrm_state_bpf(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP,
&xfrm_state_xdp_kfunc_set);
}
+88
View File
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/sysctl.h>
#include <linux/slab.h>
#include <net/net_namespace.h>
#include <net/xfrm.h>
static void __net_init __xfrm_sysctl_init(struct net *net)
{
net->xfrm.sysctl_aevent_etime = XFRM_AE_ETIME;
net->xfrm.sysctl_aevent_rseqth = XFRM_AE_SEQT_SIZE;
net->xfrm.sysctl_larval_drop = 1;
net->xfrm.sysctl_acq_expires = 30;
}
#ifdef CONFIG_SYSCTL
static struct ctl_table xfrm_table[] = {
{
.procname = "xfrm_aevent_etime",
.maxlen = sizeof(u32),
.mode = 0644,
.proc_handler = proc_douintvec
},
{
.procname = "xfrm_aevent_rseqth",
.maxlen = sizeof(u32),
.mode = 0644,
.proc_handler = proc_douintvec
},
{
.procname = "xfrm_larval_drop",
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec
},
{
.procname = "xfrm_acq_expires",
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec
},
};
int __net_init xfrm_sysctl_init(struct net *net)
{
struct ctl_table *table;
size_t table_size = ARRAY_SIZE(xfrm_table);
__xfrm_sysctl_init(net);
table = kmemdup(xfrm_table, sizeof(xfrm_table), GFP_KERNEL);
if (!table)
goto out_kmemdup;
table[0].data = &net->xfrm.sysctl_aevent_etime;
table[1].data = &net->xfrm.sysctl_aevent_rseqth;
table[2].data = &net->xfrm.sysctl_larval_drop;
table[3].data = &net->xfrm.sysctl_acq_expires;
/* Don't export sysctls to unprivileged users */
if (net->user_ns != &init_user_ns)
table_size = 0;
net->xfrm.sysctl_hdr = register_net_sysctl_sz(net, "net/core", table,
table_size);
if (!net->xfrm.sysctl_hdr)
goto out_register;
return 0;
out_register:
kfree(table);
out_kmemdup:
return -ENOMEM;
}
void __net_exit xfrm_sysctl_fini(struct net *net)
{
const struct ctl_table *table;
table = net->xfrm.sysctl_hdr->ctl_table_arg;
unregister_net_sysctl_table(net->xfrm.sysctl_hdr);
kfree(table);
}
#else
int __net_init xfrm_sysctl_init(struct net *net)
{
__xfrm_sysctl_init(net);
return 0;
}
#endif
File diff suppressed because it is too large Load Diff