diff --git a/ssl/test/PORTING.md b/ssl/test/PORTING.md index 86ad24d0d..5819b49f7 100644 --- a/ssl/test/PORTING.md +++ b/ssl/test/PORTING.md @@ -20,14 +20,16 @@ The test runner integrates with the TLS stack under test through a “shim”: a command line program which encapsulates the stack. By default, the shim points to the BoringSSL shim in the same source tree, but any program can be supplied via the `-shim-path` flag. The -runner opens up a server socket and provides the shim with a `-port` -argument that points to that socket. The shim always connects to the -runner as a TCP client even when acting as a TLS server. For DTLS, -there is a small framing layer that gives packet boundaries over -TCP. The shim can also pass a variety of command line arguments which -are used to configure the stack under test. These can be found at -`test_config.cc`. +runner opens up a server socket and provides the shim with `-port` and +optional `-ipv6` arguments. +The shim should connect to loopback as a TCP client on the specified +port, using IPv6 if `-ipv6` is specified and IPv4 otherwise. This is +true even when the shim is speaking DTLS or acting as a TLS server. +For DTLS, there is a small framing layer that gives packet boundaries +over TCP. The shim can also pass a variety of command line arguments +which are used to configure the stack under test. These can be found at +`test_config.cc`. The shim reports success by exiting with a `0` error code and failure by reporting a non-zero error code and generally sending a textual error diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index e2f79d300..c14d7c060 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -77,7 +77,20 @@ static void PrintSocketError(const char *func) { } #else static void PrintSocketError(const char *func) { - fprintf(stderr, "%s: %d\n", func, WSAGetLastError()); + int error = WSAGetLastError(); + char *buffer; + DWORD len = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, error, 0, + reinterpret_cast(&buffer), 0, nullptr); + std::string msg = "unknown error"; + if (len > 0) { + msg.assign(buffer, len); + while (!msg.empty() && (msg.back() == '\r' || msg.back() == '\n')) { + msg.resize(msg.size() - 1); + } + } + LocalFree(buffer); + fprintf(stderr, "%s: %s (%d)\n", func, msg.c_str(), error); } #endif @@ -93,56 +106,55 @@ struct Free { } }; -// Connect returns a new socket connected to localhost on |port| or -1 on -// error. -static int Connect(uint16_t port) { - for (int af : { AF_INET6, AF_INET }) { - int sock = socket(af, SOCK_STREAM, 0); - if (sock == -1) { - PrintSocketError("socket"); +// Connect returns a new socket connected to the runner, or -1 on error. +static int Connect(const TestConfig *config) { + sockaddr_storage addr; + socklen_t addr_len = 0; + if (config->ipv6) { + sockaddr_in6 sin6; + OPENSSL_memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(config->port); + if (!inet_pton(AF_INET6, "::1", &sin6.sin6_addr)) { + PrintSocketError("inet_pton"); return -1; } - int nodelay = 1; - if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - reinterpret_cast(&nodelay), sizeof(nodelay)) != 0) { - PrintSocketError("setsockopt"); - closesocket(sock); + addr_len = sizeof(sin6); + memcpy(&addr, &sin6, addr_len); + } else { + sockaddr_in sin; + OPENSSL_memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(config->port); + if (!inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr)) { + PrintSocketError("inet_pton"); return -1; } - - sockaddr_storage ss; - OPENSSL_memset(&ss, 0, sizeof(ss)); - ss.ss_family = af; - socklen_t len = 0; - - if (af == AF_INET6) { - sockaddr_in6 *sin6 = (sockaddr_in6 *) &ss; - len = sizeof(*sin6); - sin6->sin6_port = htons(port); - if (!inet_pton(AF_INET6, "::1", &sin6->sin6_addr)) { - PrintSocketError("inet_pton"); - closesocket(sock); - return -1; - } - } else if (af == AF_INET) { - sockaddr_in *sin = (sockaddr_in *) &ss; - len = sizeof(*sin); - sin->sin_port = htons(port); - if (!inet_pton(AF_INET, "127.0.0.1", &sin->sin_addr)) { - PrintSocketError("inet_pton"); - closesocket(sock); - return -1; - } - } - - if (connect(sock, reinterpret_cast(&ss), len) == 0) { - return sock; - } - closesocket(sock); + addr_len = sizeof(sin); + memcpy(&addr, &sin, addr_len); } - PrintSocketError("connect"); - return -1; + int sock = socket(addr.ss_family, SOCK_STREAM, 0); + if (sock == -1) { + PrintSocketError("socket"); + return -1; + } + int nodelay = 1; + if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&nodelay), + sizeof(nodelay)) != 0) { + PrintSocketError("setsockopt"); + closesocket(sock); + return -1; + } + + if (connect(sock, reinterpret_cast(&addr), addr_len) != 0) { + PrintSocketError("connect"); + closesocket(sock); + return -1; + } + + return sock; } class SocketCloser { @@ -775,7 +787,7 @@ static bool DoConnection(bssl::UniquePtr *out_session, #endif } - int sock = Connect(config->port); + int sock = Connect(config); if (sock == -1) { return false; } diff --git a/ssl/test/handshake_util.cc b/ssl/test/handshake_util.cc index 6516547fa..b6d8280d6 100644 --- a/ssl/test/handshake_util.cc +++ b/ssl/test/handshake_util.cc @@ -398,9 +398,9 @@ static bool StartHandshaker(ScopedProcess *out, ScopedFD *out_control, if (is_resume) { args.push_back(kResumeFlag); } - // config->argv omits argv[0]. - for (int j = 0; j < config->argc; ++j) { - args.push_back(config->argv[j]); + // config->handshaker_args omits argv[0]. + for (const char *arg : config->handshaker_args) { + args.push_back(arg); } args.push_back(nullptr); diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index da0795bbb..e7ee19419 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -1260,25 +1260,32 @@ type shimProcess struct { func newShimProcess(shimPath string, flags []string, env []string) (*shimProcess, error) { shim := new(shimProcess) var err error + useIPv6 := true shim.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv6loopback}) if err != nil { + useIPv6 = false shim.listener, err = net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IP{127, 0, 0, 1}}) } if err != nil { return nil, err } - flags = append([]string{"-port", strconv.Itoa(shim.listener.Addr().(*net.TCPAddr).Port)}, flags...) + cmdFlags := []string{"-port", strconv.Itoa(shim.listener.Addr().(*net.TCPAddr).Port)} + if useIPv6 { + cmdFlags = append(cmdFlags, "-ipv6") + } + cmdFlags = append(cmdFlags, flags...) + if *useValgrind { - shim.cmd = valgrindOf(false, shimPath, flags...) + shim.cmd = valgrindOf(false, shimPath, cmdFlags...) } else if *useGDB { - shim.cmd = gdbOf(shimPath, flags...) + shim.cmd = gdbOf(shimPath, cmdFlags...) } else if *useLLDB { - shim.cmd = lldbOf(shimPath, flags...) + shim.cmd = lldbOf(shimPath, cmdFlags...) } else if *useRR { - shim.cmd = rrOf(shimPath, flags...) + shim.cmd = rrOf(shimPath, cmdFlags...) } else { - shim.cmd = exec.Command(shimPath, flags...) + shim.cmd = exec.Command(shimPath, cmdFlags...) } shim.cmd.Stdin = os.Stdin shim.cmd.Stdout = &shim.stdout diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 485a560b2..980bef135 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -46,12 +46,18 @@ namespace { struct Flag { const char *name; bool has_param; + // skip_handshaker, if true, causes this flag to be skipped when + // forwarding flags to the handshaker. This should be used with flags + // that only impact connecting to the runner. + bool skip_handshaker; // If |has_param| is false, |param| will be nullptr. std::function set_param; }; -Flag BoolFlag(const char *name, bool TestConfig::*field) { - return Flag{name, false, [=](TestConfig *config, const char *) -> bool { +Flag BoolFlag(const char *name, bool TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, false, skip_handshaker, + [=](TestConfig *config, const char *) -> bool { config->*field = true; return true; }}; @@ -91,15 +97,19 @@ bool StringToInt(T *out, const char *str) { } template -Flag IntFlag(const char *name, T TestConfig::*field) { - return Flag{name, true, [=](TestConfig *config, const char *param) -> bool { +Flag IntFlag(const char *name, T TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, true, skip_handshaker, + [=](TestConfig *config, const char *param) -> bool { return StringToInt(&(config->*field), param); }}; } template -Flag IntVectorFlag(const char *name, std::vector TestConfig::*field) { - return Flag{name, true, [=](TestConfig *config, const char *param) -> bool { +Flag IntVectorFlag(const char *name, std::vector TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, true, skip_handshaker, + [=](TestConfig *config, const char *param) -> bool { T value; if (!StringToInt(&value, param)) { return false; @@ -109,8 +119,10 @@ Flag IntVectorFlag(const char *name, std::vector TestConfig::*field) { }}; } -Flag StringFlag(const char *name, std::string TestConfig::*field) { - return Flag{name, true, [=](TestConfig *config, const char *param) -> bool { +Flag StringFlag(const char *name, std::string TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, true, skip_handshaker, + [=](TestConfig *config, const char *param) -> bool { config->*field = param; return true; }}; @@ -119,8 +131,10 @@ Flag StringFlag(const char *name, std::string TestConfig::*field) { // TODO(davidben): When we can depend on C++17 or Abseil, switch this to // std::optional or absl::optional. Flag OptionalStringFlag(const char *name, - std::unique_ptr TestConfig::*field) { - return Flag{name, true, [=](TestConfig *config, const char *param) -> bool { + std::unique_ptr TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, true, skip_handshaker, + [=](TestConfig *config, const char *param) -> bool { (config->*field).reset(new std::string(param)); return true; }}; @@ -143,15 +157,19 @@ bool DecodeBase64(std::string *out, const std::string &in) { return true; } -Flag Base64Flag(const char *name, std::string TestConfig::*field) { - return Flag{name, true, [=](TestConfig *config, const char *param) -> bool { +Flag Base64Flag(const char *name, std::string TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, true, skip_handshaker, + [=](TestConfig *config, const char *param) -> bool { return DecodeBase64(&(config->*field), param); }}; } Flag Base64VectorFlag(const char *name, - std::vector TestConfig::*field) { - return Flag{name, true, [=](TestConfig *config, const char *param) -> bool { + std::vector TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, true, skip_handshaker, + [=](TestConfig *config, const char *param) -> bool { std::string value; if (!DecodeBase64(&value, param)) { return false; @@ -163,8 +181,10 @@ Flag Base64VectorFlag(const char *name, Flag StringPairVectorFlag( const char *name, - std::vector> TestConfig::*field) { - return Flag{name, true, [=](TestConfig *config, const char *param) -> bool { + std::vector> TestConfig::*field, + bool skip_handshaker = false) { + return Flag{name, true, skip_handshaker, + [=](TestConfig *config, const char *param) -> bool { const char *comma = strchr(param, ','); if (!comma) { return false; @@ -178,7 +198,8 @@ Flag StringPairVectorFlag( std::vector SortedFlags() { std::vector flags = { - IntFlag("-port", &TestConfig::port), + IntFlag("-port", &TestConfig::port, /*skip_handshaker=*/true), + BoolFlag("-ipv6", &TestConfig::ipv6, /*skip_handshaker=*/true), BoolFlag("-server", &TestConfig::is_server), BoolFlag("-dtls", &TestConfig::is_dtls), BoolFlag("-quic", &TestConfig::is_quic), @@ -428,11 +449,10 @@ bool ParseConfig(int argc, char **argv, bool is_shim, TestConfig *out_initial, TestConfig *out_resume, TestConfig *out_retry) { - out_initial->argc = out_resume->argc = out_retry->argc = argc; - out_initial->argv = out_resume->argv = out_retry->argv = argv; for (int i = 0; i < argc; i++) { bool skip = false; - const char *name = argv[i]; + const char *arg = argv[i]; + const char *name = arg; // -on-shim and -on-handshaker prefixes enable flags only on the shim or // handshaker. @@ -473,6 +493,13 @@ bool ParseConfig(int argc, char **argv, bool is_shim, param = argv[i]; } + if (!flag->skip_handshaker) { + out_initial->handshaker_args.push_back(arg); + if (flag->has_param) { + out_initial->handshaker_args.push_back(param); + } + } + if (!skip) { if (out != nullptr) { if (!flag->set_param(out, param)) { @@ -491,6 +518,8 @@ bool ParseConfig(int argc, char **argv, bool is_shim, } } + out_resume->handshaker_args = out_initial->handshaker_args; + out_retry->handshaker_args = out_initial->handshaker_args; return true; } diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index e5c605799..4e8480005 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -26,6 +26,7 @@ struct TestConfig { int port = 0; + bool ipv6 = false; bool is_server = false; bool is_dtls = false; bool is_quic = false; @@ -197,8 +198,7 @@ struct TestConfig { bool fips_202205 = false; bool wpa_202304 = false; - int argc; - char **argv; + std::vector handshaker_args; bssl::UniquePtr SetupCtx(SSL_CTX *old_ctx) const;