2 * Routines for dissecting mosh's State Synchronization Protocol
3 * Copyright 2020 Google LLC
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
13 * State Synchronization Protocol is the protocol used by mosh:
14 * <https://mosh.org/mosh-paper-draft.pdf>
16 * The protocol name is abbreviated as SSyncP to avoid conflict with the
17 * "Scripting Service Protocol".
19 * The protocol is based on UDP, with a plaintext header followed by an
20 * encrypted payload. For now we just support decrypting a single connection at
21 * a time, using the MOSH_KEY dumped from the environment variables
22 * (`cat /proc/$pid/environ | tr '\0' '\n' | grep MOSH_KEY` on Linux).
23 * Note that to display the embedded protobuf properly, you'll have to add
24 * src/protobufs/ from mosh's source code to the ProtoBuf search path.
25 * For now we stop decoding after reaching the first level of protobufs; in
26 * them, a second layer of protobufs is sometimes embedded (e.g. for
27 * transmitting screen contents and such). Implementing that is left as an
28 * exercise for the reader.
33 #include <epan/packet.h> /* Should be first Wireshark include (other than config.h) */
34 #include <epan/conversation.h>
35 #include <epan/wmem_scopes.h>
36 #include <epan/proto_data.h>
37 #include <epan/prefs.h>
38 #include <epan/expert.h>
40 #include <wsutil/array.h>
41 #include <wsutil/report_message.h>
42 #include <wsutil/wsgcrypt.h>
44 void proto_reg_handoff_ssyncp(void);
45 void proto_register_ssyncp(void);
47 static dissector_handle_t ssyncp_handle
;
49 static int proto_ssyncp
;
50 static int hf_ssyncp_direction
;
51 static int hf_ssyncp_seq
;
52 static int hf_ssyncp_encrypted
;
53 static int hf_ssyncp_seq_delta
;
54 static int hf_ssyncp_timestamp
;
55 static int hf_ssyncp_timestamp_reply
;
56 static int hf_ssyncp_frag_seq
;
57 static int hf_ssyncp_frag_final
;
58 static int hf_ssyncp_frag_idx
;
59 static int hf_ssyncp_rtt_to_server
;
60 static int hf_ssyncp_rtt_to_client
;
62 /* Initialize the subtree pointers */
63 static int ett_ssyncp
;
64 static int ett_ssyncp_decrypted
;
66 static expert_field ei_ssyncp_fragmented
;
67 static expert_field ei_ssyncp_bad_key
;
69 static const char *pref_ssyncp_key
;
70 static char ssyncp_raw_aes_key
[16];
71 static bool have_ssyncp_key
;
73 static dissector_handle_t dissector_protobuf
;
75 typedef struct _ssyncp_conv_info_t
{
76 /* last sequence numbers per direction */
78 /* for each direction, have we seen any traffic yet? */
81 uint16_t clock_offset
[2];
85 typedef struct _ssyncp_packet_info_t
{
88 bool have_rtt_estimate
;
90 } ssyncp_packet_info_t
;
92 #define SSYNCP_IV_PAD 4
93 #define SSYNCP_SEQ_LEN 8
94 #define SSYNCP_DATAGRAM_HEADER_LEN (SSYNCP_SEQ_LEN + 2 + 2) /* 64-bit IV and two 16-bit timestamps */
95 #define SSYNCP_TRANSPORT_HEADER_LEN (8 + 2)
96 #define SSYNCP_AUTHTAG_LEN 16 /* 128-bit auth tag */
99 * We only match on 60001, which mosh uses for its first connection.
100 * If there are more connections in the range 60002-61000, the user will have to
101 * mark those as ssyncp traffic manually - we'd have too many false positives
104 #define SSYNCP_UDP_PORT 60001
107 dissect_ssyncp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
110 /* Check that we have at least a datagram plus an OCB auth tag. */
111 if (tvb_reported_length(tvb
) < SSYNCP_DATAGRAM_HEADER_LEN
+ SSYNCP_TRANSPORT_HEADER_LEN
+ SSYNCP_AUTHTAG_LEN
)
114 uint64_t direction_and_seq
= tvb_get_uint64(tvb
, 0, ENC_BIG_ENDIAN
);
115 unsigned direction
= direction_and_seq
>> 63;
116 uint64_t seq
= direction_and_seq
& ~(1ULL << 63);
118 /* Heuristic: The 63-bit sequence number starts from zero and increments
119 * from there. Even if you send 1000 packets per second over 10 years, you
120 * won't reach 2^35. So check that the sequence number is not outrageously
123 if (seq
> (1ULL << 35))
126 /* On the first pass, track the previous sequence numbers per direction,
127 * compute deltas between sequence numbers, and save those deltas.
128 * On subsequent passes, use the computed deltas.
130 ssyncp_packet_info_t
*ssyncp_pinfo
;
131 ssyncp_conv_info_t
*ssyncp_info
= NULL
;
132 if (pinfo
->fd
->visited
) {
133 ssyncp_pinfo
= (ssyncp_packet_info_t
*)p_get_proto_data(wmem_file_scope(), pinfo
, proto_ssyncp
, 0);
135 conversation_t
*conversation
= find_or_create_conversation(pinfo
);
136 ssyncp_info
= (ssyncp_conv_info_t
*)conversation_get_proto_data(conversation
, proto_ssyncp
);
138 ssyncp_info
= wmem_new0(wmem_file_scope(), ssyncp_conv_info_t
);
139 conversation_add_proto_data(conversation
, proto_ssyncp
, ssyncp_info
);
142 ssyncp_pinfo
= wmem_new(wmem_file_scope(), ssyncp_packet_info_t
);
143 ssyncp_pinfo
->first_packet
= !ssyncp_info
->seen_packet
[direction
];
144 if (ssyncp_pinfo
->first_packet
) {
145 ssyncp_info
->seen_packet
[direction
] = true;
147 ssyncp_pinfo
->seq_delta
= seq
- ssyncp_info
->last_seq
[direction
];
149 ssyncp_pinfo
->have_rtt_estimate
= false;
150 p_add_proto_data(wmem_file_scope(), pinfo
, proto_ssyncp
, 0, ssyncp_pinfo
);
152 ssyncp_info
->last_seq
[direction
] = seq
;
155 /*** COLUMN DATA ***/
157 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "ssyncp");
159 col_clear(pinfo
->cinfo
, COL_INFO
);
161 char *direction_str
= direction
? "Server->Client" : "Client->Server";
162 col_set_str(pinfo
->cinfo
, COL_INFO
, direction_str
);
164 /*** PROTOCOL TREE ***/
166 /* create display subtree for the protocol */
167 proto_item
*ti
= proto_tree_add_item(tree
, proto_ssyncp
, tvb
, 0, -1, ENC_NA
);
169 proto_tree
*ssyncp_tree
= proto_item_add_subtree(ti
, ett_ssyncp
);
171 /* Add an item to the subtree, see section 1.5 of README.dissector for more
173 proto_tree_add_item(ssyncp_tree
, hf_ssyncp_direction
, tvb
,
174 0, 1, ENC_BIG_ENDIAN
);
175 proto_tree_add_item(ssyncp_tree
, hf_ssyncp_seq
, tvb
,
176 0, 8, ENC_BIG_ENDIAN
);
177 #ifdef GCRY_OCB_BLOCK_LEN
178 proto_item
*encrypted_item
=
180 proto_tree_add_item(ssyncp_tree
, hf_ssyncp_encrypted
,
183 if (!ssyncp_pinfo
->first_packet
) {
184 proto_item
*delta_item
=
185 proto_tree_add_int64(ssyncp_tree
, hf_ssyncp_seq_delta
, tvb
, 0, 0,
186 ssyncp_pinfo
->seq_delta
);
187 proto_item_set_generated(delta_item
);
190 unsigned char *decrypted
= NULL
;
191 unsigned decrypted_len
= 0;
193 /* avoid build failure on ancient libgcrypt without OCB support */
194 #ifdef GCRY_OCB_BLOCK_LEN
195 if (have_ssyncp_key
) {
196 gcry_error_t gcry_err
;
198 /* try to decrypt the rest of the packet */
199 gcry_cipher_hd_t gcry_hd
;
200 gcry_err
= gcry_cipher_open(&gcry_hd
, GCRY_CIPHER_AES128
, GCRY_CIPHER_MODE_OCB
, 0);
201 if (gcry_err_code(gcry_err
)) {
202 /* this shouldn't happen (even if the packet is garbage) */
203 report_failure("ssyncp: unable to initialize cipher???");
204 return tvb_captured_length(tvb
);
206 gcry_err
= gcry_cipher_setkey(gcry_hd
, ssyncp_raw_aes_key
, sizeof(ssyncp_raw_aes_key
));
207 if (gcry_err_code(gcry_err
)) {
208 /* this shouldn't happen (even if the packet is garbage) */
209 report_failure("ssyncp: unable to set key???");
210 gcry_cipher_close(gcry_hd
);
211 return tvb_captured_length(tvb
);
213 char nonce
[SSYNCP_IV_PAD
+ SSYNCP_SEQ_LEN
];
214 memset(nonce
, 0, SSYNCP_IV_PAD
);
215 tvb_memcpy(tvb
, nonce
+ SSYNCP_IV_PAD
, 0, SSYNCP_SEQ_LEN
);
216 gcry_err
= gcry_cipher_setiv(gcry_hd
, nonce
, sizeof(nonce
));
217 if (gcry_err_code(gcry_err
)) {
218 /* this shouldn't happen (even if the packet is garbage) */
219 report_failure("ssyncp: unable to set iv???");
220 gcry_cipher_close(gcry_hd
);
221 return tvb_captured_length(tvb
);
223 decrypted_len
= tvb_captured_length(tvb
) - SSYNCP_SEQ_LEN
- SSYNCP_AUTHTAG_LEN
;
224 decrypted
= (unsigned char *)tvb_memdup(pinfo
->pool
, tvb
,
225 SSYNCP_SEQ_LEN
, decrypted_len
);
226 gcry_cipher_final(gcry_hd
);
227 gcry_err
= gcry_cipher_decrypt(gcry_hd
, decrypted
, decrypted_len
, NULL
, 0);
228 if (gcry_err_code(gcry_err
)) {
229 /* this shouldn't happen (even if the packet is garbage) */
230 report_failure("ssyncp: unable to decrypt???");
231 gcry_cipher_close(gcry_hd
);
232 return tvb_captured_length(tvb
);
234 gcry_err
= gcry_cipher_checktag(gcry_hd
,
235 tvb_get_ptr(tvb
, SSYNCP_SEQ_LEN
+decrypted_len
, SSYNCP_AUTHTAG_LEN
),
237 if (gcry_err_code(gcry_err
) && gcry_err_code(gcry_err
) != GPG_ERR_CHECKSUM
) {
238 /* this shouldn't happen (even if the packet is garbage) */
239 report_failure("ssyncp: unable to check auth tag???");
240 gcry_cipher_close(gcry_hd
);
241 return tvb_captured_length(tvb
);
243 if (gcry_err_code(gcry_err
)) {
244 /* if the tag is wrong, the key was wrong and the decrypted data is useless */
246 expert_add_info(pinfo
, encrypted_item
, &ei_ssyncp_bad_key
);
248 gcry_cipher_close(gcry_hd
);
253 tvbuff_t
*decrypted_tvb
= tvb_new_child_real_data(tvb
, decrypted
, decrypted_len
, decrypted_len
);
254 add_new_data_source(pinfo
, decrypted_tvb
, "Decrypted data");
256 if (!pinfo
->fd
->visited
&& ssyncp_info
) {
257 uint16_t our_clock16
= ((uint64_t)pinfo
->abs_ts
.secs
* 1000 + pinfo
->abs_ts
.nsecs
/ 1000000) & 0xffff;
258 uint16_t sender_ts
= tvb_get_uint16(decrypted_tvb
, 0, ENC_BIG_ENDIAN
);
259 uint16_t reply_ts
= tvb_get_uint16(decrypted_tvb
, 2, ENC_BIG_ENDIAN
);
260 ssyncp_info
->clock_offset
[direction
] = sender_ts
- our_clock16
;
261 ssyncp_info
->clock_seen
[direction
] = true;
262 if (reply_ts
!= 0xffff && ssyncp_info
->clock_seen
[1-direction
]) {
263 uint16_t projected_send_time_our_clock
= reply_ts
- ssyncp_info
->clock_offset
[1-direction
];
264 ssyncp_pinfo
->rtt_estimate
= our_clock16
- projected_send_time_our_clock
;
265 ssyncp_pinfo
->have_rtt_estimate
= true;
269 proto_tree
*dec_tree
= proto_tree_add_subtree(ssyncp_tree
, decrypted_tvb
,
270 0, -1, ett_ssyncp_decrypted
, NULL
, "Decrypted data");
272 proto_tree_add_item(dec_tree
, hf_ssyncp_timestamp
, decrypted_tvb
,
273 0, 2, ENC_BIG_ENDIAN
);
274 proto_tree_add_item(dec_tree
, hf_ssyncp_timestamp_reply
, decrypted_tvb
,
275 2, 2, ENC_BIG_ENDIAN
);
277 if (ssyncp_pinfo
->have_rtt_estimate
) {
278 int rtt_id
= direction
? hf_ssyncp_rtt_to_server
: hf_ssyncp_rtt_to_client
;
279 proto_item
*rtt_item
= proto_tree_add_int(dec_tree
, rtt_id
, decrypted_tvb
, 2, 2, ssyncp_pinfo
->rtt_estimate
);
280 proto_item_set_generated(rtt_item
);
283 proto_tree_add_item(dec_tree
, hf_ssyncp_frag_seq
, decrypted_tvb
,
284 4, 8, ENC_BIG_ENDIAN
);
285 proto_tree_add_item(dec_tree
, hf_ssyncp_frag_final
, decrypted_tvb
,
286 12, 2, ENC_BIG_ENDIAN
);
287 proto_item
*frag_idx_item
= proto_tree_add_item(dec_tree
,
288 hf_ssyncp_frag_idx
, decrypted_tvb
, 12, 2, ENC_BIG_ENDIAN
);
290 /* TODO actually handle fragmentation; for now just bail out on fragmentation */
291 if (tvb_get_uint16(decrypted_tvb
, 12, ENC_BIG_ENDIAN
) != 0x8000) {
292 expert_add_info(pinfo
, frag_idx_item
, &ei_ssyncp_fragmented
);
293 return tvb_captured_length(tvb
);
296 tvbuff_t
*inflated_tvb
= tvb_child_uncompress_zlib(decrypted_tvb
, decrypted_tvb
, 14, decrypted_len
- 14);
297 if (inflated_tvb
== NULL
)
298 return tvb_captured_length(tvb
);
299 add_new_data_source(pinfo
, inflated_tvb
, "Inflated data");
301 if (dissector_protobuf
) {
302 call_dissector_with_data(dissector_protobuf
, inflated_tvb
, pinfo
,
303 dec_tree
, "message,TransportBuffers.Instruction");
307 return tvb_captured_length(tvb
);
310 /* Register the protocol with Wireshark.
312 * This format is required because a script is used to build the C function that
313 * calls all the protocol registration.
316 proto_register_ssyncp(void)
318 static const true_false_string direction_name
= {
323 /* Setup list of header fields See Section 1.5 of README.dissector for
325 static hf_register_info hf
[] = {
326 { &hf_ssyncp_direction
,
327 { "Direction", "ssyncp.direction",
328 FT_BOOLEAN
, 8, TFS(&direction_name
), 0x80,
329 "Direction of packet", HFILL
}
332 { "Sequence number", "ssyncp.seq",
333 FT_UINT64
, BASE_HEX
, NULL
, 0x7fffffffffffffff,
334 "Monotonically incrementing packet sequence number", HFILL
}
336 { &hf_ssyncp_encrypted
,
337 { "Encrypted data", "ssyncp.enc_data",
338 FT_BYTES
, BASE_NONE
, NULL
, 0,
339 "Encrypted RTT estimation fields and Transport Layer payload, encrypted with AES-128-OCB",
342 { &hf_ssyncp_seq_delta
,
343 { "Sequence number delta", "ssyncp.seq_delta",
344 FT_INT64
, BASE_DEC
, NULL
, 0,
345 "Delta from last sequence number; 1 is normal, 0 is duplicated packet, <0 is reordering, >1 is reordering or packet loss", HFILL
}
347 { &hf_ssyncp_timestamp
,
348 { "Truncated timestamp", "ssyncp.timestamp",
349 FT_UINT16
, BASE_HEX
, NULL
, 0,
350 "Low 16 bits of sender's time in milliseconds", HFILL
}
352 { &hf_ssyncp_timestamp_reply
,
353 { "Last timestamp received", "ssyncp.timestamp_reply",
354 FT_UINT16
, BASE_HEX
, NULL
, 0,
355 "Low 16 bits of timestamp of last received packet plus time since it was received (for RTT estimation)", HFILL
}
357 { &hf_ssyncp_frag_seq
,
358 { "Fragment ID", "ssyncp.frag_seq",
359 FT_UINT64
, BASE_HEX
, NULL
, 0,
360 "Transport-level sequence number, used for fragment reassembly", HFILL
}
362 { &hf_ssyncp_frag_final
,
363 { "Final fragment", "ssyncp.frag_final",
364 FT_BOOLEAN
, 16, NULL
, 0x8000,
365 "Is this the last fragment?", HFILL
}
367 { &hf_ssyncp_frag_idx
,
368 { "Fragment Index", "ssyncp.frag_idx",
369 FT_UINT16
, BASE_HEX
, NULL
, 0x7fff,
370 "Index of this fragment in the list of fragments of the transport-level message", HFILL
}
372 { &hf_ssyncp_rtt_to_server
,
373 { "RTT estimate to server (in ms)", "ssyncp.rtt_est_to_server",
374 FT_INT16
, BASE_DEC
, NULL
, 0,
375 "Estimated round trip time from point of capture to server", HFILL
}
377 { &hf_ssyncp_rtt_to_client
,
378 { "RTT estimate to client (in ms)", "ssyncp.rtt_est_to_client",
379 FT_INT16
, BASE_DEC
, NULL
, 0,
380 "Estimated round trip time from point of capture to client", HFILL
}
384 /* Setup protocol subtree array */
385 static int *ett
[] = {
387 &ett_ssyncp_decrypted
390 /* Setup protocol expert items */
391 static ei_register_info ei
[] = {
392 { &ei_ssyncp_fragmented
,
393 { "ssyncp.fragmented", PI_REASSEMBLE
, PI_WARN
,
394 "SSYNCP-level fragmentation, dissector can't handle that", EXPFILL
}
396 { &ei_ssyncp_bad_key
,
397 { "ssyncp.badkey", PI_DECRYPTION
, PI_WARN
,
398 "Encrypted data could not be decrypted with the provided key", EXPFILL
}
402 /* Register the protocol name and description */
403 proto_ssyncp
= proto_register_protocol("State Synchronization Protocol", "SSyncP", "ssyncp");
405 /* Register the dissector handle */
406 ssyncp_handle
= register_dissector("ssyncp", dissect_ssyncp
, proto_ssyncp
);
408 /* Required function calls to register the header fields and subtrees */
409 proto_register_field_array(proto_ssyncp
, hf
, array_length(hf
));
410 proto_register_subtree_array(ett
, array_length(ett
));
412 expert_module_t
*expert_ssyncp
= expert_register_protocol(proto_ssyncp
);
413 expert_register_field_array(expert_ssyncp
, ei
, array_length(ei
));
415 module_t
*ssyncp_module
= prefs_register_protocol(proto_ssyncp
, proto_reg_handoff_ssyncp
);
417 prefs_register_string_preference(ssyncp_module
, "key",
419 "MOSH_KEY AES key (from mosh-{client,server} environment variable)",
424 proto_reg_handoff_ssyncp(void)
426 static bool initialized
= false;
429 dissector_add_uint("udp.port", SSYNCP_UDP_PORT
, ssyncp_handle
);
431 dissector_protobuf
= find_dissector("protobuf");
432 if (dissector_protobuf
== NULL
) {
433 report_failure("unable to find protobuf dissector");
439 have_ssyncp_key
= false;
440 if (strlen(pref_ssyncp_key
) != 0) {
441 if (strlen(pref_ssyncp_key
) != 22) {
442 report_failure("ssyncp: invalid key, must be 22 characters long");
446 memcpy(base64_key
, pref_ssyncp_key
, 22);
447 memcpy(base64_key
+22, "==\0", 3);
449 if (g_base64_decode_inplace(base64_key
, &out_len
) == NULL
|| out_len
!= sizeof(ssyncp_raw_aes_key
)) {
450 report_failure("ssyncp: invalid key, base64 decoding (with \"==\" appended) failed");
453 memcpy(ssyncp_raw_aes_key
, base64_key
, sizeof(ssyncp_raw_aes_key
));
454 have_ssyncp_key
= true;
459 * Editor modelines - https://www.wireshark.org/tools/modelines.html
464 * indent-tabs-mode: nil
467 * vi: set shiftwidth=4 tabstop=8 expandtab:
468 * :indentSize=4:tabSize=8:noTabs=true: