Skip to content

Commit fd04f3d

Browse files
utezduyarrawalexe
andauthored
Add AF_UNIX support for destination (-d) mode (#211)
* Add AF_UNIX support for destination (-d) mode This implementation adds AF_UNIX support to the destination mode so that the connectivity can be isolated to the host running the aws iot agent. Particularly useful in containers. Tests performed 1) Implemented an echo server with below: socat UNIX-LISTEN:/tmp/echo.sock,fork EXEC:/bin/cat Connected the local proxy with -d /tmp/echo.sock Mapped this with another instance -s 3131. Connected to it with telnet localhost 3131. 2) Mapped localproxy with -d /run/ssh-unix-local/socket to the running container's systemd generated ssh socket Started another instance of localproxy with -s 2222 Connected to it with ssh -p 2222 root@localhost * Exclude AF_UNIX destination support from windows OS * nit: format files * Update function name to better reflect the synchronicity --------- Co-authored-by: Anubhav Rawal <anubhavrawal00@gmail.com>
1 parent 3812a51 commit fd04f3d

4 files changed

Lines changed: 143 additions & 2 deletions

File tree

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,18 +748,26 @@ sets the mappings between port and service identifier. For example: SSH1=5555
748748
or 5555.
749749

750750
- It follows format serviceId1=endpoint1, serviceId2=endpoint2, ...
751-
- Endpoint can be IP address:port , port or hostname:port.
751+
- Endpoint can be IP address:port, port, hostname:port, or a Unix domain socket
752+
path.
753+
- Unix domain socket paths are detected when the endpoint contains a '/'
754+
character (e.g., /run/ssh-unix-local/socket, /tmp/app.sock, ./local.sock).
755+
Note: Unix domain sockets are only supported on Unix/Linux/macOS platforms.
752756
- If only one port is needed to start local proxy, service ID is not needed. You
753757
can simply pass the port used, for example, 5555.
754758
- An item of the mapping SSH1=5555 means that local proxy will forward data
755759
received from the tunnel to TCP port 5555 for service ID SSH1.
760+
- An item of the mapping SSH1=/run/app.sock means that local proxy will forward
761+
data received from the tunnel to the Unix domain socket at /run/app.sock for
762+
service ID SSH1.
756763
- The value of service ID and how many service IDs are used needs to match with
757764
**services** in open tunnel call. For example:
758765
```shell script
759766
aws iotsecuretunneling open-tunnel --destination-config thingName=foo,services=SSH1,SSH2
760767
```
761768
Then to start local proxy in destination mode, need to use:
762-
`-d SSH1=$port1,SSH2=$port2`
769+
`-d SSH1=$port1,SSH2=$port2` or
770+
`-d SSH1=/path/to/socket1,SSH2=/path/to/socket2`
763771

764772
**-b/--local-bind-address [argvalue]** Specifies the local bind address (network
765773
interface) to use for listening for new connections when running the local proxy

src/InputValidation.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ namespace iot {
223223
return;
224224
}
225225

226+
// Check if it's a Unix socket path (contains '/')
227+
// Unix socket paths: /run/socket, ./socket, /tmp/sock
228+
if (endpoint.find('/') != std::string::npos) {
229+
validate_path(endpoint);
230+
return;
231+
}
232+
226233
// It's host:port or just host - validate the address part
227234
validate_bind_address(endpoint);
228235

src/TcpAdapterProxy.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33
#include "TcpAdapterProxy.h"
4+
#include "InputValidation.h"
45
#include "ProxySettings.h"
56
#include "WebProxyAdapter.h"
67
#include "config/ConfigFile.h"
78
#include <boost/algorithm/string.hpp>
89
#include <boost/asio.hpp>
10+
#ifndef _WIN32
11+
#include <boost/asio/local/stream_protocol.hpp>
12+
#include <unistd.h> // for close() and unlink()
13+
#endif
914
#include <boost/asio/ssl/host_name_verification.hpp>
1015
#include <boost/beast/core/flat_buffer.hpp>
1116
#include <boost/beast/websocket.hpp>
@@ -3147,6 +3152,92 @@ namespace iot {
31473152
);
31483153
}
31493154

3155+
#ifndef _WIN32
3156+
void tcp_adapter_proxy::connect_and_setup_unix_socket(
3157+
tcp_adapter_context &tac,
3158+
std::shared_ptr<basic_retry_config> retry_config,
3159+
const string &service_id,
3160+
const uint32_t &connection_id,
3161+
const std::string &socket_path
3162+
) {
3163+
BOOST_LOG_SEV(log, trace)
3164+
<< "Connecting to Unix domain socket: " << socket_path
3165+
<< " for service id: " << service_id
3166+
<< " connection id: " << connection_id;
3167+
3168+
tcp_client::pointer client
3169+
= tac.serviceId_to_tcp_client_map[service_id];
3170+
3171+
// Create a Unix domain socket and connect
3172+
boost::asio::local::stream_protocol::socket unix_socket(tac.io_ctx);
3173+
boost::system::error_code connect_ec;
3174+
3175+
try {
3176+
unix_socket.connect(
3177+
boost::asio::local::stream_protocol::endpoint(socket_path),
3178+
connect_ec
3179+
);
3180+
3181+
if (connect_ec) {
3182+
BOOST_LOG_SEV(log, error)
3183+
<< (boost::format(
3184+
"Could not connect to Unix socket %1% -- %2%"
3185+
)
3186+
% socket_path % connect_ec.message())
3187+
.str();
3188+
basic_retry_execute(
3189+
log, retry_config, [this, &tac, service_id]() {
3190+
BOOST_LOG_SEV(log, trace)
3191+
<< "resetting stream for service id:"
3192+
<< service_id
3193+
<< ", then listen for stream start";
3194+
async_send_stream_reset(tac, service_id);
3195+
setup_tcp_socket(std::ref(tac), service_id);
3196+
}
3197+
);
3198+
} else {
3199+
BOOST_LOG_SEV(log, info)
3200+
<< "Connected to Unix socket: " << socket_path;
3201+
3202+
// Transfer the Unix socket to the TCP connection
3203+
// Get the native file descriptor and assign it to the TCP
3204+
// socket
3205+
int fd = unix_socket.release();
3206+
boost::system::error_code assign_ec;
3207+
client->connectionId_to_tcp_connection_map[connection_id]
3208+
->socket()
3209+
.assign(boost::asio::ip::tcp::v4(), fd, assign_ec);
3210+
3211+
if (assign_ec) {
3212+
BOOST_LOG_SEV(log, error)
3213+
<< "Failed to assign Unix socket to TCP socket: "
3214+
<< assign_ec.message();
3215+
::close(fd);
3216+
basic_retry_execute(
3217+
log, retry_config, [this, &tac, service_id]() {
3218+
async_send_stream_reset(tac, service_id);
3219+
setup_tcp_socket(std::ref(tac), service_id);
3220+
}
3221+
);
3222+
} else {
3223+
async_setup_bidirectional_data_transfers(
3224+
tac, service_id, connection_id
3225+
);
3226+
}
3227+
}
3228+
} catch (const std::exception &e) {
3229+
BOOST_LOG_SEV(log, error)
3230+
<< "Exception connecting to Unix socket: " << e.what();
3231+
basic_retry_execute(
3232+
log, retry_config, [this, &tac, service_id]() {
3233+
async_send_stream_reset(tac, service_id);
3234+
setup_tcp_socket(std::ref(tac), service_id);
3235+
}
3236+
);
3237+
}
3238+
}
3239+
#endif
3240+
31503241
void tcp_adapter_proxy::async_resolve_destination_for_connect(
31513242
tcp_adapter_context &tac,
31523243
std::shared_ptr<basic_retry_config> retry_config,
@@ -3291,6 +3382,28 @@ namespace iot {
32913382
);
32923383
}
32933384

3385+
// Check if endpoint is a Unix domain socket path (contains '/')
3386+
#ifndef _WIN32
3387+
if (endpoint.find('/') != std::string::npos) {
3388+
BOOST_LOG_SEV(log, debug)
3389+
<< "Detected Unix domain socket path: " << endpoint;
3390+
connect_and_setup_unix_socket(
3391+
tac, retry_config, service_id, connection_id, endpoint
3392+
);
3393+
return;
3394+
}
3395+
#else
3396+
// On Windows, reject Unix socket paths with a clear error
3397+
if (endpoint.find('/') != std::string::npos) {
3398+
BOOST_LOG_SEV(log, error)
3399+
<< "Unix domain sockets are not supported on Windows. "
3400+
<< "Path provided: " << endpoint;
3401+
throw std::runtime_error(
3402+
"Unix domain sockets are not supported on Windows"
3403+
);
3404+
}
3405+
#endif
3406+
32943407
if (tac.adapter_config.bind_address.has_value()) {
32953408
BOOST_LOG_SEV(log, debug)
32963409
<< "Resolving local address host: "

src/TcpAdapterProxy.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include "WebSocketStream.h"
1414
#include <boost/asio.hpp>
1515
#include <boost/asio/ip/tcp.hpp>
16+
#ifndef _WIN32
17+
#include <boost/asio/local/stream_protocol.hpp>
18+
#endif
1619
#include <boost/asio/ssl/host_name_verification.hpp>
1720
#include <boost/beast/core/flat_buffer.hpp>
1821
#include <boost/beast/websocket.hpp>
@@ -449,6 +452,16 @@ namespace iot {
449452
tcp::resolver::results_type results
450453
);
451454

455+
#ifndef _WIN32
456+
void connect_and_setup_unix_socket(
457+
tcp_adapter_context &tac,
458+
std::shared_ptr<basic_retry_config> retry_config,
459+
const std::string &service_id,
460+
const uint32_t &connection_id,
461+
const std::string &socket_path
462+
);
463+
#endif
464+
452465
bool process_incoming_websocket_buffer(
453466
tcp_adapter_context &tac,
454467
boost::beast::multi_buffer &message_buffer

0 commit comments

Comments
 (0)