xref: /linux/drivers/net/ovpn/pktid.c (revision 0ce92d548b44649a8de706f9bb9e74a4ed2f18a7)
1 // SPDX-License-Identifier: GPL-2.0
2 /*  OpenVPN data channel offload
3  *
4  *  Copyright (C) 2020-2025 OpenVPN, Inc.
5  *
6  *  Author:	Antonio Quartulli <antonio@openvpn.net>
7  *		James Yonan <james@openvpn.net>
8  */
9 
10 #include <linux/atomic.h>
11 #include <linux/jiffies.h>
12 #include <linux/net.h>
13 #include <linux/netdevice.h>
14 #include <linux/types.h>
15 
16 #include "ovpnpriv.h"
17 #include "main.h"
18 #include "pktid.h"
19 
20 void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid)
21 {
22 	atomic_set(&pid->seq_num, 1);
23 }
24 
25 void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr)
26 {
27 	memset(pr, 0, sizeof(*pr));
28 	spin_lock_init(&pr->lock);
29 }
30 
31 /* Packet replay detection.
32  * Allows ID backtrack of up to REPLAY_WINDOW_SIZE - 1.
33  */
34 int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time)
35 {
36 	const unsigned long now = jiffies;
37 	int ret;
38 
39 	/* ID must not be zero */
40 	if (unlikely(pkt_id == 0))
41 		return -EINVAL;
42 
43 	spin_lock_bh(&pr->lock);
44 
45 	/* expire backtracks at or below pr->id after PKTID_RECV_EXPIRE time */
46 	if (unlikely(time_after_eq(now, pr->expire)))
47 		pr->id_floor = pr->id;
48 
49 	/* time changed? */
50 	if (unlikely(pkt_time != pr->time)) {
51 		if (pkt_time > pr->time) {
52 			/* time moved forward, accept */
53 			pr->base = 0;
54 			pr->extent = 0;
55 			pr->id = 0;
56 			pr->time = pkt_time;
57 			pr->id_floor = 0;
58 		} else {
59 			/* time moved backward, reject */
60 			ret = -ETIME;
61 			goto out;
62 		}
63 	}
64 
65 	if (likely(pkt_id == pr->id + 1)) {
66 		/* well-formed ID sequence (incremented by 1) */
67 		pr->base = REPLAY_INDEX(pr->base, -1);
68 		pr->history[pr->base / 8] |= (1 << (pr->base % 8));
69 		if (pr->extent < REPLAY_WINDOW_SIZE)
70 			++pr->extent;
71 		pr->id = pkt_id;
72 	} else if (pkt_id > pr->id) {
73 		/* ID jumped forward by more than one */
74 		const unsigned int delta = pkt_id - pr->id;
75 
76 		if (delta < REPLAY_WINDOW_SIZE) {
77 			unsigned int i;
78 
79 			pr->base = REPLAY_INDEX(pr->base, -delta);
80 			pr->history[pr->base / 8] |= (1 << (pr->base % 8));
81 			pr->extent += delta;
82 			if (pr->extent > REPLAY_WINDOW_SIZE)
83 				pr->extent = REPLAY_WINDOW_SIZE;
84 			for (i = 1; i < delta; ++i) {
85 				unsigned int newb = REPLAY_INDEX(pr->base, i);
86 
87 				pr->history[newb / 8] &= ~BIT(newb % 8);
88 			}
89 		} else {
90 			pr->base = 0;
91 			pr->extent = REPLAY_WINDOW_SIZE;
92 			memset(pr->history, 0, sizeof(pr->history));
93 			pr->history[0] = 1;
94 		}
95 		pr->id = pkt_id;
96 	} else {
97 		/* ID backtrack */
98 		const unsigned int delta = pr->id - pkt_id;
99 
100 		if (delta > pr->max_backtrack)
101 			pr->max_backtrack = delta;
102 		if (delta < pr->extent) {
103 			if (pkt_id > pr->id_floor) {
104 				const unsigned int ri = REPLAY_INDEX(pr->base,
105 								     delta);
106 				u8 *p = &pr->history[ri / 8];
107 				const u8 mask = (1 << (ri % 8));
108 
109 				if (*p & mask) {
110 					ret = -EINVAL;
111 					goto out;
112 				}
113 				*p |= mask;
114 			} else {
115 				ret = -EINVAL;
116 				goto out;
117 			}
118 		} else {
119 			ret = -EINVAL;
120 			goto out;
121 		}
122 	}
123 
124 	pr->expire = now + PKTID_RECV_EXPIRE;
125 	ret = 0;
126 out:
127 	spin_unlock_bh(&pr->lock);
128 	return ret;
129 }
130