Skip to content

Commit 74d19f5

Browse files
committed
Added TLS echo client and server w/ session resumption for OpenSSL
1 parent 5795f69 commit 74d19f5

6 files changed

Lines changed: 402 additions & 11 deletions

File tree

examples/tls/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ set(EXECUTABLES ${THREADED_EXECUTABLES})
5151

5252
if(SOCKPP_WITH_OPENSSL OR SOCKPP_WITH_MBEDTLS)
5353
list(APPEND EXECUTABLES certinfo tlscli tlsconn tlssvr)
54+
list(APPEND EXECUTABLES tlsecho)
55+
list(APPEND THREADED_EXECUTABLES tlsechosrvr)
56+
list(APPEND EXECUTABLES tlsechosrvr)
5457
endif()
5558

5659
foreach(EXECUTABLE ${EXECUTABLES})

examples/tls/tlsecho.cpp

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// tlsecho.cpp
2+
//
3+
// A TLS echo client that demonstrates session resumption.
4+
//
5+
// USAGE:
6+
// tlsecho [[host] port [ca.pem]]
7+
//
8+
// The host defaults to "localhost". If the first argument is a bare
9+
// integer it is taken as the port number (host keeps its default), so
10+
// both of the following work:
11+
//
12+
// tlsecho 4433 ca.pem
13+
// tlsecho myserver 4433 ca.pem
14+
//
15+
// Reads lines from stdin and sends them to the echo server, printing the
16+
// echoed response. An empty line closes the connection.
17+
//
18+
// When built with OpenSSL, the client saves the session from the first
19+
// connection and offers it on the second, demonstrating TLS session
20+
// resumption. The handshake type (full or resumed) is printed for every
21+
// connection so the speed-up is easy to observe.
22+
//
23+
// --------------------------------------------------------------------------
24+
// This file is part of the "sockpp" C++ socket library.
25+
//
26+
// Copyright (c) 2026 Frank Pagliughi
27+
// All rights reserved.
28+
//
29+
// Redistribution and use in source and binary forms, with or without
30+
// modification, are permitted provided that the following conditions are
31+
// met:
32+
//
33+
// 1. Redistributions of source code must retain the above copyright notice,
34+
// this list of conditions and the following disclaimer.
35+
//
36+
// 2. Redistributions in binary form must reproduce the above copyright
37+
// notice, this list of conditions and the following disclaimer in the
38+
// documentation and/or other materials provided with the distribution.
39+
//
40+
// 3. Neither the name of the copyright holder nor the names of its
41+
// contributors may be used to endorse or promote products derived from this
42+
// software without specific prior written permission.
43+
//
44+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
45+
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
46+
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
47+
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
48+
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
49+
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
50+
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
51+
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
52+
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
53+
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
54+
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55+
// --------------------------------------------------------------------------
56+
57+
#include <cstring>
58+
#include <iostream>
59+
#include <string>
60+
#include <system_error>
61+
62+
#include "sockpp/inet_address.h"
63+
#include "sockpp/tls/connector.h"
64+
#include "sockpp/tls/context.h"
65+
#include "sockpp/version.h"
66+
67+
#if defined(SOCKPP_OPENSSL)
68+
#include "sockpp/tls/openssl_session.h"
69+
#endif
70+
71+
using namespace std;
72+
73+
// Runs the echo loop on an already-connected TLS socket.
74+
// Returns when the user types an empty line or stdin reaches EOF.
75+
static bool run_echo_loop(sockpp::tls_connector& conn) {
76+
string s, sret;
77+
78+
while (getline(cin, s) && !s.empty()) {
79+
if (auto res = conn.write(s); !res) {
80+
cerr << "Write error: " << res.error_message() << "\n";
81+
return false;
82+
}
83+
84+
sret.resize(s.size());
85+
if (auto res = conn.read_n(sret.data(), s.size()); !res || res.value() != s.size()) {
86+
cerr << "Read error: " << (!res ? res.error_message() : "connection closed")
87+
<< "\n";
88+
return false;
89+
}
90+
91+
cout << sret << "\n";
92+
}
93+
94+
return true;
95+
}
96+
97+
// --------------------------------------------------------------------------
98+
99+
int main(int argc, char* argv[]) {
100+
cout << "TLS echo client for 'sockpp' " << sockpp::SOCKPP_VERSION << "\n" << endl;
101+
102+
// Argument parsing: if argv[1] is a bare integer treat it as the port
103+
// (host defaults to "localhost"), otherwise argv[1] is the host.
104+
// This lets both "tlsecho 4433 ca.pem" and "tlsecho myhost 4433 ca.pem" work.
105+
string host = "localhost";
106+
in_port_t port = 4433;
107+
string ca_file;
108+
109+
int next = 1;
110+
if (argc > next) {
111+
const char* a = argv[next];
112+
// Purely numeric first arg → port number, host stays "localhost"
113+
bool is_port = (*a != '\0' && strspn(a, "0123456789") == strlen(a));
114+
if (is_port) {
115+
port = static_cast<in_port_t>(atoi(a));
116+
}
117+
else {
118+
host = a;
119+
++next;
120+
if (argc > next)
121+
port = static_cast<in_port_t>(atoi(argv[next]));
122+
}
123+
++next;
124+
}
125+
if (argc > next)
126+
ca_file = argv[next];
127+
128+
sockpp::initialize();
129+
130+
// Build a client context.
131+
auto ctx = sockpp::tls_context::client();
132+
if (ca_file.empty())
133+
ctx.set_default_trust_locations();
134+
else if (auto res = ctx.set_trust_file(ca_file); !res) {
135+
cerr << "Failed to load CA file: " << res.error_message() << endl;
136+
return 1;
137+
}
138+
139+
error_code ec;
140+
sockpp::inet_address addr{host, port, ec};
141+
if (ec) {
142+
cerr << "Error resolving '" << host << "': " << ec.message() << endl;
143+
return 1;
144+
}
145+
146+
// ---- First connection ----
147+
148+
sockpp::tls_connector conn{ctx, ec};
149+
if (ec) {
150+
cerr << "Error creating connector: " << ec.message() << endl;
151+
return 1;
152+
}
153+
154+
if (auto res = conn.connect(addr); !res) {
155+
cerr << "Error connecting to " << addr << ": " << res.error_message() << endl;
156+
return 1;
157+
}
158+
159+
cout << "Connected to " << addr << " [" << conn.negotiated_version() << "]"
160+
<< " — full handshake" << endl;
161+
162+
run_echo_loop(conn);
163+
164+
#if defined(SOCKPP_OPENSSL)
165+
cout << "\nAttempting reconnect..." << endl;
166+
// Save the session before the connector goes out of scope.
167+
// The close_notify sent by the destructor marks the session as
168+
// resumable on the server side.
169+
auto sess_res = conn.get_session();
170+
if (!sess_res) {
171+
cerr << "Warning: could not capture session: " << sess_res.error_message() << endl;
172+
return 0;
173+
}
174+
sockpp::tls_session saved = std::move(sess_res.value());
175+
176+
// Close the first connection explicitly so the destructor has already
177+
// run (and sent close_notify) before we attempt the second connect.
178+
conn = sockpp::tls_connector{ctx, ec};
179+
if (ec) {
180+
cerr << "Error resetting connector: " << ec.message() << endl;
181+
return 1;
182+
}
183+
184+
// ---- Second connection: offer the saved session ----
185+
186+
if (auto res = conn.set_session(saved); !res) {
187+
cerr << "Warning: set_session failed: " << res.error_message() << endl;
188+
}
189+
190+
if (auto res = conn.connect(addr); !res) {
191+
cerr << "Error on second connect: " << res.error_message() << endl;
192+
return 1;
193+
}
194+
195+
bool reused = conn.session_reused();
196+
cout << "\nReconnected to " << addr << " [" << conn.negotiated_version() << "]"
197+
<< "" << (reused ? "session RESUMED" : "full handshake (not resumed)") << endl;
198+
199+
run_echo_loop(conn);
200+
#endif
201+
202+
return 0;
203+
}

examples/tls/tlsechosrvr.cpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// tlsechosrvr.cpp
2+
//
3+
// A multi-threaded TLS echo server for sockpp.
4+
// This is a thread-per-connection TLS server.
5+
//
6+
// USAGE:
7+
// tlsechosrvr <cert.pem> <key.pem> [port]
8+
//
9+
// The server loads a certificate and private key, then listens for TLS
10+
// connections. Each accepted connection is handled in a new thread that
11+
// echoes data until the peer closes. The TLS version and whether the
12+
// session was resumed are logged for every accepted connection.
13+
//
14+
// --------------------------------------------------------------------------
15+
// This file is part of the "sockpp" C++ socket library.
16+
//
17+
// Copyright (c) 2026 Frank Pagliughi
18+
// All rights reserved.
19+
//
20+
// Redistribution and use in source and binary forms, with or without
21+
// modification, are permitted provided that the following conditions are
22+
// met:
23+
//
24+
// 1. Redistributions of source code must retain the above copyright notice,
25+
// this list of conditions and the following disclaimer.
26+
//
27+
// 2. Redistributions in binary form must reproduce the above copyright
28+
// notice, this list of conditions and the following disclaimer in the
29+
// documentation and/or other materials provided with the distribution.
30+
//
31+
// 3. Neither the name of the copyright holder nor the names of its
32+
// contributors may be used to endorse or promote products derived from this
33+
// software without specific prior written permission.
34+
//
35+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
36+
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
37+
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38+
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
39+
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
40+
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
41+
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
42+
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
43+
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
44+
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
45+
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46+
// --------------------------------------------------------------------------
47+
48+
#include <fstream>
49+
#include <iostream>
50+
#include <iterator>
51+
#include <string>
52+
#include <thread>
53+
54+
#include "sockpp/inet_address.h"
55+
#include "sockpp/tls/acceptor.h"
56+
#include "sockpp/tls/context.h"
57+
#include "sockpp/version.h"
58+
59+
using namespace std;
60+
61+
// Reads the entire contents of a file into a string.
62+
static string read_file(const string& path) {
63+
ifstream f{path};
64+
return {istreambuf_iterator<char>{f}, istreambuf_iterator<char>{}};
65+
}
66+
67+
// --------------------------------------------------------------------------
68+
// The thread function.
69+
// Ownership of the socket is transferred to the thread; when the function
70+
// exits the socket is automatically closed and a close_notify is sent.
71+
72+
void run_echo(sockpp::tls_socket sock, const string& peer) {
73+
cout << "Connection from " << peer << " [" << sock.negotiated_version() << "]"
74+
<< (sock.session_reused() ? " (resumed)" : "") << "\n";
75+
76+
char buf[512];
77+
sockpp::result<size_t> res;
78+
79+
while ((res = sock.read(buf, sizeof(buf))) && res.value() > 0)
80+
sock.write_n(buf, res.value());
81+
82+
cout << "Connection from " << peer << " closed\n";
83+
}
84+
85+
// --------------------------------------------------------------------------
86+
// Main: bind, listen, and accept connections in a loop.
87+
// Each accepted connection is handed off to a detached thread.
88+
89+
int main(int argc, char* argv[]) {
90+
cout << "TLS echo server for 'sockpp' " << sockpp::SOCKPP_VERSION << "\n" << endl;
91+
92+
if (argc < 3) {
93+
cerr << "Usage: tlsechosrvr <cert.pem> <key.pem> [port]" << endl;
94+
return 1;
95+
}
96+
97+
string cert_path = argv[1];
98+
string key_path = argv[2];
99+
in_port_t port = (argc > 3) ? static_cast<in_port_t>(atoi(argv[3])) : 4433;
100+
101+
sockpp::initialize();
102+
103+
// Build a server context: load identity and enable session caching so
104+
// clients can demonstrate session resumption.
105+
auto ctx = sockpp::tls_context::server();
106+
107+
if (auto res = ctx.set_identity(read_file(cert_path), read_file(key_path)); !res) {
108+
cerr << "Failed to load identity: " << res.error_message() << endl;
109+
return 1;
110+
}
111+
112+
ctx.set_session_cache_mode(sockpp::tls_context::session_cache_mode::SERVER);
113+
114+
error_code ec;
115+
sockpp::tls_acceptor acc{
116+
ctx, sockpp::inet_address(port), sockpp::acceptor::DFLT_QUE_SIZE,
117+
sockpp::acceptor::REUSE, ec
118+
};
119+
if (ec) {
120+
cerr << "Error creating acceptor on port " << port << ": " << ec.message() << endl;
121+
return 1;
122+
}
123+
124+
cout << "Listening on port " << port << " ...\n";
125+
126+
while (true) {
127+
sockpp::inet_address peer;
128+
auto res = acc.accept(&peer);
129+
if (!res) {
130+
cerr << "Accept error: " << res.error_message() << endl;
131+
continue;
132+
}
133+
134+
thread thr{run_echo, res.release(), peer.to_string()};
135+
thr.detach();
136+
}
137+
138+
return 0;
139+
}

include/sockpp/tls/openssl_acceptor.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class tls_acceptor : public acceptor
8989
* @throws std::system_error on bind or listen failure.
9090
*/
9191
tls_acceptor(
92-
const tls_context& ctx, const sock_address& addr, int backlog = DFLT_QUE_SIZE
92+
const tls_context& ctx, const sock_address& addr, int queSize = DFLT_QUE_SIZE
9393
);
9494

9595
/**
@@ -101,9 +101,30 @@ class tls_acceptor : public acceptor
101101
* @param ec Receives the error code on failure.
102102
*/
103103
tls_acceptor(
104-
const tls_context& ctx, const sock_address& addr, int backlog, error_code& ec
104+
const tls_context& ctx, const sock_address& addr, int queSize, error_code& ec
105105
) noexcept;
106106

107+
/**
108+
* Creates an acceptor socket and starts it listening to the specified
109+
* address.
110+
* @param addr The address to which this server should be bound.
111+
* @param queSize The listener queue size.
112+
*/
113+
tls_acceptor(const tls_context& ctx, const sock_address& addr, int queSize, int reuse);
114+
/**
115+
* Creates an acceptor socket and starts it listening to the specified
116+
* address.
117+
* @param addr The address to which this server should be bound.
118+
* @param queSize The listener queue size.
119+
* @param reuse A reuse option for the socket. This can be SO_REUSEADDR
120+
* or SO_REUSEPORT, and is set before it tries to bind. A
121+
* value of zero doesn't set an option.
122+
* @param ec The error code, on failure
123+
*/
124+
tls_acceptor(
125+
const tls_context& ctx, const sock_address& addr, int queSize, int reuse,
126+
error_code& ec
127+
) noexcept;
107128
/**
108129
* Move constructor.
109130
* @param other The acceptor to move into this one.

0 commit comments

Comments
 (0)