Merge branch 'master' into add-desktop-size-change-callback
This commit is contained in:
commit
ab9dcee309
19
README.md
19
README.md
|
|
@ -24,6 +24,23 @@ Running in Chrome before and after connecting:
|
|||
See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
|
||||
|
||||
|
||||
### Projects/Companies using noVNC
|
||||
|
||||
* [Sentry Data Systems](http://www.sentryds.com): uses noVNC in the
|
||||
[Datanex Cloud Computing Platform](http://www.sentryds.com/products/datanex/).
|
||||
|
||||
* [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr):
|
||||
Feature [#1935](http://code.osuosl.org/issues/1935).
|
||||
|
||||
* [Archipel](http://archipelproject.org):
|
||||
[Video demo](http://antoinemercadal.fr/archipelblog/wp-content/themes/ArchipelWPTemplate/video_youtube.php?title=VNC%20Demonstration&id=te_bzW574Zo)
|
||||
|
||||
* [openQRM](http://www.openqrm.com/): VNC plugin available
|
||||
by request. Probably included in [version
|
||||
4.8](http://www.openqrm.com/?q=node/15). [Video
|
||||
demo](http://www.openqrm-enterprise.com/news/details/article/remote-vm-console-plugin-available.html).
|
||||
|
||||
|
||||
### Browser Requirements
|
||||
|
||||
* HTML5 Canvas: Except for Internet Explorer, most
|
||||
|
|
@ -120,7 +137,7 @@ There a few reasons why a proxy is required:
|
|||
|
||||
`./utils/wsproxy.py -f 8787 localhost:5901`
|
||||
|
||||
* To run the mini python web server without the launch script:
|
||||
* To run a mini python web server without the launch script:
|
||||
|
||||
`./utils/web.py PORT`
|
||||
|
||||
|
|
|
|||
30
docs/TODO
30
docs/TODO
|
|
@ -1,19 +1,10 @@
|
|||
Short Term:
|
||||
|
||||
- Playback/demo on website.
|
||||
|
||||
- VNC performance and regression playback suite.
|
||||
- Keyboard layout/internationalization support
|
||||
- convert keyCode into proper charCode
|
||||
|
||||
- Test on IE 9 preview 3.
|
||||
|
||||
- Fix cursor URI detection in Arora:
|
||||
- allows data URI, but doesn't actually work
|
||||
|
||||
|
||||
Medium Term:
|
||||
|
||||
- Viewport support
|
||||
|
||||
- Status bar menu/buttons:
|
||||
- Explanatory hover text over buttons
|
||||
|
||||
|
|
@ -29,7 +20,20 @@ Medium Term:
|
|||
- Clipboard button -> popup:
|
||||
- text, clear and send buttons
|
||||
|
||||
- wswrapper:
|
||||
- Flash policy request support.
|
||||
- SSL/TLS support.
|
||||
- Tests suite:
|
||||
- test pselect/poll/ppoll
|
||||
|
||||
Longer Term:
|
||||
Medium Term:
|
||||
|
||||
- VNC performance and regression playback suite.
|
||||
|
||||
- Viewport support
|
||||
|
||||
- Touchscreen testing/support.
|
||||
|
||||
- wswrapper:
|
||||
- epoll_* support
|
||||
|
||||
- Get web-socket-js RFC2817 proxying working again.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ Cursor appearance/style (for Cursor pseudo-encoding):
|
|||
https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property
|
||||
|
||||
|
||||
RDP Protocol specification:
|
||||
http://msdn.microsoft.com/en-us/library/cc240445(v=PROT.10).aspx
|
||||
|
||||
|
||||
Related projects:
|
||||
|
||||
guacamole: http://guacamole.sourceforge.net/
|
||||
|
|
|
|||
37
docs/notes
37
docs/notes
|
|
@ -4,39 +4,12 @@ There is an included flash object (web-socket-js) that is used to
|
|||
emulate websocket support on browsers without websocket support
|
||||
(currently only Chrome has WebSocket support).
|
||||
|
||||
The performance on Chrome is great. It only takes about 150ms on my
|
||||
system to render a complete 800x600 hextile frame. Most updates are
|
||||
partial or use copyrect which is much faster.
|
||||
|
||||
When using the flash websocket emulator, packets event aren't always
|
||||
delivered in order. This may be a bug in the way I'm using the
|
||||
FABridge interface. Or a bug in FABridge itself, or just a browser
|
||||
event delivery issue. Anyways, to get around this problem, when the
|
||||
flash emulator is being used, the client issues the WebSocket GET
|
||||
request with the "seq_num" variable. This switches on in-band sequence
|
||||
numbering of the packets in the proxy, and the client has a queue of
|
||||
out-of-order packets (20 deep currently) that it uses to re-order.
|
||||
Gross!
|
||||
|
||||
The performance on firefox 3.5 is usable. It takes 2.3 seconds to
|
||||
render a 800x600 hextile frame on my system (the initial connect).
|
||||
Once the initial first update is done though, it's quite usable (as
|
||||
above, most updates are partial or copyrect). I haven't tested firefox
|
||||
3.7 yet, it might be a lot faster.
|
||||
|
||||
Opera sucks big time. The flash websocket emulator fails to deliver as
|
||||
many as 40% of packet events when used on Opera. It may or not be
|
||||
Opera's fault, but something goes badly wrong at the FABridge
|
||||
boundary.
|
||||
|
||||
Javascript doesn't have a bytearray type, so what you get out of
|
||||
a WebSocket object is just Javascript strings. I believe that
|
||||
Javascript has UTF-16 unicode strings and anything sent through the
|
||||
WebSocket gets converted to UTF-8 and vice-versa. So, one additional
|
||||
(and necessary) function of the proxy is base64 encoding/decoding what
|
||||
is sent to/from the browser. Another option that I want to explore is
|
||||
UTF-8 encoding in the proxy.
|
||||
|
||||
a WebSocket object is just Javascript strings. Javascript has UTF-16
|
||||
unicode strings and anything sent through the WebSocket gets converted
|
||||
to UTF-8 and vice-versa. So, one additional (and necessary) function
|
||||
of wsproxy is base64 encoding/decoding what is sent to/from the
|
||||
browser.
|
||||
|
||||
Building web-socket-js emulator:
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ function constructor() {
|
|||
if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
|
||||
ctx = conf.ctx;
|
||||
|
||||
Util.Debug("User Agent: " + navigator.userAgent);
|
||||
if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
|
||||
if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
|
||||
if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
|
||||
|
|
@ -216,7 +217,7 @@ function constructor() {
|
|||
}
|
||||
|
||||
/* Translate DOM key down/up event to keysym value */
|
||||
function getKeysym(e) {
|
||||
that.getKeysym = function(e) {
|
||||
var evt, keysym;
|
||||
evt = (e ? e : window.event);
|
||||
|
||||
|
|
@ -362,24 +363,24 @@ function onMouseMove(e) {
|
|||
}
|
||||
|
||||
function onKeyDown(e) {
|
||||
//Util.Debug("keydown: " + getKeysym(e));
|
||||
//Util.Debug("keydown: " + that.getKeysym(e));
|
||||
if (! conf.focused) {
|
||||
return true;
|
||||
}
|
||||
if (c_keyPress) {
|
||||
c_keyPress(getKeysym(e), 1);
|
||||
c_keyPress(that.getKeysym(e), 1);
|
||||
}
|
||||
Util.stopEvent(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function onKeyUp(e) {
|
||||
//Util.Debug("keyup: " + getKeysym(e));
|
||||
//Util.Debug("keyup: " + that.getKeysym(e));
|
||||
if (! conf.focused) {
|
||||
return true;
|
||||
}
|
||||
if (c_keyPress) {
|
||||
c_keyPress(getKeysym(e), 0);
|
||||
c_keyPress(that.getKeysym(e), 0);
|
||||
}
|
||||
Util.stopEvent(e);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -258,7 +258,8 @@ function constructor() {
|
|||
Util.Info("Using native WebSockets");
|
||||
updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
|
||||
} else {
|
||||
Util.Warn("Using web-socket-js flash bridge");
|
||||
Util.Warn("Using web-socket-js bridge. Flash version: " +
|
||||
Util.Flash.version);
|
||||
if ((! Util.Flash) ||
|
||||
(Util.Flash.version < 9)) {
|
||||
updateState('fatal', "WebSockets or Adobe Flash is required");
|
||||
|
|
@ -819,9 +820,7 @@ init_msg = function() {
|
|||
break;
|
||||
|
||||
case 'SecurityResult' :
|
||||
if (rQlen() < 4) {
|
||||
return fail("Invalid VNC auth response");
|
||||
}
|
||||
if (rQwait("VNC auth response ", 4)) { return false; }
|
||||
switch (rQshift32()) {
|
||||
case 0: // OK
|
||||
// Fall through to ClientInitialisation
|
||||
|
|
@ -852,9 +851,7 @@ init_msg = function() {
|
|||
break;
|
||||
|
||||
case 'ServerInitialisation' :
|
||||
if (rQlen() < 24) {
|
||||
return fail("Invalid server initialisation");
|
||||
}
|
||||
if (rQwait("server initialization", 24)) { return false; }
|
||||
|
||||
/* Screen size */
|
||||
fb_width = rQshift16();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<html>
|
||||
<head><title>Input Test</title></head>
|
||||
<body>
|
||||
<br><br>
|
||||
|
||||
Canvas:<br>
|
||||
<canvas id="canvas" width="640" height="20"
|
||||
style="border-style: dotted; border-width: 1px;">
|
||||
Canvas not supported.
|
||||
</canvas>
|
||||
|
||||
<br>
|
||||
Results:<br>
|
||||
<textarea id="messages" style="font-size: 9;" cols=80 rows=25></textarea>
|
||||
</body>
|
||||
|
||||
<!--
|
||||
<script type='text/javascript'
|
||||
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
|
||||
-->
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/webutil.js"></script>
|
||||
<script src="include/base64.js"></script>
|
||||
<script src="include/canvas.js"></script>
|
||||
<script>
|
||||
var msg_cnt = 0;
|
||||
var width = 400, height = 200;
|
||||
var canvas;
|
||||
|
||||
function message(str) {
|
||||
console.log(str);
|
||||
msg_cnt++;
|
||||
cell = $D('messages');
|
||||
cell.innerHTML += msg_cnt + ": " + str + "\n";
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
}
|
||||
|
||||
function keyDown(evt) {
|
||||
var e = (evt ? evt : window.event);
|
||||
msg = "Dn: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " ksym:" + canvas.getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
|
||||
message(msg);
|
||||
}
|
||||
|
||||
function keyUp(evt) {
|
||||
var e = (evt ? evt : window.event);
|
||||
msg = "Up: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " ksym:" + canvas.getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
|
||||
message(msg);
|
||||
}
|
||||
|
||||
function keyPress(evt) {
|
||||
var e = (evt ? evt : window.event);
|
||||
msg = "Pr: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " ksym:" + canvas.getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
|
||||
message(msg);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
var c = $D('canvas');
|
||||
canvas = new Canvas({'target' : c});
|
||||
canvas.resize(width, height, true);
|
||||
//canvas.start(keyPress);
|
||||
Util.addEvent(document, 'keydown', keyDown);
|
||||
Util.addEvent(document, 'keyup', keyUp);
|
||||
Util.addEvent(document, 'keypress', keyPress);
|
||||
message("Canvas initialized");
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
|
@ -6,6 +6,7 @@ all: $(TARGETS)
|
|||
wsproxy: wsproxy.o websocket.o md5.o
|
||||
$(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@
|
||||
|
||||
wswrapper.o: wswrapper.h
|
||||
wswrapper.so: wswrapper.o md5.o
|
||||
$(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -lresolv -o $@
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
## wsproxy: WebSockets to TCP Proxy
|
||||
## WebSockets Utilities: wswrapper and wsproxy
|
||||
|
||||
|
||||
### How it works
|
||||
### wswrapper
|
||||
|
||||
wswrapper is an LD_PRELOAD library that converts a TCP listen socket
|
||||
of an existing program to a be a WebSockets socket. The `wswrap`
|
||||
script can be used to easily launch a program using wswrapper. Here is
|
||||
an example of using wswrapper with vncserver. wswrapper will convert
|
||||
the socket listening on port 5901 to be a WebSockets port:
|
||||
|
||||
`cd noVNC/utils`
|
||||
|
||||
`./wswrap 5901 vncserver -geometry 640x480 :1`
|
||||
|
||||
|
||||
### wsproxy
|
||||
|
||||
At the most basic level, wsproxy just translates WebSockets traffic
|
||||
to normal socket traffic. wsproxy accepts the WebSockets handshake,
|
||||
|
|
@ -15,7 +28,7 @@ case the data from the client is not a full WebSockets frame (i.e.
|
|||
does not end in 255).
|
||||
|
||||
|
||||
### Additional features
|
||||
#### Additional wsproxy features
|
||||
|
||||
These are not necessary for the basic operation.
|
||||
|
||||
|
|
@ -38,44 +51,63 @@ These are not necessary for the basic operation.
|
|||
option.
|
||||
|
||||
|
||||
### Implementations
|
||||
#### Implementations of wsproxy
|
||||
|
||||
There are three implementations of wsproxy included: python, C, and
|
||||
Node (node.js).
|
||||
|
||||
Here is the feature support matrix for the wsproxy implementations:
|
||||
There are three implementations of wsproxy: python, C, and Node
|
||||
(node.js). wswrapper is only implemented in C.
|
||||
|
||||
Here is the feature support matrix for the the wsproxy implementations
|
||||
and wswrapper:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Implementation</th>
|
||||
<th>Basic Proxying</th>
|
||||
<th>Multi-process</th>
|
||||
<th>Daemonizing</th>
|
||||
<th>Program</th>
|
||||
<th>Language</th>
|
||||
<th>Proxy or Interposer</th>
|
||||
<th>Multiprocess</th>
|
||||
<th>Daemonize</th>
|
||||
<th>SSL/wss</th>
|
||||
<th>Flash Policy Server</th>
|
||||
<th>Session Recording</th>
|
||||
<th>Session Record</th>
|
||||
<th>Web Server</th>
|
||||
</tr> <tr>
|
||||
<td>wsproxy.py</td>
|
||||
<td>python</td>
|
||||
<td>yes</td>
|
||||
<td>proxy</td>
|
||||
<td>yes</td>
|
||||
<td>yes</td>
|
||||
<td>yes 1</td>
|
||||
<td>yes</td>
|
||||
<td>yes</td>
|
||||
</tr> <tr>
|
||||
<td>C</td>
|
||||
<td>yes</td>
|
||||
</tr> <tr>
|
||||
<td>wsproxy</td>
|
||||
<td>C</td>
|
||||
<td>proxy</td>
|
||||
<td>yes</td>
|
||||
<td>yes</td>
|
||||
<td>yes</td>
|
||||
<td>yes</td>
|
||||
<td>no</td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
</tr> <tr>
|
||||
<td>wsproxy.js</td>
|
||||
<td>Node (node.js)</td>
|
||||
<td>proxy</td>
|
||||
<td>yes</td>
|
||||
<td>yes</td>
|
||||
<td>no</td>
|
||||
<td>no</td>
|
||||
<td>no</td>
|
||||
<td>no</td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
</tr> <tr>
|
||||
<td>wswrap/wswrapper.so</td>
|
||||
<td>shell/C</td>
|
||||
<td>interposer</td>
|
||||
<td>indirectly</td>
|
||||
<td>indirectly</td>
|
||||
<td>no</td>
|
||||
<td>no</td>
|
||||
<td>no</td>
|
||||
|
|
@ -83,6 +115,7 @@ Here is the feature support matrix for the wsproxy implementations:
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
* Note 1: to use SSL/wss with python 2.5 or older, see the following
|
||||
section on *Building the Python ssl module*.
|
||||
|
||||
|
|
|
|||
|
|
@ -5,26 +5,25 @@ usage() {
|
|||
echo "$*"
|
||||
echo
|
||||
fi
|
||||
echo "Usage: ${NAME} [--web WEB_PORT] [--proxy PROXY_PORT] [--vnc VNC_HOST:PORT]"
|
||||
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
|
||||
echo
|
||||
echo "Starts a mini-webserver and the WebSockets proxy and"
|
||||
echo "provides a cut and paste URL to go to."
|
||||
echo
|
||||
echo " --web WEB_PORT Port to serve web pages at"
|
||||
echo " Default: 8080"
|
||||
echo " --proxy PROXY_PORT Port for proxy to listen on"
|
||||
echo " Default: 8081"
|
||||
echo " --listen PORT Port for webserver/proxy to listen on"
|
||||
echo " Default: 6080"
|
||||
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
|
||||
echo " Default: localhost:5900"
|
||||
echo " --cert CERT Path to combined cert/key file"
|
||||
echo " Default: self.pem"
|
||||
exit 2
|
||||
}
|
||||
|
||||
NAME="$(basename $0)"
|
||||
HERE=$(readlink -f $(dirname $0))
|
||||
WEB_PORT="6080"
|
||||
PROXY_PORT="6081"
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
PORT="6080"
|
||||
VNC_DEST="localhost:5900"
|
||||
web_pid=""
|
||||
CERT=""
|
||||
proxy_pid=""
|
||||
|
||||
die() {
|
||||
|
|
@ -36,10 +35,6 @@ cleanup() {
|
|||
trap - TERM QUIT INT EXIT
|
||||
trap "true" CHLD # Ignore cleanup messages
|
||||
echo
|
||||
if [ -n "${web_pid}" ]; then
|
||||
echo "Terminating webserver (${web_pid})"
|
||||
kill ${web_pid}
|
||||
fi
|
||||
if [ -n "${proxy_pid}" ]; then
|
||||
echo "Terminating WebSockets proxy (${proxy_pid})"
|
||||
kill ${proxy_pid}
|
||||
|
|
@ -52,12 +47,12 @@ cleanup() {
|
|||
while [ "$*" ]; do
|
||||
param=$1; shift; OPTARG=$1
|
||||
case $param in
|
||||
--web) WEB_PORT="${OPTARG}"; shift ;;
|
||||
--proxy) PROXY_PORT="${OPTARG}"; shift ;;
|
||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||
-h|--help) usage ;;
|
||||
--listen) PORT="${OPTARG}"; shift ;;
|
||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||
--cert) CERT="${OPTARG}"; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||
*) break ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
|
@ -65,28 +60,39 @@ done
|
|||
which netstat >/dev/null 2>&1 \
|
||||
|| die "Must have netstat installed"
|
||||
|
||||
netstat -ltn | grep -qs "${WEB_PORT}.*LISTEN" \
|
||||
&& die "Port ${WEB_PORT} in use. Try --web WEB_PORT"
|
||||
|
||||
netstat -ltn | grep -qs "${PROXY_PORT}.*LISTEN" \
|
||||
&& die "Port ${PROXY_PORT} in use. Try --proxy PROXY_PORT"
|
||||
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
|
||||
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||
|
||||
trap "cleanup" TERM QUIT INT EXIT
|
||||
|
||||
echo "Starting webserver on port ${WEB_PORT}"
|
||||
${HERE}/web.py ${WEB_PORT} >/dev/null &
|
||||
web_pid="$!"
|
||||
sleep 1
|
||||
if ps -p ${web_pid} >/dev/null; then
|
||||
echo "Started webserver (pid: ${web_pid})"
|
||||
# Find vnc.html
|
||||
if [ -e "$(pwd)/vnc.html" ]; then
|
||||
WEB=$(pwd)
|
||||
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||
WEB=${HERE}/../
|
||||
elif [ -e "${HERE}/vnc.html" ]; then
|
||||
WEB=${HERE}
|
||||
else
|
||||
web_pid=
|
||||
echo "Failed to start webserver"
|
||||
exit 1
|
||||
die "Could not find vnc.html"
|
||||
fi
|
||||
|
||||
echo "Starting WebSockets proxy on port ${PROXY_PORT}"
|
||||
${HERE}/wsproxy.py -f ${PROXY_PORT} ${VNC_DEST} &
|
||||
# Find self.pem
|
||||
if [ -n "${CERT}" ]; then
|
||||
if [ ! -e "${CERT}" ]; then
|
||||
die "Could not find ${CERT}"
|
||||
fi
|
||||
elif [ -e "$(pwd)/self.pem" ]; then
|
||||
CERT="$(pwd)/self.pem"
|
||||
elif [ -e "${HERE}/../self.pem" ]; then
|
||||
CERT="${HERE}/../self.pem"
|
||||
elif [ -e "${HERE}/self.pem" ]; then
|
||||
CERT="${HERE}/self.pem"
|
||||
else
|
||||
echo "Warning: could not find self.pem"
|
||||
fi
|
||||
|
||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||
${HERE}/wsproxy.py -f --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
proxy_pid="$!"
|
||||
sleep 1
|
||||
if ps -p ${proxy_pid} >/dev/null; then
|
||||
|
|
@ -98,8 +104,7 @@ else
|
|||
fi
|
||||
|
||||
echo -e "\n\nNavigate to to this URL:\n"
|
||||
echo -e " http://$(hostname):${WEB_PORT}/vnc.html?host=$(hostname)&port=${PROXY_PORT}\n"
|
||||
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||
echo -e "Press Ctrl-C to exit\n\n"
|
||||
|
||||
wait ${web_pid}
|
||||
|
||||
wait ${proxy_pid}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ def do_request(connstream, from_addr):
|
|||
|
||||
def serve():
|
||||
bindsocket = socket.socket()
|
||||
bindsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
#bindsocket.bind(('localhost', PORT))
|
||||
bindsocket.bind(('', PORT))
|
||||
bindsocket.listen(5)
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ int ws_socket_free(ws_ctx_t *ctx) {
|
|||
ctx->ssl_ctx = NULL;
|
||||
}
|
||||
if (ctx->sockfd) {
|
||||
shutdown(ctx->sockfd, SHUT_RDWR);
|
||||
close(ctx->sockfd);
|
||||
ctx->sockfd = 0;
|
||||
}
|
||||
|
|
@ -350,26 +351,30 @@ ws_ctx_t *do_handshake(int sock) {
|
|||
handshake[len] = 0;
|
||||
if (len == 0) {
|
||||
handler_msg("ignoring empty handshake\n");
|
||||
close(sock);
|
||||
return NULL;
|
||||
} else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) {
|
||||
len = recv(sock, handshake, 1024, 0);
|
||||
handshake[len] = 0;
|
||||
handler_msg("sending flash policy response\n");
|
||||
send(sock, policy_response, sizeof(policy_response), 0);
|
||||
close(sock);
|
||||
return NULL;
|
||||
} else if ((bcmp(handshake, "\x16", 1) == 0) ||
|
||||
(bcmp(handshake, "\x80", 1) == 0)) {
|
||||
// SSL
|
||||
if (! settings.cert) { return NULL; }
|
||||
if (!settings.cert) {
|
||||
handler_msg("SSL connection but no cert specified\n");
|
||||
return NULL;
|
||||
} else if (access(settings.cert, R_OK) != 0) {
|
||||
handler_msg("SSL connection but '%s' not found\n",
|
||||
settings.cert);
|
||||
return NULL;
|
||||
}
|
||||
ws_ctx = ws_socket_ssl(sock, settings.cert, settings.key);
|
||||
if (! ws_ctx) { return NULL; }
|
||||
scheme = "wss";
|
||||
handler_msg("using SSL socket\n");
|
||||
} else if (settings.ssl_only) {
|
||||
handler_msg("non-SSL connection disallowed\n");
|
||||
close(sock);
|
||||
return NULL;
|
||||
} else {
|
||||
ws_ctx = ws_socket(sock);
|
||||
|
|
@ -380,14 +385,12 @@ ws_ctx_t *do_handshake(int sock) {
|
|||
len = ws_recv(ws_ctx, handshake, 4096);
|
||||
if (len == 0) {
|
||||
handler_emsg("Client closed during handshake\n");
|
||||
close(sock);
|
||||
return NULL;
|
||||
}
|
||||
handshake[len] = 0;
|
||||
|
||||
if (!parse_handshake(handshake, &headers)) {
|
||||
handler_emsg("Invalid WS request\n");
|
||||
close(sock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -524,8 +527,7 @@ void start_server() {
|
|||
if (pid == 0) { // handler process
|
||||
ws_ctx = do_handshake(csock);
|
||||
if (ws_ctx == NULL) {
|
||||
close(csock);
|
||||
handler_msg("No connection after handshake");
|
||||
handler_msg("No connection after handshake\n");
|
||||
break; // Child process exits
|
||||
}
|
||||
|
||||
|
|
@ -533,13 +535,22 @@ void start_server() {
|
|||
if (pipe_error) {
|
||||
handler_emsg("Closing due to SIGPIPE\n");
|
||||
}
|
||||
close(csock);
|
||||
handler_msg("handler exit\n");
|
||||
break; // Child process exits
|
||||
} else { // parent process
|
||||
settings.handler_id += 1;
|
||||
}
|
||||
}
|
||||
if (pid == 0) {
|
||||
if (ws_ctx) {
|
||||
ws_socket_free(ws_ctx);
|
||||
} else {
|
||||
shutdown(csock, SHUT_RDWR);
|
||||
close(csock);
|
||||
}
|
||||
handler_msg("handler exit\n");
|
||||
} else {
|
||||
handler_msg("wsproxy exit\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
|||
|
||||
import sys, socket, ssl, struct, traceback
|
||||
import os, resource, errno, signal # daemonizing
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from cStringIO import StringIO
|
||||
from base64 import b64encode, b64decode
|
||||
try:
|
||||
from hashlib import md5
|
||||
|
|
@ -31,7 +33,8 @@ settings = {
|
|||
'key' : None,
|
||||
'ssl_only' : False,
|
||||
'daemon' : True,
|
||||
'record' : None, }
|
||||
'record' : None,
|
||||
'web' : False, }
|
||||
|
||||
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
||||
Upgrade: WebSocket\r
|
||||
|
|
@ -47,6 +50,29 @@ policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports
|
|||
class EClose(Exception):
|
||||
pass
|
||||
|
||||
# HTTP handler with request from a string and response to a socket
|
||||
class SplitHTTPHandler(SimpleHTTPRequestHandler):
|
||||
def __init__(self, req, resp, addr):
|
||||
# Save the response socket
|
||||
self.response = resp
|
||||
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
|
||||
|
||||
def setup(self):
|
||||
self.connection = self.response
|
||||
# Duck type request string to file object
|
||||
self.rfile = StringIO(self.request)
|
||||
self.wfile = self.connection.makefile('wb', self.wbufsize)
|
||||
|
||||
def send_response(self, code, message=None):
|
||||
# Save the status code
|
||||
self.last_code = code
|
||||
SimpleHTTPRequestHandler.send_response(self, code, message)
|
||||
|
||||
def log_message(self, f, *args):
|
||||
# Save instead of printing
|
||||
self.last_message = f % args
|
||||
|
||||
|
||||
def traffic(token="."):
|
||||
if settings['verbose'] and not settings['daemon']:
|
||||
sys.stdout.write(token)
|
||||
|
|
@ -54,7 +80,10 @@ def traffic(token="."):
|
|||
|
||||
def handler_msg(msg):
|
||||
if not settings['daemon']:
|
||||
print " %d: %s" % (settings['handler_id'], msg)
|
||||
print "% 3d: %s" % (settings['handler_id'], msg)
|
||||
|
||||
def handler_vmsg(msg):
|
||||
if settings['verbose']: handler_msg(msg)
|
||||
|
||||
def encode(buf):
|
||||
buf = b64encode(buf)
|
||||
|
|
@ -96,51 +125,77 @@ def gen_md5(keys):
|
|||
return md5(struct.pack('>II8s', num1, num2, key3)).digest()
|
||||
|
||||
|
||||
def do_handshake(sock):
|
||||
def do_handshake(sock, address):
|
||||
stype = ""
|
||||
|
||||
# Peek, but don't read the data
|
||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||
#handler_msg("Handshake [%s]" % repr(handshake))
|
||||
if handshake == "":
|
||||
handler_msg("ignoring empty handshake")
|
||||
sock.close()
|
||||
return False
|
||||
raise EClose("ignoring empty handshake")
|
||||
elif handshake.startswith("<policy-file-request/>"):
|
||||
handshake = sock.recv(1024)
|
||||
handler_msg("Sending flash policy response")
|
||||
sock.send(policy_response)
|
||||
sock.close()
|
||||
return False
|
||||
raise EClose("Sending flash policy response")
|
||||
elif handshake[0] in ("\x16", "\x80"):
|
||||
retsock = ssl.wrap_socket(
|
||||
sock,
|
||||
server_side=True,
|
||||
certfile=settings['cert'],
|
||||
keyfile=settings['key'])
|
||||
if not os.path.exists(settings['cert']):
|
||||
raise EClose("SSL connection but '%s' not found"
|
||||
% settings['cert'])
|
||||
try:
|
||||
retsock = ssl.wrap_socket(
|
||||
sock,
|
||||
server_side=True,
|
||||
certfile=settings['cert'],
|
||||
keyfile=settings['key'])
|
||||
except ssl.SSLError, x:
|
||||
if x.args[0] == ssl.SSL_ERROR_EOF:
|
||||
raise EClose("")
|
||||
else:
|
||||
raise
|
||||
|
||||
scheme = "wss"
|
||||
handler_msg("using SSL/TLS")
|
||||
stype = "SSL/TLS (wss://)"
|
||||
elif settings['ssl_only']:
|
||||
handler_msg("non-SSL connection disallowed")
|
||||
sock.close()
|
||||
return False
|
||||
raise EClose("non-SSL connection received but disallowed")
|
||||
else:
|
||||
retsock = sock
|
||||
scheme = "ws"
|
||||
handler_msg("using plain (not SSL) socket")
|
||||
stype = "Plain non-SSL (ws://)"
|
||||
|
||||
# Now get the data from the socket
|
||||
handshake = retsock.recv(4096)
|
||||
#handler_msg("handshake: " + repr(handshake))
|
||||
|
||||
if len(handshake) == 0:
|
||||
raise EClose("Client closed during handshake")
|
||||
|
||||
# Handle normal web requests
|
||||
if handshake.startswith('GET ') and \
|
||||
handshake.find('Upgrade: WebSocket\r\n') == -1:
|
||||
if not settings['web']:
|
||||
raise EClose("Normal web request received but disallowed")
|
||||
sh = SplitHTTPHandler(handshake, retsock, address)
|
||||
if sh.last_code < 200 or sh.last_code >= 300:
|
||||
raise EClose(sh.last_message)
|
||||
elif settings['verbose']:
|
||||
raise EClose(sh.last_message)
|
||||
else:
|
||||
raise EClose("")
|
||||
|
||||
# Do WebSockets handshake and return the socket
|
||||
h = parse_handshake(handshake)
|
||||
|
||||
if h.get('key3'):
|
||||
trailer = gen_md5(h)
|
||||
pre = "Sec-"
|
||||
handler_msg("using protocol version 76")
|
||||
ver = 76
|
||||
else:
|
||||
trailer = ""
|
||||
pre = ""
|
||||
handler_msg("using protocol version 75")
|
||||
ver = 75
|
||||
|
||||
handler_msg("%s WebSocket connection (version %s) from %s"
|
||||
% (stype, ver, address[0]))
|
||||
|
||||
response = server_handshake % (pre, h['Origin'], pre, scheme,
|
||||
h['Host'], h['path'], pre, trailer)
|
||||
|
|
@ -172,8 +227,8 @@ def daemonize(keepfd=None):
|
|||
try:
|
||||
if fd != keepfd:
|
||||
os.close(fd)
|
||||
elif settings['verbose']:
|
||||
print "Keeping fd: %d" % fd
|
||||
else:
|
||||
handler_vmsg("Keeping fd: %d" % fd)
|
||||
except OSError, exc:
|
||||
if exc.errno != errno.EBADF: raise
|
||||
|
||||
|
|
@ -204,25 +259,25 @@ def start_server():
|
|||
csock = startsock = None
|
||||
pid = 0
|
||||
startsock, address = lsock.accept()
|
||||
handler_msg('got client connection from %s' % address[0])
|
||||
|
||||
handler_msg("forking handler process")
|
||||
handler_vmsg('%s: forking handler' % address[0])
|
||||
pid = os.fork()
|
||||
|
||||
if pid == 0: # handler process
|
||||
csock = do_handshake(startsock)
|
||||
if not csock:
|
||||
handler_msg("No connection after handshake");
|
||||
break
|
||||
csock = do_handshake(startsock, address)
|
||||
settings['handler'](csock)
|
||||
else: # parent process
|
||||
settings['handler_id'] += 1
|
||||
|
||||
except EClose, exc:
|
||||
handler_msg("handler exit: %s" % exc.args)
|
||||
if csock and csock != startsock:
|
||||
csock.close()
|
||||
startsock.close()
|
||||
if exc.args[0]:
|
||||
handler_msg("%s: %s" % (address[0], exc.args[0]))
|
||||
except Exception, exc:
|
||||
handler_msg("handler exception: %s" % str(exc))
|
||||
#handler_msg(traceback.format_exc())
|
||||
if settings['verbose']:
|
||||
handler_msg(traceback.format_exc())
|
||||
|
||||
if pid == 0:
|
||||
if csock: csock.close()
|
||||
|
|
|
|||
|
|
@ -257,6 +257,10 @@ int main(int argc, char *argv[])
|
|||
};
|
||||
|
||||
settings.cert = realpath("self.pem", NULL);
|
||||
if (!settings.cert) {
|
||||
/* Make sure it's always set to something */
|
||||
settings.cert = "self.pem";
|
||||
}
|
||||
settings.key = "";
|
||||
|
||||
while (1) {
|
||||
|
|
@ -326,9 +330,11 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
if (ssl_only) {
|
||||
if (!settings.cert || !access(settings.cert, R_OK)) {
|
||||
usage("SSL only and cert file not found\n");
|
||||
if (!access(settings.cert, R_OK)) {
|
||||
usage("SSL only and cert file '%s' not found\n", settings.cert);
|
||||
}
|
||||
} else if (access(settings.cert, R_OK) != 0) {
|
||||
fprintf(stderr, "Warning: '%s' not found\n", settings.cert);
|
||||
}
|
||||
|
||||
//printf(" verbose: %d\n", settings.verbose);
|
||||
|
|
|
|||
|
|
@ -143,6 +143,8 @@ if __name__ == '__main__':
|
|||
help="SSL key file (if separate from cert)")
|
||||
parser.add_option("--ssl-only", action="store_true",
|
||||
help="disallow non-encrypted connections")
|
||||
parser.add_option("--web", default=None, metavar="DIR",
|
||||
help="run webserver on same port. Serve files from DIR.")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if len(args) > 2: parser.error("Too many arguments")
|
||||
|
|
@ -162,6 +164,8 @@ if __name__ == '__main__':
|
|||
|
||||
if options.ssl_only and not os.path.exists(options.cert):
|
||||
parser.error("SSL only and %s not found" % options.cert)
|
||||
elif not os.path.exists(options.cert):
|
||||
print "Warning: %s not found" % options.cert
|
||||
|
||||
settings['verbose'] = options.verbose
|
||||
settings['listen_host'] = host
|
||||
|
|
@ -174,4 +178,7 @@ if __name__ == '__main__':
|
|||
settings['daemon'] = options.daemon
|
||||
if options.record:
|
||||
settings['record'] = os.path.abspath(options.record)
|
||||
if options.web:
|
||||
os.chdir = options.web
|
||||
settings['web'] = options.web
|
||||
start_server()
|
||||
|
|
|
|||
|
|
@ -3,10 +3,19 @@
|
|||
* Copyright 2010 Joel Martin
|
||||
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
*
|
||||
* Use wswrap to run a program using the wrapper.
|
||||
* wswrapper.so is meant to be LD preloaded. Use wswrap to run a program using
|
||||
* wswrapper.so.
|
||||
*/
|
||||
|
||||
/* WARNING: multi-threaded programs may not work */
|
||||
/*
|
||||
* Limitations:
|
||||
* - multi-threaded programs may not work
|
||||
* - programs using ppoll or epoll will not work correctly
|
||||
*/
|
||||
|
||||
#define DO_MSG 1
|
||||
//#define DO_DEBUG 1
|
||||
//#define DO_TRACE 1
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -14,63 +23,60 @@
|
|||
#define __USE_GNU 1 // Pull in RTLD_NEXT
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <resolv.h> /* base64 encode/decode */
|
||||
#include <sys/time.h>
|
||||
#include "md5.h"
|
||||
|
||||
//#define DO_DEBUG 1
|
||||
|
||||
#ifdef DO_DEBUG
|
||||
#define DEBUG(...) \
|
||||
if (DO_DEBUG) { \
|
||||
fprintf(stderr, "wswrapper: "); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
}
|
||||
#else
|
||||
#define DEBUG(...)
|
||||
#endif
|
||||
|
||||
#define MSG(...) \
|
||||
fprintf(stderr, "wswrapper: "); \
|
||||
fprintf(stderr, __VA_ARGS__);
|
||||
|
||||
#define RET_ERROR(eno, ...) \
|
||||
fprintf(stderr, "wswrapper error: "); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
errno = eno; \
|
||||
return -1;
|
||||
|
||||
|
||||
const char _WS_response[] = "\
|
||||
HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
|
||||
Upgrade: WebSocket\r\n\
|
||||
Connection: Upgrade\r\n\
|
||||
%sWebSocket-Origin: %s\r\n\
|
||||
%sWebSocket-Location: %s://%s%s\r\n\
|
||||
%sWebSocket-Protocol: sample\r\n\
|
||||
\r\n%s";
|
||||
|
||||
#define WS_BUFSIZE 65536
|
||||
|
||||
/* Buffers and state for each wrapped WebSocket connection */
|
||||
typedef struct {
|
||||
char rbuf[WS_BUFSIZE];
|
||||
char sbuf[WS_BUFSIZE];
|
||||
int rcarry_cnt;
|
||||
char rcarry[3];
|
||||
int newframe;
|
||||
} _WS_connection;
|
||||
|
||||
#include "wswrapper.h"
|
||||
|
||||
/*
|
||||
* If WSWRAP_PORT environment variable is set then listen to the bind fd that
|
||||
* matches WSWRAP_PORT, otherwise listen to the first socket fd that bind is
|
||||
* called on.
|
||||
* matches WSWRAP_PORT
|
||||
*/
|
||||
int _WS_listen_fd = -1;
|
||||
_WS_connection *_WS_connections[65546];
|
||||
int _WS_nfds = 0;
|
||||
int _WS_fds[WS_MAX_FDS];
|
||||
_WS_connection *_WS_connections[65536];
|
||||
|
||||
|
||||
/*
|
||||
* Utillity routines
|
||||
*/
|
||||
|
||||
/*
|
||||
* Subtract the `struct timeval' values X and Y, storing the
|
||||
* result in RESULT. If TS is set then RESULT and X are really
|
||||
* type-cast `struct timespec` so scale them appropriately.
|
||||
* Return 1 if the difference is negative or 0, otherwise 0.
|
||||
*/
|
||||
int _WS_subtract_time (result, x, y, ts)
|
||||
struct timeval *result, *x, *y;
|
||||
{
|
||||
int scale = ts ? 1000 : 1;
|
||||
/* Perform the carry for the later subtraction by updating y. */
|
||||
if ((x->tv_usec / scale) < y->tv_usec) {
|
||||
int sec = (y->tv_usec - (x->tv_usec / scale)) / 1000000 + 1;
|
||||
y->tv_usec -= 1000000 * sec;
|
||||
y->tv_sec += sec;
|
||||
}
|
||||
if ((x->tv_usec / scale) - y->tv_usec > 1000000) {
|
||||
int sec = ((x->tv_usec / scale) - y->tv_usec) / 1000000;
|
||||
y->tv_usec += 1000000 * sec;
|
||||
y->tv_sec -= sec;
|
||||
}
|
||||
|
||||
/* Compute the time remaining to wait.
|
||||
* tv_usec is certainly positive. */
|
||||
result->tv_sec = x->tv_sec - y->tv_sec;
|
||||
result->tv_usec = x->tv_usec - (y->tv_usec * scale);
|
||||
|
||||
/* Return 1 if result is negative or 0. */
|
||||
return x->tv_sec <= y->tv_sec;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
|
@ -243,6 +249,71 @@ int _WS_handshake(int sockfd)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Strip empty WebSockets frames and return a positive value if there is
|
||||
* enough data to base64 decode (a 4 byte chunk). If nonblock is not set then
|
||||
* it will block until there is enough data (or until an error occurs).
|
||||
*/
|
||||
ssize_t _WS_ready(int sockfd, int nonblock)
|
||||
{
|
||||
_WS_connection *ws = _WS_connections[sockfd];
|
||||
char buf[6];
|
||||
int count, len, flags, i;
|
||||
static void * (*rfunc)();
|
||||
if (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv");
|
||||
|
||||
TRACE(">> _WS_ready(%d, %d)\n", sockfd, nonblock);
|
||||
|
||||
count = 4 + ws->newframe;
|
||||
flags = MSG_PEEK;
|
||||
if (nonblock) {
|
||||
flags |= MSG_DONTWAIT;
|
||||
}
|
||||
while (1) {
|
||||
len = (int) rfunc(sockfd, buf, count, flags);
|
||||
if (len < 1) {
|
||||
TRACE("<< _WS_ready(%d, %d) len < 1, errno: %d\n",
|
||||
sockfd, nonblock, errno);
|
||||
return len;
|
||||
}
|
||||
if (len >= 2 && buf[0] == '\x00' && buf[1] == '\xff') {
|
||||
/* Strip emtpy frame */
|
||||
DEBUG("_WS_ready(%d, %d), strip empty\n", sockfd, nonblock);
|
||||
len = (int) rfunc(sockfd, buf, 2, 0);
|
||||
if (len < 2) {
|
||||
MSG("Failed to strip empty frame headers\n");
|
||||
TRACE("<< _WS_ready: failed to strip empty frame headers\n");
|
||||
return len;
|
||||
} else if (len == 2 && nonblock) {
|
||||
errno = EAGAIN;
|
||||
TRACE("<< _WS_ready(%d, %d), len == 2, EAGAIN\n",
|
||||
sockfd, nonblock);
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (len < count) {
|
||||
if (nonblock) {
|
||||
errno = EAGAIN;
|
||||
TRACE("<< _WS_ready(%d, %d), len < count, EAGAIN\n",
|
||||
sockfd, nonblock);
|
||||
return -1;
|
||||
} else {
|
||||
fprintf(stderr, "_WS_ready(%d, %d), loop: len %d, buf:",
|
||||
sockfd, nonblock, len, (unsigned char) buf[0]);
|
||||
for (i = 0; i < len; i++) {
|
||||
fprintf(stderr, "%d", (unsigned char) buf[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
TRACE("<< _WS_ready(%d, %d) len: %d\n", sockfd, nonblock, len);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* WebSockets recv/read interposer routine
|
||||
*/
|
||||
|
|
@ -250,7 +321,8 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
|||
size_t len, int flags)
|
||||
{
|
||||
_WS_connection *ws = _WS_connections[sockfd];
|
||||
int rawcount, deccount, left, rawlen, retlen, decodelen;
|
||||
int rawcount, deccount, left, striplen, decodelen, ready;
|
||||
ssize_t retlen, rawlen;
|
||||
int sockflags;
|
||||
int i;
|
||||
char *fstart, *fend, *cstart;
|
||||
|
|
@ -259,10 +331,6 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
|||
if (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv");
|
||||
if (!rfunc2) rfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "read");
|
||||
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (! ws) {
|
||||
// Not our file descriptor, just pass through
|
||||
if (recvf) {
|
||||
|
|
@ -271,9 +339,20 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
|||
return (ssize_t) rfunc2(sockfd, buf, len);
|
||||
}
|
||||
}
|
||||
DEBUG("_WS_recv(%d, _, %d) called\n", sockfd, len);
|
||||
TRACE(">> _WS_recv(%d)\n", sockfd);
|
||||
|
||||
if (len == 0) {
|
||||
TRACE("<< _WS_recv(%d) len == 0\n", sockfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sockflags = fcntl(sockfd, F_GETFL, 0);
|
||||
if (sockflags & O_NONBLOCK) {
|
||||
TRACE("_WS_recv(%d, _, %d) with O_NONBLOCK\n", sockfd, len);
|
||||
} else {
|
||||
TRACE("_WS_recv(%d, _, %d) without O_NONBLOCK\n", sockfd, len);
|
||||
}
|
||||
|
||||
left = len;
|
||||
retlen = 0;
|
||||
|
||||
|
|
@ -309,54 +388,29 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
|||
RET_ERROR(ENOMEM, "recv of %d bytes is larger than buffer\n", rawcount);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (1) {
|
||||
/* Peek at everything available */
|
||||
rawlen = (int) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1,
|
||||
flags | MSG_PEEK);
|
||||
if (rawlen <= 0) {
|
||||
DEBUG("_WS_recv: returning because rawlen %d\n", rawlen);
|
||||
return (ssize_t) rawlen;
|
||||
}
|
||||
fstart = ws->rbuf;
|
||||
|
||||
/* Strip empty frames */
|
||||
if (rawlen >= 2 && fstart[0] == '\x00' && fstart[1] == '\xff') {
|
||||
rawlen = (int) rfunc(sockfd, ws->rbuf, 2, flags);
|
||||
if (rawlen != 2) {
|
||||
RET_ERROR(EIO, "Could not strip empty frame headers\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
fstart[rawlen] = '\x00';
|
||||
|
||||
if (rawlen - ws->newframe >= 4) {
|
||||
/* We have enough to base64 decode at least 1 byte */
|
||||
break;
|
||||
}
|
||||
/* Not enough to base64 decode */
|
||||
if (sockflags & O_NONBLOCK) {
|
||||
/* Just tell the caller to call again */
|
||||
DEBUG("_WS_recv: returning because O_NONBLOCK, rawlen %d\n", rawlen);
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
/* Repeat until at least 1 byte (4 raw bytes) to decode */
|
||||
i++;
|
||||
if (i > 1000000) {
|
||||
MSG("Could not send final part of frame\n");
|
||||
ready = _WS_ready(sockfd, 0);
|
||||
if (ready < 1) {
|
||||
if (retlen) {
|
||||
/* We had some carry over, don't error until next call */
|
||||
errno = 0;
|
||||
} else {
|
||||
retlen = ready;
|
||||
}
|
||||
TRACE("<< _WS_recv(%d, _, %d) retlen %d\n", sockfd, len, retlen);
|
||||
return retlen;
|
||||
}
|
||||
|
||||
/*
|
||||
DEBUG("_WS_recv, left: %d, len: %d, rawlen: %d, newframe: %d, raw: ",
|
||||
left, len, rawlen, _WS_newframe);
|
||||
for (i = 0; i < rawlen; i++) {
|
||||
DEBUG("%u,", (unsigned char) ((char *) fstart)[i]);
|
||||
/* We have enough data to return something */
|
||||
|
||||
/* Peek at everything available */
|
||||
rawlen = (ssize_t) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1,
|
||||
flags | MSG_PEEK);
|
||||
if (rawlen <= 0) {
|
||||
RET_ERROR(EPROTO, "Socket was ready but then had failure");
|
||||
}
|
||||
DEBUG("\n");
|
||||
*/
|
||||
fstart = ws->rbuf;
|
||||
fstart[rawlen] = '\x00';
|
||||
|
||||
|
||||
if (ws->newframe) {
|
||||
if (fstart[0] != '\x00') {
|
||||
|
|
@ -389,9 +443,9 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
|||
deccount = fend - fstart;
|
||||
}
|
||||
|
||||
/* Now consume what was processed */
|
||||
/* Now consume what was processed (if not MSG_PEEK) */
|
||||
if (flags & MSG_PEEK) {
|
||||
MSG("*** Got MSG_PEEK ***\n");
|
||||
DEBUG("_WS_recv(%d, _, %d) MSG_PEEK, not consuming\n", sockfd, len);
|
||||
} else {
|
||||
rfunc(sockfd, ws->rbuf, fstart - ws->rbuf + deccount + ws->newframe, flags);
|
||||
}
|
||||
|
|
@ -423,19 +477,13 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
|||
DEBUG("Saving carry bytes: %u,%u\n", ws->rcarry[0],
|
||||
ws->rcarry[1]);
|
||||
} else {
|
||||
MSG("Waah2!\n");
|
||||
RET_ERRO(EPROTO, "Too many carry bytes!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
((char *) buf)[retlen] = '\x00';
|
||||
|
||||
/*
|
||||
DEBUG("*** recv %s as ", fstart);
|
||||
for (i = 0; i < retlen; i++) {
|
||||
DEBUG("%u,", (unsigned char) ((char *) buf)[i]);
|
||||
}
|
||||
DEBUG(" (%d -> %d): %d\n", deccount, decodelen, retlen);
|
||||
*/
|
||||
TRACE("<< _WS_recv(%d) retlen %d\n", sockfd, retlen);
|
||||
return retlen;
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +499,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
|
|||
char * target;
|
||||
int i;
|
||||
static void * (*sfunc)(), * (*sfunc2)();
|
||||
if (!sfunc) sfunc = (void *(*)()) dlsym(RTLD_NEXT, "send");
|
||||
if (!sfunc) sfunc = (void *(*)()) dlsym(RTLD_NEXT, "send");
|
||||
if (!sfunc2) sfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "write");
|
||||
|
||||
if (! ws) {
|
||||
|
|
@ -462,7 +510,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
|
|||
return (ssize_t) sfunc2(sockfd, buf, len);
|
||||
}
|
||||
}
|
||||
DEBUG("_WS_send(%d, _, %d) called\n", sockfd, len);
|
||||
TRACE(">> _WS_send(%d, _, %d)\n", sockfd, len);
|
||||
|
||||
sockflags = fcntl(sockfd, F_GETFL, 0);
|
||||
|
||||
|
|
@ -484,7 +532,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
|
|||
rlen = (int) sfunc(sockfd, ws->sbuf, rawlen, flags);
|
||||
|
||||
if (rlen <= 0) {
|
||||
/* Couldn't send, just return */
|
||||
TRACE("<< _WS_send(%d, _, %d) send failed, returning\n", sockfd, len);
|
||||
return rlen;
|
||||
} else if (rlen < rawlen) {
|
||||
/* Spin until we can send a whole base64 chunck and frame end */
|
||||
|
|
@ -503,13 +551,14 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
|
|||
} else if (clen == 0) {
|
||||
MSG("_WS_send: got clen %d\n", clen);
|
||||
} else if (!(sockflags & O_NONBLOCK)) {
|
||||
MSG("_WS_send: clen %d\n", clen);
|
||||
MSG("<< _WS_send: clen %d\n", clen);
|
||||
return clen;
|
||||
}
|
||||
if (i > 1000000) {
|
||||
MSG("Could not send final part of frame\n");
|
||||
RET_ERROR(EIO, "Could not send final part of frame\n");
|
||||
}
|
||||
} while (left > 0);
|
||||
//DEBUG("_WS_send: spins until finished %d\n", i);
|
||||
DEBUG("_WS_send: spins until finished %d\n", i);
|
||||
}
|
||||
|
||||
|
|
@ -529,32 +578,262 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
|
|||
/* Scale return value for base64 encoding size */
|
||||
retlen = (retlen*3)/4;
|
||||
|
||||
/*
|
||||
DEBUG("*** send ");
|
||||
for (i = 0; i < retlen; i++) {
|
||||
DEBUG("%u,", (unsigned char) ((char *)buf)[i]);
|
||||
}
|
||||
DEBUG(" as '%s' (%d)\n", ws->sbuf+1, rlen);
|
||||
*/
|
||||
TRACE(">> _WS_send(%d, _, %d) retlen %d\n", sockfd, len, retlen);
|
||||
return (ssize_t) retlen;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interpose select/pselect/poll.
|
||||
*
|
||||
* WebSocket descriptors are not ready until we have received a frame start
|
||||
* ('\x00') and at least 4 bytes of base64 encoded data. In addition we may
|
||||
* have carry-over data from the last 4 bytes of base64 data in which case the
|
||||
* WebSockets socket is ready even though there might not be data in the raw
|
||||
* socket itself.
|
||||
*/
|
||||
|
||||
/* Interpose on select (mode==0) and pselect (mode==1) */
|
||||
int _WS_select(int mode, int nfds, fd_set *readfds,
|
||||
fd_set *writefds, fd_set *exceptfds,
|
||||
void *timeptr, const sigset_t *sigmask)
|
||||
{
|
||||
_WS_connection *ws;
|
||||
fd_set carryfds, savefds;
|
||||
/* Assumes timeptr is two longs whether timeval or timespec */
|
||||
struct timeval savetv, starttv, nowtv, difftv;
|
||||
int carrycnt = 0, less = 0;
|
||||
int ret, i, ready, fd;
|
||||
static void * (*func0)(), * (*func1)();
|
||||
if (!func0) func0 = (void *(*)()) dlsym(RTLD_NEXT, "select");
|
||||
if (!func1) func1 = (void *(*)()) dlsym(RTLD_NEXT, "pselect");
|
||||
|
||||
if ((_WS_listen_fd == -1) || (_WS_nfds == 0)) {
|
||||
if (mode == 0) {
|
||||
ret = (int) func0(nfds, readfds, writefds, exceptfds,
|
||||
timeptr);
|
||||
} else if (mode == 1) {
|
||||
ret = (int) func1(nfds, readfds, writefds, exceptfds,
|
||||
timeptr, sigmask);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TRACE(">> _WS_select(%d, %d, _, _, _, _)\n", mode, nfds);
|
||||
memcpy(&savetv, timeptr, sizeof(savetv));
|
||||
gettimeofday(&starttv, NULL);
|
||||
|
||||
/* If we have carry-over return it right away */
|
||||
FD_ZERO(&carryfds);
|
||||
if (readfds) {
|
||||
memcpy(&savefds, readfds, sizeof(savefds));
|
||||
for (i = 0; i < _WS_nfds; i++) {
|
||||
fd = _WS_fds[i];
|
||||
ws = _WS_connections[fd];
|
||||
if ((ws->rcarry_cnt) && (FD_ISSET(fd, readfds))) {
|
||||
FD_SET(fd, &carryfds);
|
||||
carrycnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (carrycnt) {
|
||||
if (writefds) {
|
||||
FD_ZERO(writefds);
|
||||
}
|
||||
if (exceptfds) {
|
||||
FD_ZERO(writefds);
|
||||
}
|
||||
memcpy(readfds, &carryfds, sizeof(carryfds));
|
||||
TRACE("<< _WS_select(%d, %d, _, _, _, _) carrycnt %d\n",
|
||||
mode, nfds, carrycnt);
|
||||
return carrycnt;
|
||||
}
|
||||
|
||||
do {
|
||||
TRACE(" _WS_select(%d, %d, _, _, _, _) tv/ts: %ld:%ld\n", mode, nfds,
|
||||
((struct timeval *) timeptr)->tv_sec,
|
||||
((struct timeval *) timeptr)->tv_usec);
|
||||
if (mode == 0) {
|
||||
ret = (int) func0(nfds, readfds, writefds, exceptfds,
|
||||
timeptr);
|
||||
} else if (mode == 1) {
|
||||
ret = (int) func1(nfds, readfds, writefds, exceptfds,
|
||||
timeptr, sigmask);
|
||||
}
|
||||
if (! readfds) {
|
||||
break;
|
||||
}
|
||||
if (ret <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < _WS_nfds; i++) {
|
||||
fd = _WS_fds[i];
|
||||
ws = _WS_connections[fd];
|
||||
if (FD_ISSET(fd, readfds)) {
|
||||
ready = _WS_ready(fd, 1);
|
||||
if (ready == 0) {
|
||||
/* 0 means EOF which is also a ready condition */
|
||||
DEBUG("_WS_select: detected %d is closed\n", fd);
|
||||
} else if (ready < 0) {
|
||||
DEBUG("_WS_select: FD_CLR(%d,readfds) - not enough to decode\n", fd);
|
||||
FD_CLR(fd, readfds);
|
||||
ret--;
|
||||
}
|
||||
}
|
||||
}
|
||||
errno = 0; /* errno could be set by _WS_ready */
|
||||
|
||||
if (ret == 0) {
|
||||
/*
|
||||
* If all the ready readfds were WebSockets, but none of
|
||||
* them were really ready (empty frames) then we select again. But
|
||||
* first restore original values less passage of time.
|
||||
*/
|
||||
memcpy(readfds, &savefds, sizeof(savefds));
|
||||
gettimeofday(&nowtv, NULL);
|
||||
/* Amount of time that has passed */
|
||||
_WS_subtract_time(&difftv, &nowtv, &starttv, 0);
|
||||
/* Subtract from original timout */
|
||||
less = _WS_subtract_time((struct timeval *) timeptr,
|
||||
&savetv, &difftv, mode);
|
||||
if (less) {
|
||||
/* Timer has expired */
|
||||
TRACE(" _WS_select expired timer\n", mode, nfds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (ret == 0);
|
||||
|
||||
/* Restore original time value for pselect glibc does */
|
||||
if (mode == 1) {
|
||||
memcpy(timeptr, &savetv, sizeof(savetv));
|
||||
}
|
||||
|
||||
TRACE("<< _WS_select(%d, %d, _, _, _, _) ret %d, errno %d\n",
|
||||
mode, nfds, ret, errno);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Interpose on poll (mode==0) and ppoll (mode==1) */
|
||||
int _WS_poll(int mode, struct pollfd *fds, nfds_t nfds, int timeout,
|
||||
struct timespec *ptimeout, sigset_t *sigmask)
|
||||
{
|
||||
_WS_connection *ws;
|
||||
int savetimeout;
|
||||
struct timespec savets;
|
||||
struct timeval starttv, nowtv, difftv;
|
||||
struct pollfd *pfd;
|
||||
int carrycnt = 0, less = 0;
|
||||
int ret, i, ready, fd;
|
||||
static void * (*func0)(), * (*func1)();
|
||||
if (!func0) func0 = (void *(*)()) dlsym(RTLD_NEXT, "poll");
|
||||
if (!func1) func1 = (void *(*)()) dlsym(RTLD_NEXT, "ppoll");
|
||||
|
||||
if ((_WS_listen_fd == -1) || (_WS_nfds == 0)) {
|
||||
if (mode == 0) {
|
||||
ret = (int) func0(fds, nfds, timeout);
|
||||
} else if (mode == 1) {
|
||||
ret = (int) func1(fds, nfds, ptimeout, sigmask);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TRACE(">> _WS_poll(%d, %ld, _, _, _, _)\n", mode, nfds);
|
||||
if (mode == 0) {
|
||||
savetimeout = timeout;
|
||||
} else if (mode == 1) {
|
||||
memcpy(&savets, ptimeout, sizeof(savets));
|
||||
}
|
||||
gettimeofday(&starttv, NULL);
|
||||
|
||||
do {
|
||||
TRACE(" _WS_poll(%d, %ld, _, _, _, _) tv/ts: %ld:%ld\n", mode, nfds,
|
||||
ptimeout->tv_sec, ptimeout->tv_nsec);
|
||||
|
||||
if (mode == 0) {
|
||||
ret = (int) func0(fds, nfds, timeout);
|
||||
} else if (mode == 1) {
|
||||
ret = (int) func1(fds, nfds, ptimeout, sigmask);
|
||||
}
|
||||
if (ret <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < nfds; i++) {
|
||||
pfd = &fds[i];
|
||||
if (! (pfd->events & POLLIN)) {
|
||||
continue;
|
||||
}
|
||||
ws = _WS_connections[pfd->fd];
|
||||
if (! ws) {
|
||||
continue;
|
||||
}
|
||||
if (ws->rcarry_cnt) {
|
||||
if (! (pfd->revents & POLLIN)) {
|
||||
pfd->revents |= POLLIN;
|
||||
ret++;
|
||||
}
|
||||
} else if (pfd->revents & POLLIN) {
|
||||
ready = _WS_ready(pfd->fd, 1);
|
||||
if (ready == 0) {
|
||||
/* 0 means EOF which is also a ready condition */
|
||||
DEBUG("_WS_poll: detected %d is closed\n", fd);
|
||||
} else if (ready < 0) {
|
||||
DEBUG("_WS_poll: not enough to decode\n", fd);
|
||||
pfd->revents -= POLLIN;
|
||||
ret--;
|
||||
}
|
||||
}
|
||||
}
|
||||
errno = 0; /* errno could be set by _WS_ready */
|
||||
|
||||
if (ret == 0) {
|
||||
/*
|
||||
* If all the ready readfds were WebSockets, but none of
|
||||
* them were really ready (empty frames) then we select again. But
|
||||
* first restore original values less passage of time.
|
||||
*/
|
||||
gettimeofday(&nowtv, NULL);
|
||||
/* Amount of time that has passed */
|
||||
_WS_subtract_time(&difftv, &nowtv, &starttv, 0);
|
||||
if (mode == 0) {
|
||||
if (timeout < 0) {
|
||||
/* Negative timeout means infinite */
|
||||
continue;
|
||||
}
|
||||
timeout -= difftv.tv_sec * 1000 + difftv.tv_usec / 1000;
|
||||
if (timeout <= 0) {
|
||||
less = 1;
|
||||
}
|
||||
} else if (mode == 1) {
|
||||
/* Subtract from original timout */
|
||||
less = _WS_subtract_time((struct timeval *) ptimeout,
|
||||
(struct timeval *) &savets,
|
||||
&difftv, 1);
|
||||
}
|
||||
if (less) {
|
||||
/* Timer has expired */
|
||||
TRACE(" _WS_poll expired timer\n", mode, nfds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (ret == 0);
|
||||
|
||||
/* Restore original time value for pselect glibc does */
|
||||
if (mode == 1) {
|
||||
memcpy(ptimeout, &savets, sizeof(savets));
|
||||
}
|
||||
|
||||
TRACE("<< _WS_poll(%d, %ld, _, _, _, _) ret %d, errno %d\n",
|
||||
mode, nfds, ret, errno);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Overload (LD_PRELOAD) standard library network routines
|
||||
*/
|
||||
|
||||
/*
|
||||
int socket(int domain, int type, int protocol)
|
||||
{
|
||||
static void * (*func)();
|
||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "socket");
|
||||
DEBUG("socket(_, %d, _) called\n", type);
|
||||
|
||||
return (int) func(domain, type, protocol);
|
||||
}
|
||||
*/
|
||||
|
||||
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
{
|
||||
static void * (*func)();
|
||||
|
|
@ -562,37 +841,37 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
|||
char * WSWRAP_PORT, * end;
|
||||
int ret, envport, bindport = htons(addr_in->sin_port);
|
||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
|
||||
DEBUG("bind(%d, _, %d) called\n", sockfd, addrlen);
|
||||
TRACE(">> bind(%d, _, %d)\n", sockfd, addrlen);
|
||||
|
||||
ret = (int) func(sockfd, addr, addrlen);
|
||||
|
||||
if (addr_in->sin_family != AF_INET) {
|
||||
// TODO: handle IPv6
|
||||
DEBUG("bind, ignoring non-IPv4 socket\n");
|
||||
TRACE("<< bind, ignoring non-IPv4 socket\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
WSWRAP_PORT = getenv("WSWRAP_PORT");
|
||||
if ((! WSWRAP_PORT) || (*WSWRAP_PORT == '\0')) {
|
||||
// TODO: interpose on all sockets
|
||||
DEBUG("bind, not interposing: WSWRAP_PORT is not set\n");
|
||||
// TODO: interpose on all sockets when WSWRAP_PORT not set
|
||||
TRACE("<< bind, not interposing: WSWRAP_PORT is not set\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
envport = strtol(WSWRAP_PORT, &end, 10);
|
||||
if ((envport == 0) || (*end != '\0')) {
|
||||
MSG("bind, not interposing: WSWRAP_PORT is not a number\n");
|
||||
TRACE("<< bind, not interposing: WSWRAP_PORT is not a number\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (envport != bindport) {
|
||||
DEBUG("bind, not interposing on port: %d (fd %d)\n", bindport, sockfd);
|
||||
TRACE("<< bind, not interposing on port: %d (fd %d)\n", bindport, sockfd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
MSG("bind, interposing on port: %d (fd %d)\n", envport, sockfd);
|
||||
_WS_listen_fd = sockfd;
|
||||
|
||||
TRACE("<< bind, interposing on port: %d (fd %d)\n", envport, sockfd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -601,25 +880,28 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
|
|||
int fd, ret, envfd;
|
||||
static void * (*func)();
|
||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "accept");
|
||||
DEBUG("accept(%d, _, _) called\n", sockfd);
|
||||
TRACE("<< accept(%d, _, _)\n", sockfd);
|
||||
|
||||
fd = (int) func(sockfd, addr, addrlen);
|
||||
|
||||
if (_WS_listen_fd == -1) {
|
||||
DEBUG("not interposing\n");
|
||||
TRACE("<< accept: not interposing\n");
|
||||
return fd;
|
||||
}
|
||||
|
||||
if (_WS_listen_fd != sockfd) {
|
||||
DEBUG("not interposing on fd %d\n", sockfd);
|
||||
TRACE("<< accept: not interposing on fd %d\n", sockfd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
if (_WS_connections[fd]) {
|
||||
MSG("error, already interposing on fd %d\n", fd);
|
||||
RET_ERROR(EINVAL, "already interposing on fd %d\n", fd);
|
||||
} else {
|
||||
/* It's a port we're interposing on so allocate memory for it */
|
||||
if (_WS_nfds >= WS_MAX_FDS) {
|
||||
RET_ERROR(ENOMEM, "Too many interposer fds\n");
|
||||
}
|
||||
if (! (_WS_connections[fd] = malloc(sizeof(_WS_connection)))) {
|
||||
RET_ERROR(ENOMEM, "Could not allocate interposer memory\n");
|
||||
}
|
||||
|
|
@ -627,11 +909,16 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
|
|||
_WS_connections[fd]->rcarry[0] = '\0';
|
||||
_WS_connections[fd]->newframe = 1;
|
||||
|
||||
/* Add to search list for select/pselect */
|
||||
_WS_fds[_WS_nfds] = fd;
|
||||
_WS_nfds++;
|
||||
|
||||
ret = _WS_handshake(fd);
|
||||
if (ret < 0) {
|
||||
free(_WS_connections[fd]);
|
||||
_WS_connections[fd] = NULL;
|
||||
errno = EPROTO;
|
||||
TRACE("<< accept(%d, _, _): ret %d\n", sockfd, ret);
|
||||
return ret;
|
||||
}
|
||||
MSG("interposing on fd %d (allocated memory)\n", fd);
|
||||
|
|
@ -642,13 +929,28 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
|
|||
|
||||
int close(int fd)
|
||||
{
|
||||
int i;
|
||||
static void * (*func)();
|
||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "close");
|
||||
|
||||
if (_WS_connections[fd]) {
|
||||
TRACE(">> close(%d)\n", fd);
|
||||
free(_WS_connections[fd]);
|
||||
_WS_connections[fd] = NULL;
|
||||
|
||||
/* Remove from the search list for select/pselect */
|
||||
for (i = 0; i < _WS_nfds; i++) {
|
||||
if (_WS_fds[i] == fd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_WS_nfds - i - 1 > 0) {
|
||||
memmove(_WS_fds + i, _WS_fds + i + 1, _WS_nfds - i - 1);
|
||||
}
|
||||
_WS_nfds--;
|
||||
|
||||
MSG("finished interposing on fd %d (freed memory)\n", fd);
|
||||
TRACE("<< close(%d)\n", fd);
|
||||
}
|
||||
return (int) func(fd);
|
||||
}
|
||||
|
|
@ -656,25 +958,54 @@ int close(int fd)
|
|||
|
||||
ssize_t read(int fd, void *buf, size_t count)
|
||||
{
|
||||
//DEBUG("read(%d, _, %d) called\n", fd, count);
|
||||
//TRACE("read(%d, _, %d) called\n", fd, count);
|
||||
return (ssize_t) _WS_recv(0, fd, buf, count, 0);
|
||||
}
|
||||
|
||||
ssize_t write(int fd, const void *buf, size_t count)
|
||||
{
|
||||
//DEBUG("write(%d, _, %d) called\n", fd, count);
|
||||
//TRACE("write(%d, _, %d) called\n", fd, count);
|
||||
return (ssize_t) _WS_send(0, fd, buf, count, 0);
|
||||
}
|
||||
|
||||
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
|
||||
{
|
||||
//DEBUG("recv(%d, _, %d, %d) called\n", sockfd, len, flags);
|
||||
//TRACE("recv(%d, _, %d, %d) called\n", sockfd, len, flags);
|
||||
return (ssize_t) _WS_recv(1, sockfd, buf, len, flags);
|
||||
}
|
||||
|
||||
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
|
||||
{
|
||||
//DEBUG("send(%d, _, %d, %d) called\n", sockfd, len, flags);
|
||||
//TRACE("send(%d, _, %d, %d) called\n", sockfd, len, flags);
|
||||
return (ssize_t) _WS_send(1, sockfd, buf, len, flags);
|
||||
}
|
||||
|
||||
int select(int nfds, fd_set *readfds, fd_set *writefds,
|
||||
fd_set *exceptfds, struct timeval *timeout)
|
||||
{
|
||||
//TRACE("select(%d, _, _, _, _) called\n", nfds);
|
||||
return _WS_select(0, nfds, readfds, writefds, exceptfds,
|
||||
(void *) timeout, NULL);
|
||||
}
|
||||
|
||||
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
|
||||
fd_set *exceptfds, const struct timespec *timeout,
|
||||
const sigset_t *sigmask)
|
||||
{
|
||||
TRACE("pselect(%d, _, _, _, _, _) called\n", nfds);
|
||||
return _WS_select(1, nfds, readfds, writefds, exceptfds,
|
||||
(void *) timeout, sigmask);
|
||||
}
|
||||
|
||||
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
TRACE("poll(_, %ld, %d) called\n", nfds, timeout);
|
||||
return _WS_poll(0, fds, nfds, timeout, NULL, NULL);
|
||||
}
|
||||
|
||||
int ppoll(struct pollfd *fds, nfds_t nfds,
|
||||
const struct timespec *timeout, const sigset_t *sigmask)
|
||||
{
|
||||
TRACE("ppoll(_, %ld, _, _) called\n", nfds);
|
||||
return _WS_poll(0, fds, nfds, 0, timeout, sigmask);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* wswrap/wswrapper: Add WebSockets support to any service.
|
||||
* Copyright 2010 Joel Martin
|
||||
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
*
|
||||
* wswrapper.so is meant to be LD preloaded. Use wswrap to run a program using
|
||||
* wswrapper.so.
|
||||
*/
|
||||
|
||||
#ifdef DO_MSG
|
||||
#define MSG(...) \
|
||||
fprintf(stderr, "wswrapper: "); \
|
||||
fprintf(stderr, __VA_ARGS__);
|
||||
#else
|
||||
#define MSG(...)
|
||||
#endif
|
||||
|
||||
#ifdef DO_DEBUG
|
||||
#define DEBUG(...) \
|
||||
if (DO_DEBUG) { \
|
||||
fprintf(stderr, "wswrapper: "); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
}
|
||||
#else
|
||||
#define DEBUG(...)
|
||||
#endif
|
||||
|
||||
#ifdef DO_TRACE
|
||||
#define TRACE(...) \
|
||||
if (DO_TRACE) { \
|
||||
fprintf(stderr, "wswrapper: "); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
}
|
||||
#else
|
||||
#define TRACE(...)
|
||||
#endif
|
||||
|
||||
#define RET_ERROR(eno, ...) \
|
||||
fprintf(stderr, "wswrapper error: "); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
errno = eno; \
|
||||
return -1;
|
||||
|
||||
|
||||
const char _WS_response[] = "\
|
||||
HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
|
||||
Upgrade: WebSocket\r\n\
|
||||
Connection: Upgrade\r\n\
|
||||
%sWebSocket-Origin: %s\r\n\
|
||||
%sWebSocket-Location: %s://%s%s\r\n\
|
||||
%sWebSocket-Protocol: sample\r\n\
|
||||
\r\n%s";
|
||||
|
||||
#define WS_BUFSIZE 65536
|
||||
#define WS_MAX_FDS 1024
|
||||
|
||||
/* Buffers and state for each wrapped WebSocket connection */
|
||||
typedef struct {
|
||||
char rbuf[WS_BUFSIZE];
|
||||
char sbuf[WS_BUFSIZE];
|
||||
int rcarry_cnt;
|
||||
char rcarry[3];
|
||||
int newframe;
|
||||
} _WS_connection;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue