The vi answer provides a very useful tool. websocat
is so easy to use.
I want to do it only with bash
builtins. I think it can be useful to others to understand better the protocol.
The websocket protocol handshake is easy.
To open a tcp
channel in bash we can use:
wshost=echo.websocket.org
wsport=80
exec 3<>/dev/tcp/${wshost}/${wsport}
Then we can read and write data to that tcp
connection using file descriptor &3
.
First let's open a read channel:
CR=$(echo -en "\r")
while read <&3; do echo "WS MSG:[${REPLY//$CR/}]"; done &
We don't really need replace the carriage return bytes \r
, but the terminal output is weird if we don't.
Each websocket server implementation can require specific details. websocket.org requires Origin
header and \r
byte before end line.
Then we can start the connection upgrade
:
echo -e "GET / HTTP/1.1\r
Host: ${wshost}\r
Connection: Upgrade\r
Upgrade: websocket\r
Sec-WebSocket-Accept: $(echo -n "somekey"|base64)\r
Origin: http://www.websocket.org\r
\r" >&3
Imediatelly we see the ws upgrade output:
$ WS MSG:[HTTP/1.1 101 Web Socket Protocol Handshake]
WS MSG:[Access-Control-Allow-Credentials: true]
WS MSG:[Access-Control-Allow-Headers: content-type]
WS MSG:[Access-Control-Allow-Headers: authorization]
WS MSG:[Access-Control-Allow-Headers: x-websocket-extensions]
WS MSG:[Access-Control-Allow-Headers: x-websocket-version]
WS MSG:[Access-Control-Allow-Headers: x-websocket-protocol]
WS MSG:[Access-Control-Allow-Origin: http://www.websocket.org]
WS MSG:[Connection: Upgrade]
WS MSG:[Date: Thu, 29 Oct 2020 15:08:01 GMT]
WS MSG:[Sec-WebSocket-Accept: eXT5yQBZ/TOhFBUi6nLY8cfzs1s=]
WS MSG:[Server: Kaazing Gateway]
WS MSG:[Upgrade: websocket]
WS MSG:[]
You see the Sec-WebSocket-Accept
Header ? The server resolve it as described in RFC 6455. We can calculate it with:
$ echo -n "$(echo -n "somekey" | base64)258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
sha1sum | cut -d " " -f1 | xxd --ps -r | base64
eXT5yQBZ/TOhFBUi6nLY8cfzs1s=
I know... cut
, xxd
, base64
and sha1sum
are not builtin, but this validation step is just for clarify.
The handshake are done and our tcp connection are now upgraded to a websocket connection.
Now the hard part.
We need to send data. We can learn how to do it in section 5 of the RFC6455.
a client MUST mask all frames that it sends to the server (see Section 5.3 for further details)
and
The server MUST close the connection upon receiving a frame that is not masked
Read the RFC Section 5.2 to know how to mask data.
The RFC even provides a ascii-art for data masking:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
I want to write a bash function to mask ws data, but I can not do it now. I'll update this post later.