Start a new handshake from the server if we get an unknown ack.
If a client times out for some reason, or the server restarts, then the
server might get a packet for an unknown session that the client thinks
is valid. Send a handshake packet with a fresh cookie in that case, to
tell the client it should resync.
Change-Id: I82d567b293043d30a3d687a3794d3b50123319e7
diff --git a/cmds/isoping.cc b/cmds/isoping.cc
index f3bf99e..8cf6142 100644
--- a/cmds/isoping.cc
+++ b/cmds/isoping.cc
@@ -273,9 +273,10 @@
DLOG("Generated new cookie secret.\n");
}
-// Returns the kernel monotonic timestamp in microseconds. This function never
-// returns the value 0; it returns 1 instead, so that 0 can be used as a magic
-// value.
+// Returns the kernel monotonic timestamp in microseconds. We provide 64 bits
+// of data here, though we truncate to 32 on the wire to save space in our
+// packets. This function never returns the value 0; it returns 1 instead, so
+// that 0 can be used as a magic value.
#ifdef __MACH__ // MacOS X doesn't have clock_gettime()
#include <mach/mach.h>
#include <mach/mach_time.h>
@@ -446,6 +447,18 @@
}
+void send_initial_handshake_reply(Sessions *s, Packet *rx, int sock,
+ struct sockaddr_storage *remoteaddr,
+ size_t remoteaddr_len, uint64_t now) {
+ Packet tx;
+ memset(&tx, 0, sizeof(tx));
+ prepare_handshake_reply_packet(&tx, rx, now);
+ s->CalculateCookie(&tx, remoteaddr, remoteaddr_len);
+ sendto(sock, &tx, sizeof(tx), 0, (struct sockaddr *)remoteaddr,
+ remoteaddr_len);
+}
+
+
int send_packet(struct Session *s, int sock, int is_server) {
if (is_server) {
if (sendto(sock, &s->tx, sizeof(s->tx), 0,
@@ -569,10 +582,12 @@
// Note: we don't want to allocate any memory here until the client has
// completed the handshake.
if (rx.packet_type != PACKET_TYPE_HANDSHAKE) {
- fprintf(stderr, "Received non-handshake packet from unknown client\n");
- // TODO(pmccurdy): Reply with a new handshake packet, including a
- // cookie; we may have dropped a legit client and we need to tell them
- // to renegotiate.
+ fprintf(stderr,
+ "Received non-handshake packet from unknown client %s\n",
+ sockaddr_to_str((struct sockaddr *)&rxaddr));
+ // Reply with a new handshake packet, including a cookie; we may have
+ // dropped a legit client and we need to tell them to renegotiate.
+ send_initial_handshake_reply(s, &rx, sock, &rxaddr, rxaddr_len, now);
return -1;
}
}
@@ -605,7 +620,6 @@
now);
return;
} else {
- DLOG("Client received handshake packet from server\n");
handle_server_handshake_packet(s, rx, now);
return;
}
@@ -645,12 +659,7 @@
s->session_map.erase(*remoteaddr);
fprintf(stderr, "New connection from %s, sending cookie\n",
sockaddr_to_str((struct sockaddr *)remoteaddr));
- Packet tx;
- memset(&tx, 0, sizeof(tx));
- prepare_handshake_reply_packet(&tx, rx, now);
- s->CalculateCookie(&tx, remoteaddr, remoteaddr_len);
- sendto(sock, &tx, sizeof(tx), 0, (struct sockaddr *)remoteaddr,
- remoteaddr_len);
+ send_initial_handshake_reply(s, rx, sock, remoteaddr, remoteaddr_len, now);
// The handshake_state is conceptually in the COOKIE_GENERATED state now,
// but the whole point of the cookie is to avoid saving state in the server,
// so we don't store a Session here.
@@ -688,6 +697,8 @@
// We don't need to resend the handshake packet any more.
s->next_sends.pop();
+ session.next_tx_id = 1;
+ session.next_rx_id = 0;
session.tx.packet_type = PACKET_TYPE_HANDSHAKE;
session.tx.data.handshake.cookie_epoch = rx->data.handshake.cookie_epoch;
memcpy(&session.tx.data.handshake.cookie, &rx->data.handshake.cookie,
diff --git a/cmds/isoping_test.cc b/cmds/isoping_test.cc
index d1f9ef7..d7e10ee 100644
--- a/cmds/isoping_test.cc
+++ b/cmds/isoping_test.cc
@@ -668,6 +668,44 @@
WVPASSEQ(s.next_sends.size(), 2);
WVPASSEQ(s.next_send_time(), sbase + t + 10 * 1000);
+ // Let the client time out on the server side, then come back.
+ t += 65 * 1000 * 1000;
+ WVPASS(!send_waiting_packets(&s, ssock, sbase + t, is_server));
+ WVPASSEQ(s.session_map.size(), 0);
+ WVPASSEQ(s.next_sends.size(), 0);
+ WVPASSEQ(s.next_send_time(), 0);
+
+ WVPASS(!read_incoming_packet(&c, csock, cbase + t, is_client));
+ // Hack so the client doesn't spam the server catching up.
+ cSession.usec_per_pkt = 50 * 1000 * 1000;
+ WVPASS(!send_waiting_packets(&c, csock, cbase + t, is_client));
+ WVPASSEQ(read_incoming_packet(&s, ssock, sbase + t, is_server), -1);
+ WVPASSEQ(read_incoming_packet(&s, ssock, sbase + t, is_server), -1);
+
+ // The client will immediately receive a handshake packet.
+ FD_ZERO(&rfds);
+ FD_SET(csock, &rfds);
+ nfds = select(csock + 1, &rfds, NULL, NULL, &tv);
+ WVPASSEQ(nfds, 1);
+
+ // The server doesn't store any data for the client yet.
+ WVPASSEQ(s.session_map.size(), 0);
+ WVPASSEQ(s.next_sends.size(), 0);
+ WVPASSEQ(s.next_send_time(), 0);
+
+ // Once the client replies, the session is fully established again.
+ WVPASS(!read_incoming_packet(&c, csock, cbase + t, is_client));
+ t = cSession.next_send;
+ WVPASS(!send_waiting_packets(&c, csock, cbase + t, is_client));
+ WVPASS(!read_incoming_packet(&s, ssock, sbase + t, is_server));
+
+ WVPASSEQ(s.session_map.size(), 1);
+ WVPASSEQ(s.next_sends.size(), 1);
+ WVPASSEQ(s.next_send_time(), sbase + t + 10 * 1000);
+ WVPASSEQ(cSession.next_tx_id, sSession.next_rx_id);
+ WVPASSEQ(cSession.next_rx_id, 0);
+ WVPASSEQ(sSession.next_tx_id, 1);
+
// Cleanup
close(ssock);
close(csock);