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
ovpn_pktid_xmit_init(struct ovpn_pktid_xmit * pid)20 void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid)
21 {
22 atomic_set(&pid->seq_num, 1);
23 }
24
ovpn_pktid_recv_init(struct ovpn_pktid_recv * pr)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 */
ovpn_pktid_recv(struct ovpn_pktid_recv * pr,u32 pkt_id,u32 pkt_time)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