1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
use std::{ffi::OsString, net::SocketAddr, path::PathBuf};
use clap::Parser;
#[derive(Clone, Debug)]
pub struct CustomHeader {
pub name: String,
pub value: String,
}
impl CustomHeader {
fn interpret(x: &str) -> Result<CustomHeader, anyhow::Error> {
let Some((name, value)) = x.split_once(':') else {
anyhow::bail!("Custom header requires ':' to separate name and value")
};
Ok(CustomHeader {
name: name.trim().to_owned(),
value: value.trim().to_owned(),
})
}
}
#[derive(Parser, Debug)]
/// Tool to connect to WebSocket, listen them and do other network tricks
#[command(version, about)]
#[command(after_help(include_str!("help_addendum.txt")))]
pub struct WebsocatArgs {
/// Left endpoint (e.g. a WebSocket URL). May be prefixed by one or more overlays.
pub spec1: OsString,
/// Right endpoint (or stdout if omitted). May be prefixed by one or more overlays.
pub spec2: Option<OsString>,
/// do not execute this Websocat invocation, print equivalent Rhai script instead.
#[arg(long)]
pub dump_spec: bool,
/// do not execute this Websocat invocation, print debug representation of specified arguments.
#[arg(long)]
pub dump_spec_phase1: bool,
/// do not execute this Websocat invocation, print debug representation of specified arguments.
#[arg(long)]
pub dump_spec_phase2: bool,
/// execute specified file as Rhai script (e.g. resutling from --dump-spec option output)
#[arg(long, short = 'x')]
pub scenario: bool,
/// use text mode (one line = one WebSocket text message)
#[arg(long, short = 't')]
pub text: bool,
/// use binary mode (arbitrary byte chunk = one WebSocket binary message)
#[arg(long, short = 'b')]
pub binary: bool,
/// resolve hostnames to IP addresses late (every time when forwarding a connection) instead of one time at the beginning
#[arg(long)]
pub late_resolve: bool,
/// accept invalid domains and root certificates for TLS client connections
#[arg(long, short = 'k')]
pub insecure: bool,
/// manually specify domain for `tls:` overlay or override domain for `wss://` URLs
#[arg(long)]
pub tls_domain: Option<String>,
/// listen for WebSocket conenctions instead of establishing client WebSocket connection
#[arg(long, short = 's')]
pub server: bool,
/// log more data from `log:` overlay
#[arg(long)]
pub log_verbose: bool,
/// do not log full content of the data from `log:` overlay, just chunk lengths
#[arg(long)]
pub log_omit_content: bool,
/// use hex lines instead of escaped characters for `log:`` overlay.
#[arg(long)]
pub log_hex: bool,
/// automatically insert `log:` overlay in an apprioriate place to debug issues by displaying traffic chunks
#[arg(long)]
pub log_traffic: bool,
/// URI for `ws-c:` overlay.
#[arg(long)]
pub ws_c_uri: Option<String>,
/// paramemter for read_chunk_limiter: overlay, defaults to 1
#[arg(long)]
pub read_buffer_limit: Option<usize>,
/// paramemter for write_chunk_limiter: overlay, defaults to 1
#[arg(long)]
pub write_buffer_limit: Option<usize>,
/// override byte value that separates stdin-supplied text WebSocket messages
/// from each othe from default '\n'.
#[arg(long)]
pub separator: Option<u8>,
/// require this number of newline (or other) bytes to separate WebSocket messages
#[arg(long)]
pub separator_n: Option<usize>,
/// prevent mangling incoming text WebSocket by replacing `\n` (or other
/// separator sequence) with spaces (and trimming leading and trailing separator bytes)
#[arg(long)]
pub separator_inhibit_substitution: bool,
/// initial target sendto address for `udp-bind:` mode.
/// If unset, it will try to send to neutral address (unsuccessfully).
#[arg(long)]
pub udp_bind_target_addr: Option<SocketAddr>,
/// only allow incoming datagrams from specified target address for `upd-bind:` mode.
#[arg(long)]
pub udp_bind_restrict_to_one_address: bool,
/// automatically change target address for `udp-bind:` mode based in coming datagrams
#[arg(long)]
pub udp_bind_redirect_to_last_seen_address: bool,
/// turn `udp-bind:` into `udp-connect:` as soon as we receive some datagram.
/// Implied when `--udp-bind-target-addr` is not specified
#[arg(long)]
pub udp_bind_connect_to_first_seen_address: bool,
/// ignore failed `sendto` calls.
/// Attempts to send without a configured target address are ignored implicitly.
#[arg(long)]
pub udp_bind_inhibit_send_errors: bool,
/// Client timeout of udp-server: mode
#[arg(long)]
pub udp_server_timeout_ms: Option<u64>,
/// Maximum number of parallel handlers in udp-server: mode
#[arg(long)]
pub udp_server_max_clients: Option<usize>,
/// Size of receive buffer for udp-server: mode.
/// `-B` is distinct, but can also affect operation.
#[arg(long)]
pub udp_server_buffer_size: Option<usize>,
/// Queue length for udp-server: mode
#[arg(long)]
pub udp_server_qlen: Option<usize>,
/// Delay receiving more datagrams in udp-server: mode instead of dropping them in case of slow handlers
#[arg(long)]
pub udp_server_backpressure: bool,
/// Command line arguments for `exec:` endpoint.
///
/// This option is interpreted specially: it stops processing all other options
/// uses everything after it as a part of the command line
#[arg(long, num_args(..), allow_hyphen_values(true))]
pub exec_args: Vec<OsString>,
/// Immediately expire `cmd:` or `exec:` endpoints if child process terminates.
///
/// This may discard some data that remained buffered in a pipe.
#[arg(long)]
pub exec_monitor_exits: bool,
/// On Unix, try to set uid to this numeric value for the subprocess
#[arg(long)]
pub exec_uid: Option<u32>,
/// On Unix, try to set uid to this numeric value for the subprocess
#[arg(long)]
pub exec_gid: Option<u32>,
/// Try to change current directory to this value for the subprocess
#[arg(long)]
pub exec_chdir: Option<PathBuf>,
/// On Windows, try to set this numeric process creation flags
#[arg(long)]
pub exec_windows_creation_flags: Option<u32>,
/// On Unix, set first subprocess's argv[0] to this value
#[arg(long)]
pub exec_arg0: Option<OsString>,
/// Make dummy nodes also immediately signal hangup.
#[arg(long)]
pub dummy_hangup: bool,
/// Exit the whole process if hangup is detected.
#[arg(long)]
pub exit_on_hangup: bool,
/// Transfer data only from left to right specifier
#[arg(long, short = 'u')]
pub unidirectional: bool,
/// Transfer data only from right to left specifier
#[arg(long, short = 'U')]
pub unidirectional_reverse: bool,
/// Do not shutdown inactive directions when using `-u` or `-U`.
#[arg(long)]
pub unidirectional_late_drop: bool,
/// Stop transferring data when one of the transfer directions reached EOF.
#[arg(long, short = 'E')]
pub exit_on_eof: bool,
/// Override buffer size for main data transfer session.
/// Note that some overlays and endpoints may have separate buffers with sepaparately adjustable sizes.
#[arg(long, short = 'B')]
pub buffer_size: Option<usize>,
/// Do not send WebSocket close message when there is no more data to send there.
#[arg(long, short = 'n')]
pub no_close: bool,
/// Do not flush after each WebSocket frame.
#[arg(long)]
pub ws_no_flush: bool,
/// Shutdown write direction of the underlying socket backing a WebSocket on EOF.
#[arg(long)]
pub ws_shutdown_socket_on_eof: bool,
/// Do not fail WebSocket connections if maksed frame arrives instead of unmasked or vice versa.
#[arg(long)]
pub ws_ignore_invalid_masks: bool,
/// Ignore absense or invalid values of `Sec-Websocket-*` things and just continue connecting.
#[arg(long)]
pub ws_dont_check_headers: bool,
/// Do not automatically insert buffering layer after WebSocket if underlying connections does not support `writev`.
#[arg(long)]
pub ws_no_auto_buffer: bool,
/// Skip request or response headers for Websocket upgrade
#[arg(long)]
pub ws_omit_headers: bool,
/// Add this custom header to WebSocket upgrade request when connecting to a Websocket.
/// Colon separates name and value.
#[arg(long, short='H', value_parser=CustomHeader::interpret)]
pub header: Vec<CustomHeader>,
/// Add this custom header to WebSocket upgrade response when serving a Websocket connection.
/// Colon separates name and value.
#[arg(long, value_parser=CustomHeader::interpret)]
pub server_header: Vec<CustomHeader>,
/// Specify this Sec-WebSocket-Protocol: header when connecting to a WebSocket.
#[arg(long)]
pub protocol: Option<String>,
/// Use this `Sec-WebSocket-Protocol:` value when serving a Websocket,
/// and reject incoming connections if the don't specify this protocol.
#[arg(long)]
pub server_protocol: Option<String>,
/// Don't reject incoming connections that fail to specify proper `Sec-WebSocket-Protocol`
/// header. The header would be omitted from the response in this case.
#[arg(long)]
pub server_protocol_lax: bool,
/// If client specifies Sec-WebSocket-Protocol, choose the first mentioned protocol
/// and use if for response's Sec-WebSocket-Protocol.
#[arg(long)]
pub server_protocol_choose_first: bool,
/// When listening UNIX sockets, attempt to delete the file first to avoid the failure to bind
#[arg(long)]
pub unlink: bool,
/// When listening UNIX sockets, change socket filesystem permissions to only allow owner connections
#[arg(long)]
pub chmod_owner: bool,
/// When listening UNIX sockets, change socket filesystem permissions to allow owner and group connections
#[arg(long)]
pub chmod_group: bool,
/// When listening UNIX sockets, change socket filesystem permissions to allow connections from everywhere
#[arg(long)]
pub chmod_everyone: bool,
}