libsshpp v0.1.7
Modern C++17 wrapper for libssh
Loading...
Searching...
No Matches
forwarding.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// Port forwarding primitives (direct-tcpip / tcpip-forward) plus LocalForward
4// and RemoteForward pumps, the Connector/BidirectionalPump primitive,
5// X11Forwarder and SocksProxy. See docs/design/07-api-forwarding.md.
6//
7// Scope note: LocalForward/RemoteForward serve one active connection at a
8// time (simple poll-based byte pump, POSIX sockets only). BidirectionalPump
9// always uses that same poll-based pump internally (rather than
10// ssh_connector_*) so it can report accurate byte_counts()/finished(); the
11// real ssh_connector_* API is still exposed as Connector for callers who want
12// to drive it via an Event directly.
13#pragma once
14
15#include <sshpp/config.hpp>
16
17#include <sshpp/channel.hpp>
19#include <sshpp/export.hpp>
20#include <sshpp/fwd.hpp>
21#include <sshpp/result.hpp>
22#include <sshpp/types.hpp>
23
24#include <atomic>
25#include <chrono>
26#include <cstdint>
27#include <functional>
28#include <mutex>
29#include <optional>
30#include <string>
31#include <string_view>
32#include <thread>
33#include <variant>
34
35namespace sshpp {
36
38 std::string host;
39 std::uint16_t port = 0;
40};
41
42struct UnixEndpoint { std::string path; };
43
44using ForwardTarget = std::variant<TcpEndpoint, UnixEndpoint>;
45
48SSHPP_API Result<Channel> open_direct(Session&, const ForwardTarget& remote,
49 TcpEndpoint origin = {"127.0.0.1", 0});
50
51struct SSHPP_API ForwardStats {
52 std::uint64_t connections = 0;
53 std::uint64_t bytes_out = 0;
54 std::uint64_t bytes_in = 0;
55 std::uint64_t rejected = 0;
56};
57
65
66class SSHPP_API RemoteForwardListener {
67public:
71 RemoteForwardListener& operator=(RemoteForwardListener&&) noexcept;
73
76 static Result<RemoteForwardListener> create(Session&, std::string_view bind_address,
77 std::uint16_t port);
78
79 explicit operator bool() const noexcept { return core_ != nullptr; }
80 std::uint16_t bound_port() const noexcept { return bound_port_; }
81
83 Result<std::optional<IncomingForward>> try_accept(std::chrono::milliseconds timeout);
84 Result<void> try_cancel();
85
86private:
87 RemoteForwardListener(detail::SessionCorePtr core, std::string bind_address, std::uint16_t bound_port)
88 : core_(std::move(core)), bind_address_(std::move(bind_address)), bound_port_(bound_port) {}
89
90 detail::SessionCorePtr core_;
91 std::string bind_address_;
92 std::uint16_t bound_port_ = 0;
93 bool cancelled_ = false;
94};
95
98class SSHPP_API LocalForward {
99public:
100 struct Options {
101 TcpEndpoint listen{"127.0.0.1", 0};
103 std::size_t buffer_size = 64 * 1024;
104 std::function<bool(const TcpEndpoint& peer)> accept_filter;
105 std::function<void(const ErrorInfo&)> on_error;
106 };
107
110 LocalForward(const LocalForward&) = delete;
112
113 Result<void> try_start();
114 void stop() noexcept;
115 bool running() const noexcept { return running_.load(); }
116 TcpEndpoint local_endpoint() const noexcept;
117 ForwardStats stats() const noexcept;
118
120 Result<void> try_run_until_stopped();
121
122private:
123 Result<void> bind_listener();
124 void accept_loop();
125 void pump_one_connection(int client_fd);
126
127 Session* session_;
128 Options options_;
129 int listen_fd_ = -1;
130 std::uint16_t bound_port_ = 0;
131 std::atomic<bool> running_{false};
132 std::atomic<bool> stop_requested_{false};
133 std::thread thread_;
134 mutable std::mutex stats_mutex_;
135 ForwardStats stats_;
136};
137
139class SSHPP_API RemoteForward {
140public:
141 struct Options {
142 std::string bind_address = "localhost";
143 std::uint16_t remote_port = 0;
145 std::function<bool(const TcpEndpoint& originator)> accept_filter;
146 std::function<void(const ErrorInfo&)> on_error;
147 };
148
151 RemoteForward(const RemoteForward&) = delete;
153
154 Result<void> try_start();
155 Result<void> try_run_until_stopped();
156 void stop() noexcept;
157 std::uint16_t remote_port() const noexcept { return listener_.bound_port(); }
158 ForwardStats stats() const noexcept;
159
160private:
161 void run_loop();
162 void pump_one_connection(IncomingForward&&);
163
164 Session* session_;
165 Options options_;
166 RemoteForwardListener listener_;
167 std::atomic<bool> stop_requested_{false};
168 std::thread thread_;
169 mutable std::mutex stats_mutex_;
170 ForwardStats stats_;
171};
172
173#if SSHPP_HAS_CONNECTOR
176class SSHPP_API Connector {
177public:
178 explicit Connector(Session&);
179 ~Connector();
180 Connector(Connector&&) noexcept;
181 Connector& operator=(Connector&&) noexcept;
182 Connector(const Connector&) = delete;
183
184 Result<void> try_set_in_channel(Channel&, Stream = Stream::stdout_);
185 Result<void> try_set_out_channel(Channel&, Stream = Stream::stdout_);
186 Result<void> try_set_in_fd(int) noexcept;
187 Result<void> try_set_out_fd(int) noexcept;
188
189 native_connector native_handle() const noexcept { return native_; }
190
191private:
192 native_connector native_ = nullptr;
193};
194#endif
195
202class SSHPP_API BidirectionalPump {
203public:
204 BidirectionalPump(Channel&, int local_fd, std::size_t buffer_size = 64 * 1024);
208
210 Result<void> try_run_until_stopped();
211 void stop() noexcept;
212 bool finished() const noexcept { return finished_.load(); }
213 std::pair<std::uint64_t, std::uint64_t> byte_counts() const noexcept;
214
215private:
216 Channel* channel_;
217 int local_fd_;
218 std::size_t buffer_size_;
219 std::atomic<bool> stop_requested_{false};
220 std::atomic<bool> finished_{false};
221 mutable std::mutex stats_mutex_;
222 ForwardStats stats_;
223};
224
225// -------------------------------------------------------------------- X11 ----
226
228 bool single_connection = false;
229 std::string auth_protocol = "MIT-MAGIC-COOKIE-1";
230 std::string auth_cookie; // hex; empty -> generate a random one
231 std::uint32_t screen_number = 0;
232};
233
239class SSHPP_API X11Forwarder {
240public:
241 struct Options {
243 ForwardTarget display_target; // defaults to $DISPLAY parsing if left empty
244 bool trusted = false;
245 };
246
247 // No `= {}` default here: GCC rejects a default argument whose type is a
248 // nested class of the same enclosing class when that nested class has a
249 // default member initializer (a `<brace-enclosed initializer list>` from
250 // conversion error). Callers wanting defaults pass `Options{}` explicitly.
251 explicit X11Forwarder(Channel& session_channel, Options options);
252
253 Result<void> try_request();
255 Result<std::optional<Channel>> try_accept(std::chrono::milliseconds timeout);
257 Result<void> try_run_until_stopped();
258 void stop() noexcept;
259
261 static Result<ForwardTarget> target_from_display(std::string_view display);
262
263private:
264 Channel* session_channel_;
265 Options options_;
266 std::atomic<bool> stop_requested_{false};
267};
268
269// ----------------------------------------------------------------- SOCKS ----
270
280class SSHPP_API SocksProxy {
281public:
282 struct Options {
283 TcpEndpoint listen{"127.0.0.1", 1080};
284 bool allow_socks4 = false;
285 std::size_t max_connections = 128;
286 std::size_t buffer_size = 64 * 1024;
288 std::function<bool(const ForwardTarget&)> allow;
289 std::function<void(const ErrorInfo&)> on_error;
290 };
291
293 ~SocksProxy();
294 SocksProxy(const SocksProxy&) = delete;
295 SocksProxy& operator=(const SocksProxy&) = delete;
296
297 Result<void> try_start();
298 Result<void> try_run_until_stopped();
299 void stop() noexcept;
300 bool running() const noexcept { return running_.load(); }
301 TcpEndpoint local_endpoint() const noexcept { return {options_.listen.host, bound_port_}; }
302 ForwardStats stats() const noexcept;
303
304private:
305 Result<void> bind_listener();
306 void accept_loop();
307 void serve_one_connection(int client_fd);
308
309 Session* session_;
310 Options options_;
311 int listen_fd_ = -1;
312 std::uint16_t bound_port_ = 0;
313 std::atomic<bool> running_{false};
314 std::atomic<bool> stop_requested_{false};
315 std::thread thread_;
316 mutable std::mutex stats_mutex_;
317 ForwardStats stats_;
318};
319
320} // namespace sshpp
321
322#if SSHPP_HEADER_ONLY
324#endif
Definition forwarding.hpp:202
BidirectionalPump & operator=(const BidirectionalPump &)=delete
BidirectionalPump(const BidirectionalPump &)=delete
Definition channel.hpp:36
Definition forwarding.hpp:98
LocalForward(const LocalForward &)=delete
LocalForward & operator=(const LocalForward &)=delete
Definition forwarding.hpp:66
std::uint16_t bound_port() const noexcept
Definition forwarding.hpp:80
ssh -R equivalent: accept loop that connects each inbound channel to a local endpoint.
Definition forwarding.hpp:139
RemoteForward & operator=(const RemoteForward &)=delete
RemoteForward(const RemoteForward &)=delete
Definition result.hpp:16
Definition session.hpp:41
Definition forwarding.hpp:280
SocksProxy & operator=(const SocksProxy &)=delete
TcpEndpoint local_endpoint() const noexcept
Definition forwarding.hpp:301
SocksProxy(const SocksProxy &)=delete
Definition forwarding.hpp:239
std::shared_ptr< SessionCore > SessionCorePtr
Definition session_core.hpp:43
Definition auth.hpp:18
SSHPP_INLINE Result< Channel > open_direct(Session &session, const ForwardTarget &remote, TcpEndpoint origin)
Definition forwarding.ipp:187
std::variant< TcpEndpoint, UnixEndpoint > ForwardTarget
Definition forwarding.hpp:44
::ssh_connector_struct * native_connector
Definition native_fwd.hpp:35
Definition error.hpp:110
Definition error.hpp:119
Definition forwarding.hpp:51
ssh -R originator/listener primitive: server listens, hands us inbound channels.
Definition forwarding.hpp:59
TcpEndpoint originator
Attacker-controlled: the server reports this, never use it for authorization.
Definition forwarding.hpp:63
std::uint16_t bound_port
Definition forwarding.hpp:61
Channel channel
Definition forwarding.hpp:60
Definition forwarding.hpp:100
ForwardTarget target
Definition forwarding.hpp:102
std::function< void(const ErrorInfo &)> on_error
Definition forwarding.hpp:105
std::function< bool(const TcpEndpoint &peer)> accept_filter
Definition forwarding.hpp:104
Definition forwarding.hpp:141
std::function< bool(const TcpEndpoint &originator)> accept_filter
Definition forwarding.hpp:145
ForwardTarget local_target
Definition forwarding.hpp:144
std::function< void(const ErrorInfo &)> on_error
Definition forwarding.hpp:146
Definition forwarding.hpp:282
std::function< void(const ErrorInfo &)> on_error
Definition forwarding.hpp:289
std::function< bool(const ForwardTarget &)> allow
Called with the requested destination; return false to refuse.
Definition forwarding.hpp:288
Definition forwarding.hpp:37
std::string host
Definition forwarding.hpp:38
std::uint16_t port
Definition forwarding.hpp:39
Definition forwarding.hpp:42
std::string path
Definition forwarding.hpp:42
Definition forwarding.hpp:241
X11Request request
Definition forwarding.hpp:242
ForwardTarget display_target
Definition forwarding.hpp:243
Definition forwarding.hpp:227
std::string auth_cookie
Definition forwarding.hpp:230
std::string auth_protocol
Definition forwarding.hpp:229
std::uint32_t screen_number
Definition forwarding.hpp:231
bool single_connection
Definition forwarding.hpp:228