From dce6d80d2ed3cad2c516082682d5f6ca877ef714 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 28 Aug 2023 03:28:43 +0000 Subject: [PATCH] upstream: Introduce a transport-level ping facility This adds a pair of SSH transport protocol messages SSH2_MSG_PING/PONG to implement a ping capability. These messages use numbers in the "local extensions" number space and are advertised using a "ping@openssh.com" ext-info message with a string version number of "0". ok markus@ OpenBSD-Commit-ID: b6b3c4cb2084c62f85a8dc67cf74954015eb547f --- PROTOCOL | 35 ++++++++++++++++++++++++++++++++++- kex.c | 47 ++++++++++++++++++++++++++++++++++------------- kex.h | 3 ++- packet.c | 23 +++++++++++++++++++++-- ssh2.h | 6 +++++- 5 files changed, 96 insertions(+), 18 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 27804d0ca..d453c779b 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -104,6 +104,39 @@ http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.o This is identical to curve25519-sha256 as later published in RFC8731. +1.9 transport: ping facility + +OpenSSH implements a transport level ping message SSH2_MSG_PING +and a corresponding SSH2_MSG_PONG reply. + +#define SSH2_MSG_PING 192 +#define SSH2_MSG_PONG 193 + +The ping message is simply: + + byte SSH_MSG_PING + string data + +The reply copies the data (which may be the empty string) from the +ping: + + byte SSH_MSG_PONG + string data + +Replies are sent in order. They are sent immediately except when rekeying +is in progress, in which case they are queued until rekeying completes. + +The server advertises support for these messages using the +SSH2_MSG_EXT_INFO mechanism (RFC8308), with the following message: + + string "ping@openssh.com" + string "0" (version) + +The ping/reply message is implemented at the transport layer rather +than as a named global or channel request to allow pings with very +short packet lengths, which would not be possible with other +approaches. + 2. Connection protocol changes 2.1. connection: Channel write close extension "eow@openssh.com" @@ -712,4 +745,4 @@ master instance and later clients. OpenSSH extends the usual agent protocol. These changes are documented in the PROTOCOL.agent file. -$OpenBSD: PROTOCOL,v 1.48 2022/11/07 01:53:01 dtucker Exp $ +$OpenBSD: PROTOCOL,v 1.49 2023/08/28 03:28:43 djm Exp $ diff --git a/kex.c b/kex.c index 502da12ed..8ff92f2a2 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.180 2023/08/21 21:16:18 tobhe Exp $ */ +/* $OpenBSD: kex.c,v 1.181 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -492,12 +492,14 @@ kex_send_ext_info(struct ssh *ssh) return SSH_ERR_ALLOC_FAIL; /* XXX filter algs list by allowed pubkey/hostbased types */ if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 || - (r = sshpkt_put_u32(ssh, 2)) != 0 || + (r = sshpkt_put_u32(ssh, 3)) != 0 || (r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 || (r = sshpkt_put_cstring(ssh, algs)) != 0 || (r = sshpkt_put_cstring(ssh, "publickey-hostbound@openssh.com")) != 0 || (r = sshpkt_put_cstring(ssh, "0")) != 0 || + (r = sshpkt_put_cstring(ssh, "ping@openssh.com")) != 0 || + (r = sshpkt_put_cstring(ssh, "0")) != 0 || (r = sshpkt_send(ssh)) != 0) { error_fr(r, "compose"); goto out; @@ -527,6 +529,23 @@ kex_send_newkeys(struct ssh *ssh) return 0; } +/* Check whether an ext_info value contains the expected version string */ +static int +kex_ext_info_check_ver(struct kex *kex, const char *name, + const u_char *val, size_t len, const char *want_ver, u_int flag) +{ + if (memchr(val, '\0', len) != NULL) { + error("SSH2_MSG_EXT_INFO: %s value contains nul byte", name); + return SSH_ERR_INVALID_FORMAT; + } + debug_f("%s=<%s>", name, val); + if (strcmp(val, want_ver) == 0) + kex->flags |= flag; + else + debug_f("unsupported version of %s extension", name); + return 0; +} + int kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) { @@ -557,6 +576,8 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) /* Ensure no \0 lurking in value */ if (memchr(val, '\0', vlen) != NULL) { error_f("nul byte in %s", name); + free(name); + free(val); return SSH_ERR_INVALID_FORMAT; } debug_f("%s=<%s>", name, val); @@ -564,18 +585,18 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) val = NULL; } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) { - /* XXX refactor */ - /* Ensure no \0 lurking in value */ - if (memchr(val, '\0', vlen) != NULL) { - error_f("nul byte in %s", name); - return SSH_ERR_INVALID_FORMAT; + if ((r = kex_ext_info_check_ver(kex, name, val, vlen, + "0", KEX_HAS_PUBKEY_HOSTBOUND)) != 0) { + free(name); + free(val); + return r; } - debug_f("%s=<%s>", name, val); - if (strcmp(val, "0") == 0) - kex->flags |= KEX_HAS_PUBKEY_HOSTBOUND; - else { - debug_f("unsupported version of %s extension", - name); + } else if (strcmp(name, "ping@openssh.com") == 0) { + if ((r = kex_ext_info_check_ver(kex, name, val, vlen, + "0", KEX_HAS_PING)) != 0) { + free(name); + free(val); + return r; } } else debug_f("%s (unrecognised)", name); diff --git a/kex.h b/kex.h index 8b54e3f4b..5f7ef784e 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.118 2023/03/06 12:14:48 dtucker Exp $ */ +/* $OpenBSD: kex.h,v 1.119 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -111,6 +111,7 @@ enum kex_exchange { #define KEX_HAS_PUBKEY_HOSTBOUND 0x0004 #define KEX_RSA_SHA2_256_SUPPORTED 0x0008 /* only set in server for now */ #define KEX_RSA_SHA2_512_SUPPORTED 0x0010 /* only set in server for now */ +#define KEX_HAS_PING 0x0020 struct sshenc { char *name; diff --git a/packet.c b/packet.c index fdb8783bc..77e5c57ba 100644 --- a/packet.c +++ b/packet.c @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.c,v 1.310 2023/04/06 03:21:31 djm Exp $ */ +/* $OpenBSD: packet.c,v 1.311 2023/08/28 03:28:43 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1054,6 +1054,8 @@ int ssh_packet_log_type(u_char type) { switch (type) { + case SSH2_MSG_PING: + case SSH2_MSG_PONG: case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EXTENDED_DATA: case SSH2_MSG_CHANNEL_WINDOW_ADJUST: @@ -1675,7 +1677,7 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) goto out; if (ssh_packet_log_type(*typep)) debug3("receive packet: type %u", *typep); - if (*typep < SSH2_MSG_MIN || *typep >= SSH2_MSG_LOCAL_MIN) { + if (*typep < SSH2_MSG_MIN) { if ((r = sshpkt_disconnect(ssh, "Invalid ssh2 packet type: %d", *typep)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) @@ -1710,6 +1712,8 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) u_int reason, seqnr; int r; u_char *msg; + const u_char *d; + size_t len; for (;;) { msg = NULL; @@ -1753,6 +1757,21 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) debug("Received SSH2_MSG_UNIMPLEMENTED for %u", seqnr); break; + case SSH2_MSG_PING: + if ((r = sshpkt_get_string_direct(ssh, &d, &len)) != 0) + return r; + DBG(debug("Received SSH2_MSG_PING len %zu", len)); + if ((r = sshpkt_start(ssh, SSH2_MSG_PONG)) != 0 || + (r = sshpkt_put_string(ssh, d, len)) != 0 || + (r = sshpkt_send(ssh)) != 0) + return r; + break; + case SSH2_MSG_PONG: + if ((r = sshpkt_get_string_direct(ssh, + NULL, &len)) != 0) + return r; + DBG(debug("Received SSH2_MSG_PONG len %zu", len)); + break; default: return 0; } diff --git a/ssh2.h b/ssh2.h index 288c39f69..0d48d0527 100644 --- a/ssh2.h +++ b/ssh2.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh2.h,v 1.20 2023/08/14 03:37:00 djm Exp $ */ +/* $OpenBSD: ssh2.h,v 1.21 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -108,6 +108,10 @@ #define SSH2_MSG_KEX_ECDH_INIT 30 #define SSH2_MSG_KEX_ECDH_REPLY 31 +/* transport layer: OpenSSH extensions */ +#define SSH2_MSG_PING 192 +#define SSH2_MSG_PONG 193 + /* user authentication: generic */ #define SSH2_MSG_USERAUTH_REQUEST 50