tcpwarp/lib.rs
1/*!
2# A userspace tunnel between two hosts mapping ports on client machine to addresses reachable from server machine
3
4```bash
5tcp-warp server
6tcp-warp client -c 8080:towel.blinkenlights.nl:23
7nc 127.0.0.1 8080
8```
9
10## Features
11
121. A userspace tunnel to connect ports on client network with connections available on server side.
131. Uses only single port.
141. Client push of addresses to connect from server.
15
16## Installation
17
18With [cargo](https://www.rust-lang.org/learn/get-started):
19
20```bash
21cargo install tcp-warp-cli
22```
23
24## Usage
25
26To create a tunnel we need to start a server listening on some port and then connect to it with a client.
27
28### Docker usage for server part
29
30```bash
31docker run --rm -d -p 18000:18000 tcpwarp/tcpwarp
32```
33
34or with custom listen port (ex: 18234):
35
36```bash
37docker run --rm -d -p 18234:18234 tcpwarp/tcpwarp tcp-warp server --listen=0.0.0.0:18234
38```
39
40### Simple local running port remapper
41
421. Start server:
43
44 ```bash
45 tcp-warp server
46 ```
47
481. Start client:
49
50 ```bash
51 tcp-warp client -c 8080:towel.blinkenlights.nl:23
52 ```
53
541. Enjoy the show:
55
56 ```bash
57 nc 127.0.0.1 8080
58 ```
59
601. This example uses default listen and connect interfaces. In a real life scenario you need at least provide -t / --tunnel parameter to client:
61
62 ```bash
63 tcp-warp client -t host:port ...
64 ```
65
66Both client and server have address on which they listen for incoming connections and client additionally have parameter to specify connection address.
67
68Next we look at more specific example.
69
70### Use case: running Docker on machine without Docker daemon installed with Docker daemon behind SSH
71
72Background:
73
74- client: client machine runs on Windows, has Windows version of `tcp-warp` and Docker CLI installed. Client cannot run Docker daemon.
75- public: master node accessible with SSH from which Docker daemon node can be accessed.
76- docker: docker daemon node accessible with SSH.
77
78Target:
79
80Run Docker over tcp transport, allowing `client` to build and run containers. Environment should be available for each developer independent of other.
81
82Solution:
83
84Run on `docker` machine Docker-in-Docker container (`dind`) using tcp host protocol. Use `DOCKER_HOST` environment variable on `client` to connect to `dind`. `dind` is bindet to host port on `docker` host and forwarded via `public` with SSH port-forwarding.
85
86The sequence of commands can be following:
87
88#### Initial sequence (installation)
89
901. Go to `docker` node and start required containers:
91
92 ```bash
93 user@client $ ssh user1@public
94 user1@public $ ssh user2@docker
95 user2@docker $ docker run --rm --privileged -p 2375:2375 -p 18000:18000 -d --name some-docker docker:dind dockerd --host=tcp://0.0.0.0:2375
96 user2@docker $ DOCKER_HOST=tcp://127.0.0.1:2375 docker run --rm -p 18000:18000 -d --name some-docker-tcp-warp tcpwarp/tcpwarp
97 ```
98
991. Disconnect from `docker` and `public` nodes.
100
101#### Normal sequence (usage)
102
1031. Connect to `public` node with `ssh` and forward port for `tcp-warp`:
104
105 ```bash
106 ssh -L 18000:docker:18000 user1@public
107 ```
108
1091. Connect to Docker daemon with `tcp-warp client` on `client` machine:
110
111 ```bash
112 tcp-warp client -c 10001:172.18.0.1:2375
113 ```
114
115 `172.18.0.1` here is the address of host node in `dind`.
116
1171. Export DOCKER_HOST environment variable on `client` machine:
118
119 ```bash
120 export DOCKER_HOST=tcp://127.0.0.1:10001
121 ```
122
1231. Run docker commands from `client`:
124
125 ```bash
126 docker ps
127 docker run hello-world
128 docker run -it alpine ash
129 ```
130
131#### Additional services
132
133We can start additional services and relaunch `tcp-warp client` with additional `-c` for these services.
134
135Simple example with `whoami` service:
136
1371. Create network to use for hostname resolution. Start `whoami` service with all above steps done. Connect tcp-warp container to new network:
138
139 ```bash
140 docker network create our-network
141 docker run --rm -d --net our-network --name whoami containous/whoami
142 docker network connect our-network some-docker-tcp-warp
143 ```
144
1451. Stop `tcp-warp client`. Start it with additional port mapping for `whoami` service:
146
147 ```bash
148 tcp-warp client -c 10001:172.18.0.1:2375 -c 8080:whoami:80
149 ```
150
1511. Test `whoami` service:
152
153 ```bash
154 $ curl http://localhost:8080/
155 Hostname: 9fe704cf0e87
156 IP: 127.0.0.1
157 IP: 172.18.0.3
158 IP: 172.19.0.3
159 RemoteAddr: 172.19.0.2:44612
160 GET / HTTP/1.1
161 Host: localhost:8080
162 User-Agent: curl/7.64.1
163 ```
164
165*/
166use bytes::{Buf, BufMut, BytesMut};
167use futures::{
168 future::{abortable, AbortHandle},
169 prelude::*,
170 try_join,
171};
172use log::*;
173use std::{
174 collections::HashMap,
175 convert::TryInto,
176 error::Error,
177 net::{IpAddr, SocketAddr},
178 str::FromStr,
179 sync::Arc,
180 time::Duration,
181};
182use tokio::{
183 net::{TcpListener, TcpStream, ToSocketAddrs},
184 prelude::*,
185 spawn,
186 sync::{
187 mpsc::{channel, Sender},
188 oneshot,
189 },
190 time::delay_for,
191};
192use tokio_util::codec::{Decoder, Encoder, Framed};
193use uuid::Uuid;
194
195mod client;
196mod proto;
197mod server;
198
199pub use client::TcpWarpClient;
200pub use proto::{TcpWarpMessage, TcpWarpProto, TcpWarpProtoClient, TcpWarpProtoHost};
201pub use server::TcpWarpServer;
202
203#[derive(Debug, Clone, PartialEq)]
204pub struct TcpWarpPortConnection {
205 client_port: Option<u16>,
206 host: Option<String>,
207 port: u16,
208}
209
210impl FromStr for TcpWarpPortConnection {
211 type Err = io::Error;
212
213 fn from_str(s: &str) -> Result<Self, Self::Err> {
214 let mut parts = s.split(':');
215
216 match (parts.next(), parts.next(), parts.next(), parts.next()) {
217 (Some(client_port), host, Some(port), None) => {
218 match (client_port.parse(), port.parse()) {
219 (Ok(client_port), Ok(port)) => Ok(TcpWarpPortConnection {
220 client_port: Some(client_port),
221 host: host.map(str::to_owned),
222 port,
223 }),
224 _ => Err(io::Error::new(
225 io::ErrorKind::Other,
226 "cannot parse port mapping",
227 )),
228 }
229 }
230 (Some(client_port_or_host), Some(port), None, None) => {
231 let port = match port.parse() {
232 Ok(port) => port,
233 Err(_) => {
234 return Err(io::Error::new(
235 io::ErrorKind::Other,
236 "cannot parse port mapping",
237 ))
238 }
239 };
240 let client_port: Result<u16, _> = client_port_or_host.parse();
241 if let Ok(client_port) = client_port {
242 Ok(TcpWarpPortConnection {
243 client_port: Some(client_port),
244 host: None,
245 port,
246 })
247 } else {
248 Ok(TcpWarpPortConnection {
249 client_port: None,
250 host: Some(client_port_or_host.to_owned()),
251 port,
252 })
253 }
254 }
255 (Some(port), None, None, None) => {
256 let port = match port.parse() {
257 Ok(port) => port,
258 Err(_) => {
259 return Err(io::Error::new(
260 io::ErrorKind::Other,
261 "cannot parse port mapping",
262 ))
263 }
264 };
265 Ok(TcpWarpPortConnection {
266 client_port: None,
267 host: None,
268 port,
269 })
270 }
271 _ => Err(io::Error::new(
272 io::ErrorKind::Other,
273 "cannot parse port mapping",
274 )),
275 }
276 }
277}
278
279pub struct TcpWarpConnection {
280 sender: Sender<TcpWarpMessage>,
281 connected_sender: Option<oneshot::Sender<Result<(), io::Error>>>,
282}
283
284#[cfg(test)]
285mod tests {
286
287 use super::*;
288
289 #[test]
290 fn connection_from_str() {
291 assert_eq!(
292 Ok(TcpWarpPortConnection {
293 client_port: None,
294 host: None,
295 port: 8080
296 }),
297 "8080".parse().map_err(|_| ())
298 );
299 assert_eq!(
300 Ok(TcpWarpPortConnection {
301 client_port: Some(8081),
302 host: None,
303 port: 8080
304 }),
305 "8081:8080".parse().map_err(|_| ())
306 );
307 assert_eq!(
308 Ok(TcpWarpPortConnection {
309 client_port: Some(8081),
310 host: Some("localhost".into()),
311 port: 8080
312 }),
313 "8081:localhost:8080".parse().map_err(|_| ())
314 );
315 }
316}