# # # patch "netcmd.cc" # from [db8fdc942bab5c77a2335b0663baf8df116f62e0] # to [fce7a074431682ce3b9ec06ab4da5e902f0dbfe7] # # patch "netcmd.hh" # from [4eada7d522530ad874ae8f3258608bb495c7525f] # to [0fbd7278e26c2d94de3431f411bf816f1279bbea] # # patch "netsync.cc" # from [2eeffe750a843e30a2c13e4621f6f62f0ebb0096] # to [0e92e7594fd82cf3e3b89ee5285e5ed7eab03828] # # patch "unit-tests/netcmd.cc" # from [56ed8d5af0e7cff22e014cf74af4ac96c5b6b8ed] # to [1d71ef9e25baee6f28f9ce34cee3f46886467138] # ============================================================ --- netcmd.cc db8fdc942bab5c77a2335b0663baf8df116f62e0 +++ netcmd.cc fce7a074431682ce3b9ec06ab4da5e902f0dbfe7 @@ -46,8 +46,9 @@ read_netcmd_item_type(string const & in, } } -netcmd::netcmd() : version(0), - cmd_code(error_cmd) +netcmd::netcmd(u8 ver) + : version(ver), + cmd_code(error_cmd) {} size_t netcmd::encoded_size() @@ -74,7 +75,7 @@ netcmd::write(string & out, chained_hmac out += static_cast(cmd_code); insert_variable_length_string(payload, out); - if (hmac.is_active() && cmd_code != usher_reply_cmd) + if (hmac.is_active() && cmd_code != usher_reply_cmd && cmd_code != usher_cmd) { string digest = hmac.process(out, oldlen); I(hmac.hmac_length == constants::netsync_hmac_value_length_in_bytes); @@ -92,6 +93,8 @@ netcmd::read(string_queue & inbuf, chain return false; u8 extracted_ver = extract_datum_lsb(inbuf, pos, "netcmd protocol number"); + bool too_old = extracted_ver < constants::netcmd_minimum_protocol_version; + bool too_new = extracted_ver > constants::netcmd_current_protocol_version; u8 cmd_byte = extract_datum_lsb(inbuf, pos, "netcmd code"); switch (cmd_byte) @@ -107,27 +110,33 @@ netcmd::read(string_queue & inbuf, chain case static_cast(data_cmd): case static_cast(delta_cmd): case static_cast(usher_cmd): + case static_cast(usher_reply_cmd): cmd_code = static_cast(cmd_byte); break; default: // if the versions don't match, we will throw the more descriptive // error immediately after this switch. - if (extracted_ver <= constants::netcmd_current_protocol_version && - extracted_ver >= constants::netcmd_minimum_protocol_version) + if (!too_old && !too_new) throw bad_decode(F("unknown netcmd code 0x%x") % widen(cmd_byte)); } - // Ignore the version on usher_cmd packets. - if (extracted_ver <= constants::netcmd_current_protocol_version && - extracted_ver >= constants::netcmd_minimum_protocol_version && - cmd_code != usher_cmd) - throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'\n" - "%s") - % widen(version) - % widen(extracted_ver) - % ((version < extracted_ver) - ? _("the remote side has a newer, incompatible version of monotone") - : _("the remote side has an older, incompatible version of monotone"))); + // check that the version is reasonable + if (cmd_code != usher_cmd) + { + if (too_old || (cmd_code != usher_reply_cmd && too_new)) + { + throw bad_decode(F("protocol version mismatch: wanted between '%d' and '%d' got '%d' (netcmd code %d)\n" + "%s") + % widen(constants::netcmd_minimum_protocol_version) + % widen(constants::netcmd_current_protocol_version) + % widen(extracted_ver) + % widen(cmd_code) + % ((constants::netcmd_current_protocol_version < extracted_ver) + ? _("the remote side has a newer, incompatible version of monotone") + : _("the remote side has an older, incompatible version of monotone"))); + } + } + version = extracted_ver; // check to see if we have even enough bytes for a complete uleb128 size_t payload_len = 0; @@ -141,7 +150,7 @@ netcmd::read(string_queue & inbuf, chain // there might not be enough data yet in the input buffer unsigned int minsize; - if (hmac.is_active() && cmd_code != usher_cmd) + if (hmac.is_active() && cmd_code != usher_cmd && cmd_code != usher_reply_cmd) minsize = pos + payload_len + constants::netsync_hmac_value_length_in_bytes; else minsize = pos + payload_len; @@ -154,7 +163,7 @@ netcmd::read(string_queue & inbuf, chain string digest; string cmd_digest; - if (hmac.is_active() && cmd_code != usher_cmd) + if (hmac.is_active() && cmd_code != usher_cmd && cmd_code != usher_reply_cmd) { // grab it before the data gets munged I(hmac.hmac_length == constants::netsync_hmac_value_length_in_bytes); @@ -163,7 +172,7 @@ netcmd::read(string_queue & inbuf, chain payload = extract_substring(inbuf, pos, payload_len, "netcmd payload"); - if (hmac.is_active() && cmd_code != usher_cmd) + if (hmac.is_active() && cmd_code != usher_cmd && cmd_code != usher_reply_cmd) { // they might have given us bogus data cmd_digest = extract_substring(inbuf, pos, @@ -174,7 +183,7 @@ netcmd::read(string_queue & inbuf, chain inbuf.pop_front(pos); if (hmac.is_active() - && cmd_code != usher_cmd + && cmd_code != usher_cmd && cmd_code != usher_reply_cmd && cmd_digest != digest) { throw bad_decode(F("bad HMAC checksum (got %s, wanted %s)\n" @@ -183,6 +192,9 @@ netcmd::read(string_queue & inbuf, chain % digest); } + L(FL("read packet with code %d and version %d") + % widen(cmd_code) % widen(version)); + return true; } @@ -209,10 +221,12 @@ void void -netcmd::read_hello_cmd(key_name & server_keyname, +netcmd::read_hello_cmd(u8 & server_version, + key_name & server_keyname, rsa_pub_key & server_key, id & nonce) const { + server_version = version; size_t pos = 0; // syntax is: string skn_str, sk_str; @@ -546,12 +560,34 @@ netcmd::read_usher_cmd(utf8 & greeting) { size_t pos = 0; string str; - extract_variable_length_string(payload, str, pos, "error netcmd, message"); + extract_variable_length_string(payload, str, pos, "usher netcmd, message"); greeting = utf8(str, origin::network); - assert_end_of_buffer(payload, pos, "error netcmd payload"); + assert_end_of_buffer(payload, pos, "usher netcmd payload"); } void +netcmd::write_usher_cmd(utf8 const & greeting) +{ + version = constants::netcmd_current_protocol_version; + cmd_code = usher_cmd; + insert_variable_length_string(greeting(), payload); +} + +void +netcmd::read_usher_reply_cmd(u8 & version_out, + utf8 & server, globish & pattern) const +{ + version_out = this->version; + string str; + size_t pos = 0; + extract_variable_length_string(payload, str, pos, "usher_reply netcmd, server"); + server = utf8(str, origin::network); + extract_variable_length_string(payload, str, pos, "usher_reply netcmd, pattern"); + pattern = globish(str, origin::network); + assert_end_of_buffer(payload, pos, "usher_reply netcmd payload"); +} + +void netcmd::write_usher_reply_cmd(utf8 const & server, globish const & pattern) { cmd_code = usher_reply_cmd; ============================================================ --- netcmd.hh 4eada7d522530ad874ae8f3258608bb495c7525f +++ netcmd.hh 0fbd7278e26c2d94de3431f411bf816f1279bbea @@ -72,11 +72,10 @@ typedef enum delta_cmd = 9, // usher commands - // usher_cmd is sent by a server farm (or anyone else who wants to serve - // from multiple databases over the same port), and the reply (containing - // the client's include pattern) is used to choose a server to forward the - // connection to. - // usher_cmd is never sent by the monotone server itself. + // usher_cmd is sent either by a proxy that needs to know where + // to forward a connection (the reply gives the desired hostname and + // include pattern), or by a server performing protocol + // version negotiation. usher_cmd = 100, usher_reply_cmd = 101 } @@ -89,8 +88,9 @@ public: netcmd_code cmd_code; std::string payload; public: - netcmd(); + netcmd(u8 ver); netcmd_code get_cmd_code() const {return cmd_code;} + u8 get_version() const { return version; } size_t encoded_size(); bool operator==(netcmd const & other) const; @@ -116,7 +116,8 @@ public: void read_error_cmd(std::string & errmsg) const; void write_error_cmd(std::string const & errmsg); - void read_hello_cmd(key_name & server_keyname, + void read_hello_cmd(u8 & server_version, + key_name & server_keyname, rsa_pub_key & server_key, id & nonce) const; void write_hello_cmd(key_name const & server_keyname, @@ -174,6 +175,8 @@ public: delta const & del); void read_usher_cmd(utf8 & greeting) const; + void write_usher_cmd(utf8 const & greeting); + void read_usher_reply_cmd(u8 & version, utf8 & server, globish & pattern) const; void write_usher_reply_cmd(utf8 const & server, globish const & pattern); }; ============================================================ --- netsync.cc 2eeffe750a843e30a2c13e4621f6f62f0ebb0096 +++ netsync.cc 0e92e7594fd82cf3e3b89ee5285e5ed7eab03828 @@ -645,6 +645,7 @@ session: public enumerator_callbacks, public session_base { + u8 version; protocol_role role; protocol_voice const voice; globish our_include_pattern; @@ -780,6 +781,7 @@ private: void write_netcmd_and_try_flush(netcmd const & cmd); // Outgoing queue-writers. + void queue_usher_cmd(utf8 const & message); void queue_bye_cmd(u8 phase); void queue_error_cmd(string const & errmsg); void queue_done_cmd(netcmd_item_type type, size_t n_items); @@ -809,7 +811,8 @@ private: // Incoming dispatch-called methods. bool process_error_cmd(string const & errmsg); - bool process_hello_cmd(key_name const & server_keyname, + bool process_hello_cmd(u8 server_version, + key_name const & server_keyname, rsa_pub_key const & server_key, id const & nonce); bool process_bye_cmd(u8 phase, transaction_guard & guard); @@ -832,6 +835,9 @@ private: id const & ident, delta const & del); bool process_usher_cmd(utf8 const & msg); + bool process_usher_reply_cmd(u8 client_version, + utf8 const & server, + globish const & pattern); // The incoming dispatcher. bool dispatch_payload(netcmd const & cmd, @@ -870,6 +876,7 @@ session::session(options & opts, shared_ptr sock, bool initiated_by_server) : session_base(peer, sock), + version(constants::netcmd_current_protocol_version), role(role), voice(voice), our_include_pattern(our_include_pattern), @@ -880,6 +887,7 @@ session::session(options & opts, lua(lua), use_transport_auth(opts.use_transport_auth), signing_key(keys.signing_key), + cmd(constants::netcmd_current_protocol_version), armed(false), received_remote_key(false), session_key(constants::netsync_key_initializer), @@ -1438,7 +1446,7 @@ session::queue_error_cmd(string const & session::queue_error_cmd(string const & errmsg) { L(FL("queueing 'error' command")); - netcmd cmd; + netcmd cmd(version); cmd.write_error_cmd(errmsg); write_netcmd_and_try_flush(cmd); } @@ -1448,7 +1456,7 @@ session::queue_bye_cmd(u8 phase) { L(FL("queueing 'bye' command, phase %d") % static_cast(phase)); - netcmd cmd; + netcmd cmd(version); cmd.write_bye_cmd(phase); write_netcmd_and_try_flush(cmd); } @@ -1461,7 +1469,7 @@ session::queue_done_cmd(netcmd_item_type netcmd_item_type_to_string(type, typestr); L(FL("queueing 'done' command for %s (%d items)") % typestr % n_items); - netcmd cmd; + netcmd cmd(version); cmd.write_done_cmd(type, n_items); write_netcmd_and_try_flush(cmd); } @@ -1484,7 +1492,7 @@ session::queue_anonymous_cmd(protocol_ro globish const & exclude_pattern, id const & nonce2) { - netcmd cmd; + netcmd cmd(version); rsa_oaep_sha_data hmac_key_encrypted; if (use_transport_auth) project.db.encrypt_rsa(remote_peer_key_id, nonce2(), hmac_key_encrypted); @@ -1503,7 +1511,7 @@ session::queue_auth_cmd(protocol_role ro id const & nonce2, rsa_sha1_signature const & signature) { - netcmd cmd; + netcmd cmd(version); rsa_oaep_sha_data hmac_key_encrypted; I(use_transport_auth); project.db.encrypt_rsa(remote_peer_key_id, nonce2(), hmac_key_encrypted); @@ -1516,7 +1524,7 @@ session::queue_confirm_cmd() void session::queue_confirm_cmd() { - netcmd cmd; + netcmd cmd(version); cmd.write_confirm_cmd(); write_netcmd_and_try_flush(cmd); } @@ -1531,7 +1539,7 @@ session::queue_refine_cmd(refinement_typ L(FL("queueing refinement %s of %s node '%s', level %d") % (ty == refinement_query ? "query" : "response") % typestr % hpref() % static_cast(node.level)); - netcmd cmd; + netcmd cmd(version); cmd.write_refine_cmd(ty, node); write_netcmd_and_try_flush(cmd); } @@ -1558,7 +1566,7 @@ session::queue_data_cmd(netcmd_item_type L(FL("queueing %d bytes of data for %s item '%s'") % dat.size() % typestr % hid()); - netcmd cmd; + netcmd cmd(version); // TODO: This pair of functions will make two copies of a large // file, the first in cmd.write_data_cmd, and the second in // write_netcmd_and_try_flush when the data is copied from the @@ -1599,7 +1607,7 @@ session::queue_delta_cmd(netcmd_item_typ L(FL("queueing %s delta '%s' -> '%s'") % typestr % base_hid() % ident_hid()); - netcmd cmd; + netcmd cmd(version); cmd.write_delta_cmd(type, base, ident, del); write_netcmd_and_try_flush(cmd); note_item_sent(type, ident); @@ -1634,13 +1642,20 @@ bool static const var_domain known_servers_domain = var_domain("known-servers"); bool -session::process_hello_cmd(key_name const & their_keyname, +session::process_hello_cmd(u8 server_version, + key_name const & their_keyname, rsa_pub_key const & their_key, id const & nonce) { I(!this->received_remote_key); I(this->saved_nonce().empty()); + // version sanity has already been checked by netcmd::read() + L(FL("received hello command; setting version from %d to %d") + % widen(version) + % widen(server_version)); + version = server_version; + key_identity_info their_identity; if (use_transport_auth) { @@ -2424,7 +2439,7 @@ session::process_usher_cmd(utf8 const & else L(FL("Received greeting from usher: %s") % msg().substr(1)); } - netcmd cmdout; + netcmd cmdout(version); cmdout.write_usher_reply_cmd(utf8(peer_id, origin::internal), our_include_pattern); write_netcmd_and_try_flush(cmdout); @@ -2474,11 +2489,12 @@ session::dispatch_payload(netcmd const & require(! authenticated, "hello netcmd received when not authenticated"); require(voice == client_voice, "hello netcmd received in client voice"); { + u8 server_version(0); key_name server_keyname; rsa_pub_key server_key; id nonce; - cmd.read_hello_cmd(server_keyname, server_key, nonce); - return process_hello_cmd(server_keyname, server_key, nonce); + cmd.read_hello_cmd(server_version, server_keyname, server_key, nonce); + return process_hello_cmd(server_version, server_keyname, server_key, nonce); } break; @@ -2615,7 +2631,13 @@ session::dispatch_payload(netcmd const & break; case usher_reply_cmd: - return false; // Should not happen. + { + u8 client_version; + utf8 server; + globish pattern; + cmd.read_usher_reply_cmd(client_version, server, pattern); + return process_usher_reply_cmd(client_version, server, pattern); + } break; } return false; @@ -2625,11 +2647,37 @@ session::begin_service() void session::begin_service() { + queue_usher_cmd(utf8("", origin::internal)); +} + +void +session::queue_usher_cmd(utf8 const & message) +{ + L(FL("queueing 'usher' command")); + netcmd cmd(0); + cmd.write_usher_cmd(message); + write_netcmd_and_try_flush(cmd); +} + +bool +session::process_usher_reply_cmd(u8 client_version, + utf8 const & server, + globish const & pattern) +{ + // netcmd::read() has already checked that the client isn't too old + if (client_version < constants::netcmd_current_protocol_version) + { + version = client_version; + } + L(FL("client has maximum version %d, using %d") + % widen(client_version) % widen(version)); + key_name name; keypair kp; if (use_transport_auth) keys.get_key_pair(signing_key, name, kp); queue_hello_cmd(name, kp.pub, mk_nonce()); + return true; } void ============================================================ --- unit-tests/netcmd.cc 56ed8d5af0e7cff22e014cf74af4ac96c5b6b8ed +++ unit-tests/netcmd.cc 1d71ef9e25baee6f28f9ce34cee3f46886467138 @@ -17,7 +17,8 @@ UNIT_TEST(mac) UNIT_TEST(mac) { - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); string buf; netsync_session_key key(constants::netsync_key_initializer); { @@ -82,7 +83,8 @@ UNIT_TEST(functions) // error_cmd { L(FL("checking i/o round trip on error_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); string out_errmsg("your shoelaces are untied"), in_errmsg; string buf; out_cmd.write_error_cmd(out_errmsg); @@ -95,14 +97,17 @@ UNIT_TEST(functions) // hello_cmd { L(FL("checking i/o round trip on hello_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(0); string buf; key_name out_server_keyname("address@hidden"), in_server_keyname; rsa_pub_key out_server_key("9387938749238792874"), in_server_key; id out_nonce(raw_sha1("nonce it up"), origin::internal), in_nonce; out_cmd.write_hello_cmd(out_server_keyname, out_server_key, out_nonce); do_netcmd_roundtrip(out_cmd, in_cmd, buf); - in_cmd.read_hello_cmd(in_server_keyname, in_server_key, in_nonce); + u8 ver(0); + in_cmd.read_hello_cmd(ver, in_server_keyname, in_server_key, in_nonce); + UNIT_TEST_CHECK(ver == constants::netcmd_current_protocol_version); UNIT_TEST_CHECK(in_server_keyname == out_server_keyname); UNIT_TEST_CHECK(in_server_key == out_server_key); UNIT_TEST_CHECK(in_nonce == out_nonce); @@ -112,7 +117,8 @@ UNIT_TEST(functions) // bye_cmd { L(FL("checking i/o round trip on bye_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); u8 out_phase(1), in_phase; string buf; @@ -126,7 +132,8 @@ UNIT_TEST(functions) // anonymous_cmd { L(FL("checking i/o round trip on anonymous_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); protocol_role out_role = source_and_sink_role, in_role; string buf; // total cheat, since we don't actually verify that rsa_oaep_sha_data @@ -150,7 +157,8 @@ UNIT_TEST(functions) // auth_cmd { L(FL("checking i/o round trip on auth_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); protocol_role out_role = source_and_sink_role, in_role; string buf; key_id out_client(raw_sha1("happy client day"), origin::internal); @@ -185,7 +193,8 @@ UNIT_TEST(functions) // confirm_cmd { L(FL("checking i/o round trip on confirm_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); string buf; out_cmd.write_confirm_cmd(); do_netcmd_roundtrip(out_cmd, in_cmd, buf); @@ -196,7 +205,8 @@ UNIT_TEST(functions) // refine_cmd { L(FL("checking i/o round trip on refine_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); string buf; refinement_type out_ty (refinement_query), in_ty(refinement_response); merkle_node out_node, in_node; @@ -221,7 +231,8 @@ UNIT_TEST(functions) // done_cmd { L(FL("checking i/o round trip on done_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); size_t out_n_items(12), in_n_items(0); netcmd_item_type out_type(key_item), in_type(revision_item); string buf; @@ -237,7 +248,8 @@ UNIT_TEST(functions) // data_cmd { L(FL("checking i/o round trip on data_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); netcmd_item_type out_type(file_item), in_type(key_item); id out_id(raw_sha1("tuna is not yummy"), origin::internal), in_id; string out_dat("thank you for flying northwest"), in_dat; @@ -253,7 +265,8 @@ UNIT_TEST(functions) // delta_cmd { L(FL("checking i/o round trip on delta_cmd")); - netcmd out_cmd, in_cmd; + netcmd out_cmd(constants::netcmd_current_protocol_version); + netcmd in_cmd(constants::netcmd_current_protocol_version); netcmd_item_type out_type(file_item), in_type(key_item); id out_head(raw_sha1("your seat cusion can be reused"), origin::internal), in_head; id out_base(raw_sha1("as a floatation device"), origin::internal), in_base;