The unified diff between revisions [869f0c37..] and [e24ccc23..] is displayed below. It can also be downloaded as a raw diff.
This diff has been restricted to the following files: 'netcmd.cc'
#
#
# patch "netcmd.cc"
# from [3d925448e00e4f5a0e70f9ac977899fbf9312a1e]
# to [207caa3c268ad7e6a93eaff3f3298aa53f45fadd]
#
============================================================
--- netcmd.cc 3d925448e00e4f5a0e70f9ac977899fbf9312a1e
+++ netcmd.cc 207caa3c268ad7e6a93eaff3f3298aa53f45fadd
@@ -8,6 +8,8 @@
#include <utility>
#include "cryptopp/gzip.h"
+#include "cryptopp/hmac.h"
+#include "cryptopp/sha.h"
#include "adler32.hh"
#include "constants.hh"
@@ -50,9 +52,6 @@ netcmd::netcmd() : version(constants::ne
cmd_code(bye_cmd),
payload_len(0), length_len(0)
{}
-
-netcmd::netcmd(u8 _version) : version(_version), cmd_code(bye_cmd),
- payload_len(0), length_len(0)
{}
size_t netcmd::encoded_size()
@@ -82,61 +81,53 @@ void
}
void
-netcmd::write(string & out) const
+netcmd::write(string & out, netsync_session_key const & key,
+ netsync_hmac_value & hmac_val) const
{
+ size_t oldlen = out.size();
out += static_cast<char>(version);
out += static_cast<char>(cmd_code);
insert_variable_length_string(payload, out);
- adler32 check(reinterpret_cast<u8 const *>(payload.data()), payload.size());
- insert_datum_lsb<u32>(check.sum(), out);
-}
-// last should be zero (doesn't mean we're compatible with version 0).
-// The nonzero elements are the historical netsync/netcmd versions we can
-// interoperate with. For interoperating with newer versions, assume
-// compatibility and let the remote host make the call.
-static u8 const compatible_versions[] = {4, 0};
-
-bool is_compatible(u8 version)
-{
- for (u8 const *x = compatible_versions; *x; ++x)
+ I(key().size() == CryptoPP::SHA::DIGESTSIZE);
+ I(key().size() == hmac_val().size());
+ byte keybuf[CryptoPP::SHA::DIGESTSIZE];
+ for (size_t i = 0; i < sizeof(keybuf); i++)
{
- if (*x == version)
- return true;
+ keybuf[i] = key()[i] ^ hmac_val()[i];
}
- return false;
+ CryptoPP::HMAC<CryptoPP::SHA> hmac(keybuf, sizeof(keybuf));
+ char digest[CryptoPP::SHA::DIGESTSIZE];
+ hmac.CalculateDigest(reinterpret_cast<byte *>(digest),
+ reinterpret_cast<const byte *>(out.data() + oldlen),
+ out.size() - oldlen);
+ string digest_str(digest, sizeof(digest));
+ hmac_val = netsync_hmac_value(digest_str);
+ out.append(digest_str);
}
-
+
bool
-netcmd::read(string & inbuf)
+netcmd::read(string & inbuf, netsync_session_key const & key,
+ netsync_hmac_value & hmac_val)
{
size_t pos = 0;
if (inbuf.size() < constants::netcmd_minsz)
return false;
- u8 ver = extract_datum_lsb<u8>(inbuf, pos, "netcmd protocol number");
- int v = version;
+ u8 extracted_ver = extract_datum_lsb<u8>(inbuf, pos, "netcmd protocol number");
+ if (extracted_ver != version)
+ throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
+ % widen<u32,u8>(version)
+ % widen<u32,u8>(extracted_ver));
+ version = extracted_ver;
u8 cmd_byte = extract_datum_lsb<u8>(inbuf, pos, "netcmd code");
switch (cmd_byte)
{
- // hello may be newer than expected, or one we're compatible with
case static_cast<u8>(hello_cmd):
- if (ver < version && !is_compatible(ver))
- throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
- % widen<u32,u8>(v)
- % widen<u32,u8>(ver));
- break;
- // these may be older compatible versions
case static_cast<u8>(anonymous_cmd):
case static_cast<u8>(auth_cmd):
- if (ver != version && (ver > version || !is_compatible(ver)))
- throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
- % widen<u32,u8>(v)
- % widen<u32,u8>(ver));
- break;
- // these must match exactly what's expected
case static_cast<u8>(error_cmd):
case static_cast<u8>(bye_cmd):
case static_cast<u8>(confirm_cmd):
@@ -147,16 +138,11 @@ netcmd::read(string & inbuf)
case static_cast<u8>(data_cmd):
case static_cast<u8>(delta_cmd):
case static_cast<u8>(nonexistant_cmd):
- if (ver != version)
- throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
- % widen<u32,u8>(v)
- % widen<u32,u8>(ver));
+ cmd_code = static_cast<netcmd_code>(cmd_byte);
break;
default:
throw bad_decode(F("unknown netcmd code 0x%x") % widen<u32,u8>(cmd_byte));
}
- cmd_code = static_cast<netcmd_code>(cmd_byte);
- version = ver;
// check to see if we have even enough bytes for a complete uleb128
size_t old_pos = pos;
@@ -170,9 +156,9 @@ netcmd::read(string & inbuf)
throw bad_decode(F("oversized payload of '%d' bytes") % payload_len);
// there might not be enough data yet in the input buffer
- if (inbuf.size() < pos + payload_len + sizeof(u32))
+ if (inbuf.size() < pos + payload_len + CryptoPP::SHA::DIGESTSIZE)
{
- inbuf.reserve(pos + payload_len + sizeof(u32) + constants::bufsz);
+ inbuf.reserve(pos + payload_len + CryptoPP::SHA::DIGESTSIZE + constants::bufsz);
return false;
}
@@ -192,10 +178,25 @@ netcmd::read(string & inbuf)
length_len = 0;
// they might have given us bogus data
- adler32 check(reinterpret_cast<u8 const *>(payload.data()),
- payload.size());
- if (checksum != check.sum())
- throw bad_decode(F("bad checksum 0x%x vs. 0x%x") % checksum % check.sum());
+ string cmd_digest = extract_substring(inbuf, pos, CryptoPP::SHA::DIGESTSIZE,
+ "netcmd HMAC");
+ I(key().size() == CryptoPP::SHA::DIGESTSIZE);
+ I(key().size() == hmac_val().size());
+ byte keybuf[CryptoPP::SHA::DIGESTSIZE];
+ for (size_t i = 0; i < sizeof(keybuf); i++)
+ {
+ keybuf[i] = key()[i] ^ hmac_val()[i];
+ }
+ CryptoPP::HMAC<CryptoPP::SHA> hmac(keybuf, sizeof(keybuf));
+ char digest_buf[CryptoPP::SHA::DIGESTSIZE];
+ hmac.CalculateDigest(reinterpret_cast<byte *>(digest_buf),
+ reinterpret_cast<const byte *>(payload.data()),
+ payload.size());
+ string digest(digest_buf, sizeof(digest_buf));
+ if (cmd_digest != digest)
+ throw bad_decode(F("bad HMAC %s vs. %s") % encode_hexenc(cmd_digest)
+ % encode_hexenc(digest));
+ hmac_val = netsync_hmac_value(digest);
return true;
}
@@ -256,37 +257,37 @@ netcmd::write_hello_cmd(rsa_keypair_id c
}
-void
-netcmd::read_anonymous_cmd(protocol_role & role,
+void
+netcmd::read_anonymous_cmd(protocol_role & role,
std::string & pattern,
- id & nonce2) const
+ rsa_oaep_sha_data & hmac_key_encrypted) const
{
size_t pos = 0;
- // syntax is: <role:1 byte> <pattern: vstr> <nonce2: 20 random bytes>
- u8 role_byte = extract_datum_lsb<u8>(payload, pos, "anonymous netcmd, role");
+ // syntax is: <role:1 byte> <pattern: vstr> <hmac_key_encrypted: vstr>
+ u8 role_byte = extract_datum_lsb<u8>(payload, pos, "anonymous(hmac) netcmd, role");
if (role_byte != static_cast<u8>(source_role)
&& role_byte != static_cast<u8>(sink_role)
&& role_byte != static_cast<u8>(source_and_sink_role))
throw bad_decode(F("unknown role specifier %d") % widen<u32,u8>(role_byte));
role = static_cast<protocol_role>(role_byte);
extract_variable_length_string(payload, pattern, pos,
- "anonymous netcmd, pattern");
- nonce2 = id(extract_substring(payload, pos,
- constants::merkle_hash_length_in_bytes,
- "anonymous netcmd, nonce2"));
- assert_end_of_buffer(payload, pos, "anonymous netcmd payload");
+ "anonymous(hmac) netcmd, pattern");
+ string hmac_key_string;
+ extract_variable_length_string(payload, hmac_key_string, pos,
+ "anonymous(hmac) netcmd, hmac_key_encrypted");
+ hmac_key_encrypted = rsa_oaep_sha_data(hmac_key_string);
+ assert_end_of_buffer(payload, pos, "anonymous(hmac) netcmd payload");
}
-void
-netcmd::write_anonymous_cmd(protocol_role role,
+void
+netcmd::write_anonymous_cmd(protocol_role role,
std::string const & pattern,
- id const & nonce2)
+ rsa_oaep_sha_data const & hmac_key_encrypted)
{
cmd_code = anonymous_cmd;
- I(nonce2().size() == constants::merkle_hash_length_in_bytes);
payload = static_cast<char>(role);
insert_variable_length_string(pattern, payload);
- payload += nonce2();
+ insert_variable_length_string(hmac_key_encrypted(), payload);
}
void
@@ -294,74 +295,68 @@ netcmd::read_auth_cmd(protocol_role & ro
string & pattern,
id & client,
id & nonce1,
- id & nonce2,
+ rsa_oaep_sha_data & hmac_key_encrypted,
string & signature) const
{
size_t pos = 0;
// syntax is: <role:1 byte> <pattern: vstr>
- // <client: 20 bytes sha1> <nonce1: 20 random bytes> <nonce2: 20 random bytes>
- // <signature: vstr>
+ // <client: 20 bytes sha1> <nonce1: 20 random bytes>
+ // <hmac_key_encrypted: vstr> <signature: vstr>
u8 role_byte = extract_datum_lsb<u8>(payload, pos, "auth netcmd, role");
if (role_byte != static_cast<u8>(source_role)
&& role_byte != static_cast<u8>(sink_role)
&& role_byte != static_cast<u8>(source_and_sink_role))
throw bad_decode(F("unknown role specifier %d") % widen<u32,u8>(role_byte));
role = static_cast<protocol_role>(role_byte);
- extract_variable_length_string(payload, pattern, pos, "auth netcmd, pattern");
+ extract_variable_length_string(payload, pattern, pos, "auth(hmac) netcmd, pattern");
client = id(extract_substring(payload, pos,
constants::merkle_hash_length_in_bytes,
- "auth netcmd, client identifier"));
+ "auth(hmac) netcmd, client identifier"));
nonce1 = id(extract_substring(payload, pos,
constants::merkle_hash_length_in_bytes,
- "auth netcmd, nonce1"));
- nonce2 = id(extract_substring(payload, pos,
- constants::merkle_hash_length_in_bytes,
- "auth netcmd, nonce2"));
+ "auth(hmac) netcmd, nonce1"));
+ string hmac_key;
+ extract_variable_length_string(payload, hmac_key, pos,
+ "auth(hmac) netcmd, hmac_key_encrypted");
+ hmac_key_encrypted = rsa_oaep_sha_data(hmac_key);
extract_variable_length_string(payload, signature, pos,
- "auth netcmd, signature");
- assert_end_of_buffer(payload, pos, "auth netcmd payload");
+ "auth(hmac) netcmd, signature");
+ assert_end_of_buffer(payload, pos, "auth(hmac) netcmd payload");
}
-void
-netcmd::write_auth_cmd(protocol_role role,
- string const & pattern,
+void
+netcmd::write_auth_cmd(protocol_role role,
+ string const & pattern,
id const & client,
- id const & nonce1,
- id const & nonce2,
+ id const & nonce1,
+ rsa_oaep_sha_data const & hmac_key_encrypted,
string const & signature)
{
cmd_code = auth_cmd;
I(client().size() == constants::merkle_hash_length_in_bytes);
I(nonce1().size() == constants::merkle_hash_length_in_bytes);
- I(nonce2().size() == constants::merkle_hash_length_in_bytes);
payload = static_cast<char>(role);
insert_variable_length_string(pattern, payload);
payload += client();
payload += nonce1();
- payload += nonce2();
+ insert_variable_length_string(hmac_key_encrypted(), payload);
insert_variable_length_string(signature, payload);
}
-
-void
-netcmd::read_confirm_cmd(string & signature) const
+void
+netcmd::read_confirm_cmd() const
{
size_t pos = 0;
-
- // syntax is: <signature: vstr>
- extract_variable_length_string(payload, signature, pos,
- "confirm netcmd, signature");
assert_end_of_buffer(payload, pos, "confirm netcmd payload");
}
-void
-netcmd::write_confirm_cmd(string const & signature)
+void
+netcmd::write_confirm_cmd()
{
cmd_code = confirm_cmd;
payload.clear();
- insert_variable_length_string(signature, payload);
}
-
+
void
netcmd::read_refine_cmd(merkle_node & node) const
{
@@ -577,6 +572,65 @@ netcmd::write_nonexistant_cmd(netcmd_ite
#include "transforms.hh"
#include <boost/lexical_cast.hpp>
+void
+test_netcmd_mac()
+{
+ netcmd out_cmd, in_cmd;
+ string buf;
+ netsync_session_key key(constants::netsync_key_initializer);
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ // mutates mac
+ out_cmd.write(buf, key, mac);
+ BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+ }
+
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ out_cmd.write(buf, key, mac);
+ }
+ buf[0] ^= 0xff;
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+ }
+
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ out_cmd.write(buf, key, mac);
+ }
+ buf[buf.size() - 1] ^= 0xff;
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+ }
+
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ out_cmd.write(buf, key, mac);
+ }
+ buf += '\0';
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+ }
+}
+
+static void
+do_netcmd_roundtrip(netcmd const & out_cmd, netcmd & in_cmd, string & buf)
+{
+ netsync_session_key key(constants::netsync_key_initializer);
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ out_cmd.write(buf, key, mac);
+ }
+ {
+ netsync_hmac_value mac(constants::netsync_key_initializer);
+ BOOST_CHECK(in_cmd.read(buf, key, mac));
+ }
+ BOOST_CHECK(in_cmd == out_cmd);
+}
+
void
test_netcmd_functions()
{
@@ -591,10 +645,8 @@ test_netcmd_functions()
string out_errmsg("your shoelaces are untied"), in_errmsg;
string buf;
out_cmd.write_error_cmd(out_errmsg);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_error_cmd(in_errmsg);
- BOOST_CHECK(in_cmd == out_cmd);
BOOST_CHECK(in_errmsg == out_errmsg);
L(F("errmsg_cmd test done, buffer was %d bytes\n") % buf.size());
}
@@ -604,9 +656,7 @@ test_netcmd_functions()
L(F("checking i/o round trip on bye_cmd\n"));
netcmd out_cmd, in_cmd;
string buf;
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
- BOOST_CHECK(in_cmd == out_cmd);
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
L(F("bye_cmd test done, buffer was %d bytes\n") % buf.size());
}
@@ -619,10 +669,8 @@ test_netcmd_functions()
rsa_pub_key out_server_key("9387938749238792874"), in_server_key;
id out_nonce(raw_sha1("nonce it up")), in_nonce;
out_cmd.write_hello_cmd(out_server_keyname, out_server_key, out_nonce);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_hello_cmd(in_server_keyname, in_server_key, in_nonce);
- BOOST_CHECK(in_cmd == out_cmd);
BOOST_CHECK(in_server_keyname == out_server_keyname);
BOOST_CHECK(in_server_key == out_server_key);
BOOST_CHECK(in_nonce == out_nonce);
@@ -635,15 +683,15 @@ test_netcmd_functions()
netcmd out_cmd, in_cmd;
protocol_role out_role = source_and_sink_role, in_role;
string buf;
- id out_nonce2(raw_sha1("nonce start my heart")), in_nonce2;
+ // total cheat, since we don't actually verify that rsa_oaep_sha_data
+ // is sensible anywhere here...
+ rsa_oaep_sha_data out_key("nonce start my heart"), in_key;
string out_pattern("radishes galore!"), in_pattern;
- out_cmd.write_anonymous_cmd(out_role, out_pattern, out_nonce2);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
- in_cmd.read_anonymous_cmd(in_role, in_pattern, in_nonce2);
- BOOST_CHECK(in_cmd == out_cmd);
- BOOST_CHECK(in_nonce2 == out_nonce2);
+ out_cmd.write_anonymous_cmd(out_role, out_pattern, out_key);
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
+ in_cmd.read_anonymous_cmd(in_role, in_pattern, in_key);
+ BOOST_CHECK(in_key == out_key);
BOOST_CHECK(in_role == out_role);
L(F("anonymous_cmd test done, buffer was %d bytes\n") % buf.size());
}
@@ -655,21 +703,21 @@ test_netcmd_functions()
protocol_role out_role = source_and_sink_role, in_role;
string buf;
id out_client(raw_sha1("happy client day")), out_nonce1(raw_sha1("nonce me amadeus")),
- out_nonce2(raw_sha1("nonce start my heart")),
- in_client, in_nonce1, in_nonce2;
+ in_client, in_nonce1;
+ // total cheat, since we don't actually verify that rsa_oaep_sha_data
+ // is sensible anywhere here...
+ rsa_oaep_sha_data out_key("nonce start my heart"), in_key;
string out_signature(raw_sha1("burble") + raw_sha1("gorby")), out_pattern("radishes galore!"),
in_signature, in_pattern;
out_cmd.write_auth_cmd(out_role, out_pattern, out_client, out_nonce1,
- out_nonce2, out_signature);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ out_key, out_signature);
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_auth_cmd(in_role, in_pattern, in_client,
- in_nonce1, in_nonce2, in_signature);
- BOOST_CHECK(in_cmd == out_cmd);
+ in_nonce1, in_key, in_signature);
BOOST_CHECK(in_client == out_client);
BOOST_CHECK(in_nonce1 == out_nonce1);
- BOOST_CHECK(in_nonce2 == out_nonce2);
+ BOOST_CHECK(in_key == out_key);
BOOST_CHECK(in_signature == out_signature);
BOOST_CHECK(in_role == out_role);
L(F("auth_cmd test done, buffer was %d bytes\n") % buf.size());
@@ -680,14 +728,9 @@ test_netcmd_functions()
L(F("checking i/o round trip on confirm_cmd\n"));
netcmd out_cmd, in_cmd;
string buf;
- string out_signature(raw_sha1("egg") + raw_sha1("tomago")), in_signature;
-
- out_cmd.write_confirm_cmd(out_signature);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
- in_cmd.read_confirm_cmd(in_signature);
- BOOST_CHECK(in_cmd == out_cmd);
- BOOST_CHECK(in_signature == out_signature);
+ out_cmd.write_confirm_cmd();
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
+ in_cmd.read_confirm_cmd();
L(F("confirm_cmd test done, buffer was %d bytes\n") % buf.size());
}
@@ -708,10 +751,8 @@ test_netcmd_functions()
out_node.set_slot_state(15, subtree_state);
out_cmd.write_refine_cmd(out_node);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_refine_cmd(in_node);
- BOOST_CHECK(in_cmd == out_cmd);
BOOST_CHECK(in_node == out_node);
L(F("refine_cmd test done, buffer was %d bytes\n") % buf.size());
}
@@ -725,8 +766,7 @@ test_netcmd_functions()
string buf;
out_cmd.write_done_cmd(out_level, out_type);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_done_cmd(in_level, in_type);
BOOST_CHECK(in_level == out_level);
BOOST_CHECK(in_type == out_type);
@@ -742,8 +782,7 @@ test_netcmd_functions()
string buf;
out_cmd.write_send_data_cmd(out_type, out_id);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_send_data_cmd(in_type, in_id);
BOOST_CHECK(in_type == out_type);
BOOST_CHECK(in_id == out_id);
@@ -760,8 +799,7 @@ test_netcmd_functions()
string buf;
out_cmd.write_send_delta_cmd(out_type, out_head, out_base);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_send_delta_cmd(in_type, in_head, in_base);
BOOST_CHECK(in_type == out_type);
BOOST_CHECK(in_head == out_head);
@@ -778,8 +816,7 @@ test_netcmd_functions()
string out_dat("thank you for flying northwest"), in_dat;
string buf;
out_cmd.write_data_cmd(out_type, out_id, out_dat);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_data_cmd(in_type, in_id, in_dat);
BOOST_CHECK(in_id == out_id);
BOOST_CHECK(in_dat == out_dat);
@@ -797,8 +834,7 @@ test_netcmd_functions()
string buf;
out_cmd.write_delta_cmd(out_type, out_head, out_base, out_delta);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_delta_cmd(in_type, in_head, in_base, in_delta);
BOOST_CHECK(in_type == out_type);
BOOST_CHECK(in_head == out_head);
@@ -816,8 +852,7 @@ test_netcmd_functions()
string buf;
out_cmd.write_nonexistant_cmd(out_type, out_id);
- out_cmd.write(buf);
- BOOST_CHECK(in_cmd.read(buf));
+ do_netcmd_roundtrip(out_cmd, in_cmd, buf);
in_cmd.read_nonexistant_cmd(in_type, in_id);
BOOST_CHECK(in_type == out_type);
BOOST_CHECK(in_id == out_id);
@@ -836,6 +871,7 @@ add_netcmd_tests(test_suite * suite)
add_netcmd_tests(test_suite * suite)
{
suite->add(BOOST_TEST_CASE(&test_netcmd_functions));
+ suite->add(BOOST_TEST_CASE(&test_netcmd_mac));
}
#endif // BUILD_UNIT_TESTS