aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2016-10-19 01:08:08 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2016-10-19 17:22:13 +0900
commit87427a7ec866c7f8fb829fa07340b3a9b2d7f66f (patch)
treefd6d181b9e4fd82b62ea45377b8af79e1bbbdc12 /src
parenttimers: move constants to header (diff)
downloadwireguard-monolithic-historical-87427a7ec866c7f8fb829fa07340b3a9b2d7f66f.tar.xz
wireguard-monolithic-historical-87427a7ec866c7f8fb829fa07340b3a9b2d7f66f.zip
timers: always delay handshakes for responder
With the prior behavior, when sending a packet, we checked to see if it was about time to start a new handshake, and if we were past a certain time, we started it. For the responder, we made that time a bit further in the future than for the initiator, to prevent the thundering herd problem of them both starting at the same time. However, this was flawed. If both parties stopped communicating after 2.2 minutes, and then one party decided to initiate a TCP connection before the 3 minute mark, the currently open session would be used. However, because it was after the 2.2 minute mark, both peers would try to initiate a handshake upon sending their first packet. The errant flow was as follows: 1. Peer A sends SYN. 2. Peer A sees that his key is getting old and initiates new handshake. 3. Peer B receives SYN and sends ACK. 4. Peer B sees that his key is getting old and initiates new handshake. Since these events happened after the 2.2 minute mark, there's no delay between handshake initiations, and problems begin. The new behavior is changed to: 1. Peer A sends SYN. 2. Peer A sees that his key is getting old and initiates new handshake. 3. Peer B receives SYN and sends ACK. 4. Peer B sees that his key is getting old and schedules a delayed handshake for 12.5 seconds in the future. 5. Peer B receives handshake initiation and cancels scheduled handshake.
Diffstat (limited to 'src')
-rw-r--r--src/peer.h2
-rw-r--r--src/receive.c2
-rw-r--r--src/send.c19
-rw-r--r--src/timers.c33
-rw-r--r--src/timers.h2
5 files changed, 50 insertions, 8 deletions
diff --git a/src/peer.h b/src/peer.h
index 5072169..6eb6224 100644
--- a/src/peer.h
+++ b/src/peer.h
@@ -29,7 +29,7 @@ struct wireguard_peer {
struct cookie latest_cookie;
struct hlist_node pubkey_hash;
uint64_t rx_bytes, tx_bytes;
- struct timer_list timer_retransmit_handshake, timer_send_keepalive, timer_new_handshake, timer_kill_ephemerals, timer_persistent_keepalive;
+ struct timer_list timer_retransmit_handshake, timer_delay_handshake, timer_send_keepalive, timer_new_handshake, timer_kill_ephemerals, timer_persistent_keepalive;
unsigned int timer_handshake_attempts;
unsigned long persistent_keepalive_interval;
bool timer_need_another_keepalive;
diff --git a/src/receive.c b/src/receive.c
index 54e74b0..58a0086 100644
--- a/src/receive.c
+++ b/src/receive.c
@@ -123,6 +123,7 @@ static void receive_handshake_packet(struct wireguard_device *wg, void *data, si
return;
}
net_dbg_ratelimited("Receiving handshake initiation from peer %Lu (%pISpfsc)\n", peer->internal_id, &addr);
+ timers_handshake_received(peer);
update_latest_addr(peer, skb);
packet_send_handshake_response(peer);
break;
@@ -138,6 +139,7 @@ static void receive_handshake_packet(struct wireguard_device *wg, void *data, si
return;
}
net_dbg_ratelimited("Receiving handshake response from peer %Lu (%pISpfsc)\n", peer->internal_id, &addr);
+ timers_handshake_received(peer);
if (noise_handshake_begin_session(&peer->handshake, &peer->keypairs, true)) {
timers_ephemeral_key_created(peer);
timers_handshake_complete(peer);
diff --git a/src/send.c b/src/send.c
index 36fe918..90d5d14 100644
--- a/src/send.c
+++ b/src/send.c
@@ -90,7 +90,6 @@ void packet_send_handshake_cookie(struct wireguard_device *wg, struct sk_buff *i
static inline void keep_key_fresh(struct wireguard_peer *peer)
{
struct noise_keypair *keypair;
- unsigned long rekey_after_time = REKEY_AFTER_TIME;
rcu_read_lock();
keypair = rcu_dereference(peer->keypairs.current_keypair);
@@ -99,14 +98,20 @@ static inline void keep_key_fresh(struct wireguard_peer *peer)
return;
}
- /* We don't want both peers initiating a new handshake at the same time */
- if (!keypair->i_am_the_initiator)
- rekey_after_time += REKEY_TIMEOUT / 2 + REKEY_TIMEOUT * 2;
-
if (atomic64_read(&keypair->sending.counter.counter) > REKEY_AFTER_MESSAGES ||
- time_is_before_eq_jiffies64(keypair->sending.birthdate + rekey_after_time)) {
+ time_is_before_eq_jiffies64(keypair->sending.birthdate + REKEY_AFTER_TIME)) {
rcu_read_unlock();
- ratelimit_packet_send_handshake_initiation(peer);
+ /* The initiator can try it immediately, but the responder has to wait a bit,
+ * to prevent the thundering herd effect. */
+ if (keypair->i_am_the_initiator)
+ ratelimit_packet_send_handshake_initiation(peer);
+ else {
+ /* If it's going to be dead soon, we rekey early. */
+ if (time_is_before_eq_jiffies64(keypair->sending.birthdate + REJECT_AFTER_TIME - KEEPALIVE_TIMEOUT - REKEY_TIMEOUT))
+ timers_delay_handshake(peer, REKEY_TIMEOUT / 2);
+ else /* Otherwise rekey at the usual staggered delay. */
+ timers_delay_handshake(peer, REKEY_TIMEOUT / 2 + REKEY_TIMEOUT * 2);
+ }
} else
rcu_read_unlock();
}
diff --git a/src/timers.c b/src/timers.c
index 300becb..7002e54 100644
--- a/src/timers.c
+++ b/src/timers.c
@@ -11,6 +11,7 @@
* Timer for initiating new handshake if we have sent a packet but after have not received one (even empty) for `(KEEPALIVE_TIMEOUT + REKEY_TIMEOUT)` ms
* Timer for zeroing out all ephemeral keys after `(REJECT_AFTER_TIME * 3)` ms if no new keys have been received
* Timer for, if enabled, sending an empty authenticated packet every user-specified seconds
+ * Timer for starting a new handshake based on a delay
*/
/* This rounds the time down to the closest power of two of the closest quarter second. */
@@ -58,6 +59,12 @@ static void expired_new_handshake(unsigned long ptr)
packet_queue_send_handshake_initiation(peer);
}
+static void expired_delay_handshake(unsigned long ptr)
+{
+ struct wireguard_peer *peer = (struct wireguard_peer *)ptr;
+ packet_queue_send_handshake_initiation(peer);
+}
+
static void expired_kill_ephemerals(unsigned long ptr)
{
struct wireguard_peer *peer = (struct wireguard_peer *)ptr;
@@ -119,20 +126,38 @@ void timers_any_authenticated_packet_received(struct wireguard_peer *peer)
/* Should be called after a handshake initiation message is sent. */
void timers_handshake_initiated(struct wireguard_peer *peer)
{
+ if (likely(peer->timer_delay_handshake.data))
+ del_timer(&peer->timer_delay_handshake);
if (likely(peer->timer_send_keepalive.data))
del_timer(&peer->timer_send_keepalive);
if (likely(peer->timer_retransmit_handshake.data))
mod_timer(&peer->timer_retransmit_handshake, slack_time(jiffies + REKEY_TIMEOUT + HZ / 4));
}
+/* Should be called after a handshake message of any kind is received. */
+void timers_handshake_received(struct wireguard_peer *peer)
+{
+ if (likely(peer->timer_delay_handshake.data))
+ del_timer(&peer->timer_delay_handshake);
+}
+
/* Should be called after a handshake response message is received and processed. */
void timers_handshake_complete(struct wireguard_peer *peer)
{
+ if (likely(peer->timer_delay_handshake.data))
+ del_timer(&peer->timer_delay_handshake);
if (likely(peer->timer_retransmit_handshake.data))
del_timer(&peer->timer_retransmit_handshake);
peer->timer_handshake_attempts = 0;
}
+/* Should be called in order to initiate a handshake a little bit in the future. */
+void timers_delay_handshake(struct wireguard_peer *peer, unsigned int delay)
+{
+ if (likely(peer->timer_delay_handshake.data) && !timer_pending(&peer->timer_delay_handshake))
+ mod_timer(&peer->timer_delay_handshake, jiffies + delay);
+}
+
/* Should be called after an ephemeral key is created, which is before sending a handshake response or after receiving a handshake response. */
void timers_ephemeral_key_created(struct wireguard_peer *peer)
{
@@ -154,6 +179,10 @@ void timers_init_peer(struct wireguard_peer *peer)
peer->timer_retransmit_handshake.function = expired_retransmit_handshake;
peer->timer_retransmit_handshake.data = (unsigned long)peer;
+ init_timer(&peer->timer_delay_handshake);
+ peer->timer_delay_handshake.function = expired_delay_handshake;
+ peer->timer_delay_handshake.data = (unsigned long)peer;
+
init_timer(&peer->timer_send_keepalive);
peer->timer_send_keepalive.function = expired_send_keepalive;
peer->timer_send_keepalive.data = (unsigned long)peer;
@@ -179,6 +208,10 @@ void timers_uninit_peer(struct wireguard_peer *peer)
del_timer(&peer->timer_retransmit_handshake);
peer->timer_retransmit_handshake.data = 0;
}
+ if (peer->timer_delay_handshake.data) {
+ del_timer(&peer->timer_delay_handshake);
+ peer->timer_delay_handshake.data = 0;
+ }
if (peer->timer_send_keepalive.data) {
del_timer(&peer->timer_send_keepalive);
peer->timer_send_keepalive.data = 0;
diff --git a/src/timers.h b/src/timers.h
index b6f80fd..349bdab 100644
--- a/src/timers.h
+++ b/src/timers.h
@@ -13,7 +13,9 @@ void timers_data_sent(struct wireguard_peer *peer);
void timers_data_received(struct wireguard_peer *peer);
void timers_any_authenticated_packet_received(struct wireguard_peer *peer);
void timers_handshake_initiated(struct wireguard_peer *peer);
+void timers_handshake_received(struct wireguard_peer *peer);
void timers_handshake_complete(struct wireguard_peer *peer);
+void timers_delay_handshake(struct wireguard_peer *peer, unsigned int delay);
void timers_ephemeral_key_created(struct wireguard_peer *peer);
void timers_any_authenticated_packet_traversal(struct wireguard_peer *peer);