vi / websocat

Command-line client for WebSockets, like netcat (or curl) for ws:// with advanced socat-like functions
MIT License
7.01k stars 272 forks source link

piping with tcp reuse-raw splits payload #169

Closed philipprimli closed 1 year ago

philipprimli commented 1 year ago

let's assume test_message.txt defined as 74 65 73 74 0a 20 --> so payload is 6 bytes

1st Terminal: websocat -u -t tcp-l:127.0.0.1:1234 reuse-raw:- | websocat -v -n --ping-interval 3 --ws-c-uri=wss://ws.postman-echo.com/raw --no-line --protocol 'v11.stomp' - ws-c:cmd:'openssl s_client -connect ws.postman-echo.com:443 -quiet -keylogfile key_log.txt' 2nd Terminal: cat test_message.txt | nc -q 1 127.0.0.1 1234

capturing with tcpdump reveals payload being splitted into 2 parts:

Frame 257: 108 bytes on wire (864 bits), 108 bytes captured (864 bits) Linux cooked capture v1 Internet Protocol Version 4, Src: 192.168.128.12, Dst: 52.7.229.150 Transmission Control Protocol, Src Port: 32958, Dst Port: 443, Seq: 861, Ack: 6020, Len: 40 Transport Layer Security WebSocket 1... .... = Fin: True .000 .... = Reserved: 0x0 .... 0001 = Opcode: Text (1) 1... .... = Mask: True .000 0101 = Payload length: 5 Masking-Key: c381ba5a Masked payload Payload Line-based text data (1 lines) test\n

Payload in HEX: 74 65 73 74 0a

Frame 261: 104 bytes on wire (832 bits), 104 bytes captured (832 bits) Linux cooked capture v1 Internet Protocol Version 4, Src: 192.168.128.12, Dst: 52.7.229.150 Transmission Control Protocol, Src Port: 32958, Dst Port: 443, Seq: 901, Ack: 6056, Len: 36 Transport Layer Security WebSocket 1... .... = Fin: True .000 .... = Reserved: 0x0 .... 0001 = Opcode: Text (1) 1... .... = Mask: True .000 0001 = Payload length: 1 Masking-Key: 1ff85fe5 Masked payload Payload Line-based text data (1 lines) \000

Payload in HEX: 00

Whereas piping the file directly to websocat would transmit the payload correctly: cat test_message.txt | websocat -v -n --ping-interval 3 --ws-c-uri=wss://ws.postman-echo.com/raw --no-line --protocol 'v11.stomp' - ws-c:cmd:'openssl s_client -connect ws.postman-echo.com:443 -quiet -keylogfile key_log.txt'

Frame 2192: 109 bytes on wire (872 bits), 109 bytes captured (872 bits) Linux cooked capture v1 Internet Protocol Version 4, Src: 192.168.128.12, Dst: 3.218.100.165 Transmission Control Protocol, Src Port: 58000, Dst Port: 443, Seq: 673, Ack: 5848, Len: 41 Transport Layer Security WebSocket 1... .... = Fin: True .000 .... = Reserved: 0x0 .... 0001 = Opcode: Text (1) 1... .... = Mask: True .000 0110 = Payload length: 6 Masking-Key: 2080be21 Masked payload Payload Line-based text data (2 lines) test\n \000

Payload in HEX: 74 65 73 74 0a 00

vi commented 1 year ago

Websocat's binary mode (here it reads stdin in binary mode, but then encodes chunks as text WebSocket messages) is delimited by syscalls. Each read syscall returning a portion of data gets converted to a WebSocket message. Directly piping file into Websocat and expecting it to be interpreted as a one chunk is also unreliable, as operating system is allowed to give file's data little by little (short reads). Short reads are just more probable when involving sockets or pipes compared to just reading from files.

Such mode where boundaries between chunks are insignificant is useful for tunneling e.g. TCP over WebSockets, or transferring large files chunk by chunk.

If you want direct control over chunking, you should probably use line2msg:- instead of just -, --base64 and --binary (or --base64-text and --text and removed --no-line) and format your file as one base64-encoded chunk per line instead of raw data. line2msg: will accumulate all the input until it encounters \n, then --base64-text (or --base64) would decode that buffer to the full text message with embedded newlines.

--binary-prefix, --text-prefix,--base64,--base64-text,msg2line: and line2msg: should together give complete control over WebSocket, allowing you to emit (and discern) both text and binary WebSocket messages with arbitrary content (including embedded newlines or zero bytes). This requires specially prepared data though.

You may also want to increase the -B buffer size.

There is currently no easy option to treat entire input as one chunk - it either waits for \n or \0 delimiter (buffering content) or just splits the stream to chunks arbitrarily. --max-messages 1 won't do the trick.

Special overlay like buffer-entire-input:- may be implemented in future.

philipprimli commented 1 year ago

ok, thanks. Your suggestion worked fine with me using a base64-encoded approach.