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>.
|
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
|
### Browser Requirements
|
||||||
|
|
||||||
* HTML5 Canvas: Except for Internet Explorer, most
|
* 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`
|
`./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`
|
`./utils/web.py PORT`
|
||||||
|
|
||||||
|
|
|
||||||
30
docs/TODO
30
docs/TODO
|
|
@ -1,19 +1,10 @@
|
||||||
Short Term:
|
Short Term:
|
||||||
|
|
||||||
- Playback/demo on website.
|
- Keyboard layout/internationalization support
|
||||||
|
- convert keyCode into proper charCode
|
||||||
- VNC performance and regression playback suite.
|
|
||||||
|
|
||||||
- Test on IE 9 preview 3.
|
- 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:
|
- Status bar menu/buttons:
|
||||||
- Explanatory hover text over buttons
|
- Explanatory hover text over buttons
|
||||||
|
|
||||||
|
|
@ -29,7 +20,20 @@ Medium Term:
|
||||||
- Clipboard button -> popup:
|
- Clipboard button -> popup:
|
||||||
- text, clear and send buttons
|
- 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
|
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:
|
Related projects:
|
||||||
|
|
||||||
guacamole: http://guacamole.sourceforge.net/
|
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
|
emulate websocket support on browsers without websocket support
|
||||||
(currently only Chrome has 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
|
Javascript doesn't have a bytearray type, so what you get out of
|
||||||
a WebSocket object is just Javascript strings. I believe that
|
a WebSocket object is just Javascript strings. Javascript has UTF-16
|
||||||
Javascript has UTF-16 unicode strings and anything sent through the
|
unicode strings and anything sent through the WebSocket gets converted
|
||||||
WebSocket gets converted to UTF-8 and vice-versa. So, one additional
|
to UTF-8 and vice-versa. So, one additional (and necessary) function
|
||||||
(and necessary) function of the proxy is base64 encoding/decoding what
|
of wsproxy is base64 encoding/decoding what is sent to/from the
|
||||||
is sent to/from the browser. Another option that I want to explore is
|
browser.
|
||||||
UTF-8 encoding in the proxy.
|
|
||||||
|
|
||||||
|
|
||||||
Building web-socket-js emulator:
|
Building web-socket-js emulator:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ function constructor() {
|
||||||
if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
|
if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
|
||||||
ctx = conf.ctx;
|
ctx = conf.ctx;
|
||||||
|
|
||||||
|
Util.Debug("User Agent: " + navigator.userAgent);
|
||||||
if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
|
if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
|
||||||
if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
|
if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
|
||||||
if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
|
if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
|
||||||
|
|
@ -216,7 +217,7 @@ function constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Translate DOM key down/up event to keysym value */
|
/* Translate DOM key down/up event to keysym value */
|
||||||
function getKeysym(e) {
|
that.getKeysym = function(e) {
|
||||||
var evt, keysym;
|
var evt, keysym;
|
||||||
evt = (e ? e : window.event);
|
evt = (e ? e : window.event);
|
||||||
|
|
||||||
|
|
@ -362,24 +363,24 @@ function onMouseMove(e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e) {
|
function onKeyDown(e) {
|
||||||
//Util.Debug("keydown: " + getKeysym(e));
|
//Util.Debug("keydown: " + that.getKeysym(e));
|
||||||
if (! conf.focused) {
|
if (! conf.focused) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (c_keyPress) {
|
if (c_keyPress) {
|
||||||
c_keyPress(getKeysym(e), 1);
|
c_keyPress(that.getKeysym(e), 1);
|
||||||
}
|
}
|
||||||
Util.stopEvent(e);
|
Util.stopEvent(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyUp(e) {
|
function onKeyUp(e) {
|
||||||
//Util.Debug("keyup: " + getKeysym(e));
|
//Util.Debug("keyup: " + that.getKeysym(e));
|
||||||
if (! conf.focused) {
|
if (! conf.focused) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (c_keyPress) {
|
if (c_keyPress) {
|
||||||
c_keyPress(getKeysym(e), 0);
|
c_keyPress(that.getKeysym(e), 0);
|
||||||
}
|
}
|
||||||
Util.stopEvent(e);
|
Util.stopEvent(e);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,8 @@ function constructor() {
|
||||||
Util.Info("Using native WebSockets");
|
Util.Info("Using native WebSockets");
|
||||||
updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
|
updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
|
||||||
} else {
|
} else {
|
||||||
Util.Warn("Using web-socket-js flash bridge");
|
Util.Warn("Using web-socket-js bridge. Flash version: " +
|
||||||
|
Util.Flash.version);
|
||||||
if ((! Util.Flash) ||
|
if ((! Util.Flash) ||
|
||||||
(Util.Flash.version < 9)) {
|
(Util.Flash.version < 9)) {
|
||||||
updateState('fatal', "WebSockets or Adobe Flash is required");
|
updateState('fatal', "WebSockets or Adobe Flash is required");
|
||||||
|
|
@ -819,9 +820,7 @@ init_msg = function() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'SecurityResult' :
|
case 'SecurityResult' :
|
||||||
if (rQlen() < 4) {
|
if (rQwait("VNC auth response ", 4)) { return false; }
|
||||||
return fail("Invalid VNC auth response");
|
|
||||||
}
|
|
||||||
switch (rQshift32()) {
|
switch (rQshift32()) {
|
||||||
case 0: // OK
|
case 0: // OK
|
||||||
// Fall through to ClientInitialisation
|
// Fall through to ClientInitialisation
|
||||||
|
|
@ -852,9 +851,7 @@ init_msg = function() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ServerInitialisation' :
|
case 'ServerInitialisation' :
|
||||||
if (rQlen() < 24) {
|
if (rQwait("server initialization", 24)) { return false; }
|
||||||
return fail("Invalid server initialisation");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Screen size */
|
/* Screen size */
|
||||||
fb_width = rQshift16();
|
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
|
wsproxy: wsproxy.o websocket.o md5.o
|
||||||
$(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@
|
$(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@
|
||||||
|
|
||||||
|
wswrapper.o: wswrapper.h
|
||||||
wswrapper.so: wswrapper.o md5.o
|
wswrapper.so: wswrapper.o md5.o
|
||||||
$(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -lresolv -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
|
At the most basic level, wsproxy just translates WebSockets traffic
|
||||||
to normal socket traffic. wsproxy accepts the WebSockets handshake,
|
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).
|
does not end in 255).
|
||||||
|
|
||||||
|
|
||||||
### Additional features
|
#### Additional wsproxy features
|
||||||
|
|
||||||
These are not necessary for the basic operation.
|
These are not necessary for the basic operation.
|
||||||
|
|
||||||
|
|
@ -38,44 +51,63 @@ These are not necessary for the basic operation.
|
||||||
option.
|
option.
|
||||||
|
|
||||||
|
|
||||||
### Implementations
|
#### Implementations of wsproxy
|
||||||
|
|
||||||
There are three implementations of wsproxy included: python, C, and
|
There are three implementations of wsproxy: python, C, and Node
|
||||||
Node (node.js).
|
(node.js). wswrapper is only implemented in C.
|
||||||
|
|
||||||
Here is the feature support matrix for the wsproxy implementations:
|
|
||||||
|
|
||||||
|
Here is the feature support matrix for the the wsproxy implementations
|
||||||
|
and wswrapper:
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Implementation</th>
|
<th>Program</th>
|
||||||
<th>Basic Proxying</th>
|
<th>Language</th>
|
||||||
<th>Multi-process</th>
|
<th>Proxy or Interposer</th>
|
||||||
<th>Daemonizing</th>
|
<th>Multiprocess</th>
|
||||||
|
<th>Daemonize</th>
|
||||||
<th>SSL/wss</th>
|
<th>SSL/wss</th>
|
||||||
<th>Flash Policy Server</th>
|
<th>Flash Policy Server</th>
|
||||||
<th>Session Recording</th>
|
<th>Session Record</th>
|
||||||
|
<th>Web Server</th>
|
||||||
</tr> <tr>
|
</tr> <tr>
|
||||||
|
<td>wsproxy.py</td>
|
||||||
<td>python</td>
|
<td>python</td>
|
||||||
<td>yes</td>
|
<td>proxy</td>
|
||||||
<td>yes</td>
|
<td>yes</td>
|
||||||
<td>yes</td>
|
<td>yes</td>
|
||||||
<td>yes 1</td>
|
<td>yes 1</td>
|
||||||
<td>yes</td>
|
<td>yes</td>
|
||||||
<td>yes</td>
|
<td>yes</td>
|
||||||
</tr> <tr>
|
|
||||||
<td>C</td>
|
|
||||||
<td>yes</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>yes</td>
|
<td>yes</td>
|
||||||
<td>yes</td>
|
<td>yes</td>
|
||||||
<td>no</td>
|
<td>no</td>
|
||||||
|
<td>no</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tr> <tr>
|
</tr> <tr>
|
||||||
|
<td>wsproxy.js</td>
|
||||||
<td>Node (node.js)</td>
|
<td>Node (node.js)</td>
|
||||||
|
<td>proxy</td>
|
||||||
<td>yes</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>
|
<td>no</td>
|
||||||
<td>no</td>
|
<td>no</td>
|
||||||
|
|
@ -83,6 +115,7 @@ Here is the feature support matrix for the wsproxy implementations:
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
* Note 1: to use SSL/wss with python 2.5 or older, see the following
|
* Note 1: to use SSL/wss with python 2.5 or older, see the following
|
||||||
section on *Building the Python ssl module*.
|
section on *Building the Python ssl module*.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,25 @@ usage() {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
fi
|
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
|
||||||
echo "Starts a mini-webserver and the WebSockets proxy and"
|
echo "Starts a mini-webserver and the WebSockets proxy and"
|
||||||
echo "provides a cut and paste URL to go to."
|
echo "provides a cut and paste URL to go to."
|
||||||
echo
|
echo
|
||||||
echo " --web WEB_PORT Port to serve web pages at"
|
echo " --listen PORT Port for webserver/proxy to listen on"
|
||||||
echo " Default: 8080"
|
echo " Default: 6080"
|
||||||
echo " --proxy PROXY_PORT Port for proxy to listen on"
|
|
||||||
echo " Default: 8081"
|
|
||||||
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
|
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
|
||||||
echo " Default: localhost:5900"
|
echo " Default: localhost:5900"
|
||||||
|
echo " --cert CERT Path to combined cert/key file"
|
||||||
|
echo " Default: self.pem"
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
NAME="$(basename $0)"
|
NAME="$(basename $0)"
|
||||||
HERE=$(readlink -f $(dirname $0))
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
WEB_PORT="6080"
|
PORT="6080"
|
||||||
PROXY_PORT="6081"
|
|
||||||
VNC_DEST="localhost:5900"
|
VNC_DEST="localhost:5900"
|
||||||
web_pid=""
|
CERT=""
|
||||||
proxy_pid=""
|
proxy_pid=""
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
|
|
@ -36,10 +35,6 @@ cleanup() {
|
||||||
trap - TERM QUIT INT EXIT
|
trap - TERM QUIT INT EXIT
|
||||||
trap "true" CHLD # Ignore cleanup messages
|
trap "true" CHLD # Ignore cleanup messages
|
||||||
echo
|
echo
|
||||||
if [ -n "${web_pid}" ]; then
|
|
||||||
echo "Terminating webserver (${web_pid})"
|
|
||||||
kill ${web_pid}
|
|
||||||
fi
|
|
||||||
if [ -n "${proxy_pid}" ]; then
|
if [ -n "${proxy_pid}" ]; then
|
||||||
echo "Terminating WebSockets proxy (${proxy_pid})"
|
echo "Terminating WebSockets proxy (${proxy_pid})"
|
||||||
kill ${proxy_pid}
|
kill ${proxy_pid}
|
||||||
|
|
@ -52,9 +47,9 @@ cleanup() {
|
||||||
while [ "$*" ]; do
|
while [ "$*" ]; do
|
||||||
param=$1; shift; OPTARG=$1
|
param=$1; shift; OPTARG=$1
|
||||||
case $param in
|
case $param in
|
||||||
--web) WEB_PORT="${OPTARG}"; shift ;;
|
--listen) PORT="${OPTARG}"; shift ;;
|
||||||
--proxy) PROXY_PORT="${OPTARG}"; shift ;;
|
|
||||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||||
|
--cert) CERT="${OPTARG}"; shift ;;
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||||
*) break ;;
|
*) break ;;
|
||||||
|
|
@ -65,28 +60,39 @@ done
|
||||||
which netstat >/dev/null 2>&1 \
|
which netstat >/dev/null 2>&1 \
|
||||||
|| die "Must have netstat installed"
|
|| die "Must have netstat installed"
|
||||||
|
|
||||||
netstat -ltn | grep -qs "${WEB_PORT}.*LISTEN" \
|
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
|
||||||
&& die "Port ${WEB_PORT} in use. Try --web WEB_PORT"
|
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||||
|
|
||||||
netstat -ltn | grep -qs "${PROXY_PORT}.*LISTEN" \
|
|
||||||
&& die "Port ${PROXY_PORT} in use. Try --proxy PROXY_PORT"
|
|
||||||
|
|
||||||
trap "cleanup" TERM QUIT INT EXIT
|
trap "cleanup" TERM QUIT INT EXIT
|
||||||
|
|
||||||
echo "Starting webserver on port ${WEB_PORT}"
|
# Find vnc.html
|
||||||
${HERE}/web.py ${WEB_PORT} >/dev/null &
|
if [ -e "$(pwd)/vnc.html" ]; then
|
||||||
web_pid="$!"
|
WEB=$(pwd)
|
||||||
sleep 1
|
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||||
if ps -p ${web_pid} >/dev/null; then
|
WEB=${HERE}/../
|
||||||
echo "Started webserver (pid: ${web_pid})"
|
elif [ -e "${HERE}/vnc.html" ]; then
|
||||||
|
WEB=${HERE}
|
||||||
else
|
else
|
||||||
web_pid=
|
die "Could not find vnc.html"
|
||||||
echo "Failed to start webserver"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting WebSockets proxy on port ${PROXY_PORT}"
|
# Find self.pem
|
||||||
${HERE}/wsproxy.py -f ${PROXY_PORT} ${VNC_DEST} &
|
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="$!"
|
proxy_pid="$!"
|
||||||
sleep 1
|
sleep 1
|
||||||
if ps -p ${proxy_pid} >/dev/null; then
|
if ps -p ${proxy_pid} >/dev/null; then
|
||||||
|
|
@ -98,8 +104,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n\nNavigate to to this URL:\n"
|
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"
|
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():
|
def serve():
|
||||||
bindsocket = socket.socket()
|
bindsocket = socket.socket()
|
||||||
|
bindsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
#bindsocket.bind(('localhost', PORT))
|
#bindsocket.bind(('localhost', PORT))
|
||||||
bindsocket.bind(('', PORT))
|
bindsocket.bind(('', PORT))
|
||||||
bindsocket.listen(5)
|
bindsocket.listen(5)
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,7 @@ int ws_socket_free(ws_ctx_t *ctx) {
|
||||||
ctx->ssl_ctx = NULL;
|
ctx->ssl_ctx = NULL;
|
||||||
}
|
}
|
||||||
if (ctx->sockfd) {
|
if (ctx->sockfd) {
|
||||||
|
shutdown(ctx->sockfd, SHUT_RDWR);
|
||||||
close(ctx->sockfd);
|
close(ctx->sockfd);
|
||||||
ctx->sockfd = 0;
|
ctx->sockfd = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -350,26 +351,30 @@ ws_ctx_t *do_handshake(int sock) {
|
||||||
handshake[len] = 0;
|
handshake[len] = 0;
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
handler_msg("ignoring empty handshake\n");
|
handler_msg("ignoring empty handshake\n");
|
||||||
close(sock);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) {
|
} else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) {
|
||||||
len = recv(sock, handshake, 1024, 0);
|
len = recv(sock, handshake, 1024, 0);
|
||||||
handshake[len] = 0;
|
handshake[len] = 0;
|
||||||
handler_msg("sending flash policy response\n");
|
handler_msg("sending flash policy response\n");
|
||||||
send(sock, policy_response, sizeof(policy_response), 0);
|
send(sock, policy_response, sizeof(policy_response), 0);
|
||||||
close(sock);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if ((bcmp(handshake, "\x16", 1) == 0) ||
|
} else if ((bcmp(handshake, "\x16", 1) == 0) ||
|
||||||
(bcmp(handshake, "\x80", 1) == 0)) {
|
(bcmp(handshake, "\x80", 1) == 0)) {
|
||||||
// SSL
|
// 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);
|
ws_ctx = ws_socket_ssl(sock, settings.cert, settings.key);
|
||||||
if (! ws_ctx) { return NULL; }
|
if (! ws_ctx) { return NULL; }
|
||||||
scheme = "wss";
|
scheme = "wss";
|
||||||
handler_msg("using SSL socket\n");
|
handler_msg("using SSL socket\n");
|
||||||
} else if (settings.ssl_only) {
|
} else if (settings.ssl_only) {
|
||||||
handler_msg("non-SSL connection disallowed\n");
|
handler_msg("non-SSL connection disallowed\n");
|
||||||
close(sock);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
} else {
|
} else {
|
||||||
ws_ctx = ws_socket(sock);
|
ws_ctx = ws_socket(sock);
|
||||||
|
|
@ -380,14 +385,12 @@ ws_ctx_t *do_handshake(int sock) {
|
||||||
len = ws_recv(ws_ctx, handshake, 4096);
|
len = ws_recv(ws_ctx, handshake, 4096);
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
handler_emsg("Client closed during handshake\n");
|
handler_emsg("Client closed during handshake\n");
|
||||||
close(sock);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
handshake[len] = 0;
|
handshake[len] = 0;
|
||||||
|
|
||||||
if (!parse_handshake(handshake, &headers)) {
|
if (!parse_handshake(handshake, &headers)) {
|
||||||
handler_emsg("Invalid WS request\n");
|
handler_emsg("Invalid WS request\n");
|
||||||
close(sock);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,8 +527,7 @@ void start_server() {
|
||||||
if (pid == 0) { // handler process
|
if (pid == 0) { // handler process
|
||||||
ws_ctx = do_handshake(csock);
|
ws_ctx = do_handshake(csock);
|
||||||
if (ws_ctx == NULL) {
|
if (ws_ctx == NULL) {
|
||||||
close(csock);
|
handler_msg("No connection after handshake\n");
|
||||||
handler_msg("No connection after handshake");
|
|
||||||
break; // Child process exits
|
break; // Child process exits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,13 +535,22 @@ void start_server() {
|
||||||
if (pipe_error) {
|
if (pipe_error) {
|
||||||
handler_emsg("Closing due to SIGPIPE\n");
|
handler_emsg("Closing due to SIGPIPE\n");
|
||||||
}
|
}
|
||||||
close(csock);
|
|
||||||
handler_msg("handler exit\n");
|
|
||||||
break; // Child process exits
|
break; // Child process exits
|
||||||
} else { // parent process
|
} else { // parent process
|
||||||
settings.handler_id += 1;
|
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 sys, socket, ssl, struct, traceback
|
||||||
import os, resource, errno, signal # daemonizing
|
import os, resource, errno, signal # daemonizing
|
||||||
|
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||||
|
from cStringIO import StringIO
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
try:
|
try:
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
|
@ -31,7 +33,8 @@ settings = {
|
||||||
'key' : None,
|
'key' : None,
|
||||||
'ssl_only' : False,
|
'ssl_only' : False,
|
||||||
'daemon' : True,
|
'daemon' : True,
|
||||||
'record' : None, }
|
'record' : None,
|
||||||
|
'web' : False, }
|
||||||
|
|
||||||
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
||||||
Upgrade: WebSocket\r
|
Upgrade: WebSocket\r
|
||||||
|
|
@ -47,6 +50,29 @@ policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports
|
||||||
class EClose(Exception):
|
class EClose(Exception):
|
||||||
pass
|
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="."):
|
def traffic(token="."):
|
||||||
if settings['verbose'] and not settings['daemon']:
|
if settings['verbose'] and not settings['daemon']:
|
||||||
sys.stdout.write(token)
|
sys.stdout.write(token)
|
||||||
|
|
@ -54,7 +80,10 @@ def traffic(token="."):
|
||||||
|
|
||||||
def handler_msg(msg):
|
def handler_msg(msg):
|
||||||
if not settings['daemon']:
|
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):
|
def encode(buf):
|
||||||
buf = b64encode(buf)
|
buf = b64encode(buf)
|
||||||
|
|
@ -96,51 +125,77 @@ def gen_md5(keys):
|
||||||
return md5(struct.pack('>II8s', num1, num2, key3)).digest()
|
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
|
# Peek, but don't read the data
|
||||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||||
#handler_msg("Handshake [%s]" % repr(handshake))
|
#handler_msg("Handshake [%s]" % repr(handshake))
|
||||||
if handshake == "":
|
if handshake == "":
|
||||||
handler_msg("ignoring empty handshake")
|
raise EClose("ignoring empty handshake")
|
||||||
sock.close()
|
|
||||||
return False
|
|
||||||
elif handshake.startswith("<policy-file-request/>"):
|
elif handshake.startswith("<policy-file-request/>"):
|
||||||
handshake = sock.recv(1024)
|
handshake = sock.recv(1024)
|
||||||
handler_msg("Sending flash policy response")
|
|
||||||
sock.send(policy_response)
|
sock.send(policy_response)
|
||||||
sock.close()
|
raise EClose("Sending flash policy response")
|
||||||
return False
|
|
||||||
elif handshake[0] in ("\x16", "\x80"):
|
elif handshake[0] in ("\x16", "\x80"):
|
||||||
|
if not os.path.exists(settings['cert']):
|
||||||
|
raise EClose("SSL connection but '%s' not found"
|
||||||
|
% settings['cert'])
|
||||||
|
try:
|
||||||
retsock = ssl.wrap_socket(
|
retsock = ssl.wrap_socket(
|
||||||
sock,
|
sock,
|
||||||
server_side=True,
|
server_side=True,
|
||||||
certfile=settings['cert'],
|
certfile=settings['cert'],
|
||||||
keyfile=settings['key'])
|
keyfile=settings['key'])
|
||||||
|
except ssl.SSLError, x:
|
||||||
|
if x.args[0] == ssl.SSL_ERROR_EOF:
|
||||||
|
raise EClose("")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
scheme = "wss"
|
scheme = "wss"
|
||||||
handler_msg("using SSL/TLS")
|
stype = "SSL/TLS (wss://)"
|
||||||
elif settings['ssl_only']:
|
elif settings['ssl_only']:
|
||||||
handler_msg("non-SSL connection disallowed")
|
raise EClose("non-SSL connection received but disallowed")
|
||||||
sock.close()
|
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
retsock = sock
|
retsock = sock
|
||||||
scheme = "ws"
|
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)
|
handshake = retsock.recv(4096)
|
||||||
#handler_msg("handshake: " + repr(handshake))
|
#handler_msg("handshake: " + repr(handshake))
|
||||||
|
|
||||||
if len(handshake) == 0:
|
if len(handshake) == 0:
|
||||||
raise EClose("Client closed during handshake")
|
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)
|
h = parse_handshake(handshake)
|
||||||
|
|
||||||
if h.get('key3'):
|
if h.get('key3'):
|
||||||
trailer = gen_md5(h)
|
trailer = gen_md5(h)
|
||||||
pre = "Sec-"
|
pre = "Sec-"
|
||||||
handler_msg("using protocol version 76")
|
ver = 76
|
||||||
else:
|
else:
|
||||||
trailer = ""
|
trailer = ""
|
||||||
pre = ""
|
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,
|
response = server_handshake % (pre, h['Origin'], pre, scheme,
|
||||||
h['Host'], h['path'], pre, trailer)
|
h['Host'], h['path'], pre, trailer)
|
||||||
|
|
@ -172,8 +227,8 @@ def daemonize(keepfd=None):
|
||||||
try:
|
try:
|
||||||
if fd != keepfd:
|
if fd != keepfd:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
elif settings['verbose']:
|
else:
|
||||||
print "Keeping fd: %d" % fd
|
handler_vmsg("Keeping fd: %d" % fd)
|
||||||
except OSError, exc:
|
except OSError, exc:
|
||||||
if exc.errno != errno.EBADF: raise
|
if exc.errno != errno.EBADF: raise
|
||||||
|
|
||||||
|
|
@ -204,25 +259,25 @@ def start_server():
|
||||||
csock = startsock = None
|
csock = startsock = None
|
||||||
pid = 0
|
pid = 0
|
||||||
startsock, address = lsock.accept()
|
startsock, address = lsock.accept()
|
||||||
handler_msg('got client connection from %s' % address[0])
|
handler_vmsg('%s: forking handler' % address[0])
|
||||||
|
|
||||||
handler_msg("forking handler process")
|
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
|
|
||||||
if pid == 0: # handler process
|
if pid == 0: # handler process
|
||||||
csock = do_handshake(startsock)
|
csock = do_handshake(startsock, address)
|
||||||
if not csock:
|
|
||||||
handler_msg("No connection after handshake");
|
|
||||||
break
|
|
||||||
settings['handler'](csock)
|
settings['handler'](csock)
|
||||||
else: # parent process
|
else: # parent process
|
||||||
settings['handler_id'] += 1
|
settings['handler_id'] += 1
|
||||||
|
|
||||||
except EClose, exc:
|
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:
|
except Exception, exc:
|
||||||
handler_msg("handler exception: %s" % str(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 pid == 0:
|
||||||
if csock: csock.close()
|
if csock: csock.close()
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,10 @@ int main(int argc, char *argv[])
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.cert = realpath("self.pem", NULL);
|
settings.cert = realpath("self.pem", NULL);
|
||||||
|
if (!settings.cert) {
|
||||||
|
/* Make sure it's always set to something */
|
||||||
|
settings.cert = "self.pem";
|
||||||
|
}
|
||||||
settings.key = "";
|
settings.key = "";
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|
@ -326,9 +330,11 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ssl_only) {
|
if (ssl_only) {
|
||||||
if (!settings.cert || !access(settings.cert, R_OK)) {
|
if (!access(settings.cert, R_OK)) {
|
||||||
usage("SSL only and cert file not found\n");
|
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);
|
//printf(" verbose: %d\n", settings.verbose);
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,8 @@ if __name__ == '__main__':
|
||||||
help="SSL key file (if separate from cert)")
|
help="SSL key file (if separate from cert)")
|
||||||
parser.add_option("--ssl-only", action="store_true",
|
parser.add_option("--ssl-only", action="store_true",
|
||||||
help="disallow non-encrypted connections")
|
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()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
if len(args) > 2: parser.error("Too many arguments")
|
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):
|
if options.ssl_only and not os.path.exists(options.cert):
|
||||||
parser.error("SSL only and %s not found" % 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['verbose'] = options.verbose
|
||||||
settings['listen_host'] = host
|
settings['listen_host'] = host
|
||||||
|
|
@ -174,4 +178,7 @@ if __name__ == '__main__':
|
||||||
settings['daemon'] = options.daemon
|
settings['daemon'] = options.daemon
|
||||||
if options.record:
|
if options.record:
|
||||||
settings['record'] = os.path.abspath(options.record)
|
settings['record'] = os.path.abspath(options.record)
|
||||||
|
if options.web:
|
||||||
|
os.chdir = options.web
|
||||||
|
settings['web'] = options.web
|
||||||
start_server()
|
start_server()
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,19 @@
|
||||||
* Copyright 2010 Joel Martin
|
* Copyright 2010 Joel Martin
|
||||||
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
* 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 <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -14,63 +23,60 @@
|
||||||
#define __USE_GNU 1 // Pull in RTLD_NEXT
|
#define __USE_GNU 1 // Pull in RTLD_NEXT
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <resolv.h> /* base64 encode/decode */
|
#include <resolv.h> /* base64 encode/decode */
|
||||||
|
#include <sys/time.h>
|
||||||
#include "md5.h"
|
#include "md5.h"
|
||||||
|
#include "wswrapper.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;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If WSWRAP_PORT environment variable is set then listen to the bind fd that
|
* 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
|
* matches WSWRAP_PORT
|
||||||
* called on.
|
|
||||||
*/
|
*/
|
||||||
int _WS_listen_fd = -1;
|
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;
|
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
|
* 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)
|
size_t len, int flags)
|
||||||
{
|
{
|
||||||
_WS_connection *ws = _WS_connections[sockfd];
|
_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 sockflags;
|
||||||
int i;
|
int i;
|
||||||
char *fstart, *fend, *cstart;
|
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 (!rfunc) rfunc = (void *(*)()) dlsym(RTLD_NEXT, "recv");
|
||||||
if (!rfunc2) rfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "read");
|
if (!rfunc2) rfunc2 = (void *(*)()) dlsym(RTLD_NEXT, "read");
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! ws) {
|
if (! ws) {
|
||||||
// Not our file descriptor, just pass through
|
// Not our file descriptor, just pass through
|
||||||
if (recvf) {
|
if (recvf) {
|
||||||
|
|
@ -271,9 +339,20 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
||||||
return (ssize_t) rfunc2(sockfd, buf, len);
|
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);
|
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;
|
left = len;
|
||||||
retlen = 0;
|
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);
|
RET_ERROR(ENOMEM, "recv of %d bytes is larger than buffer\n", rawcount);
|
||||||
}
|
}
|
||||||
|
|
||||||
i = 0;
|
ready = _WS_ready(sockfd, 0);
|
||||||
while (1) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We have enough data to return something */
|
||||||
|
|
||||||
/* Peek at everything available */
|
/* Peek at everything available */
|
||||||
rawlen = (int) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1,
|
rawlen = (ssize_t) rfunc(sockfd, ws->rbuf, WS_BUFSIZE-1,
|
||||||
flags | MSG_PEEK);
|
flags | MSG_PEEK);
|
||||||
if (rawlen <= 0) {
|
if (rawlen <= 0) {
|
||||||
DEBUG("_WS_recv: returning because rawlen %d\n", rawlen);
|
RET_ERROR(EPROTO, "Socket was ready but then had failure");
|
||||||
return (ssize_t) rawlen;
|
|
||||||
}
|
}
|
||||||
fstart = ws->rbuf;
|
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';
|
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
DEBUG("\n");
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (ws->newframe) {
|
if (ws->newframe) {
|
||||||
if (fstart[0] != '\x00') {
|
if (fstart[0] != '\x00') {
|
||||||
|
|
@ -389,9 +443,9 @@ ssize_t _WS_recv(int recvf, int sockfd, const void *buf,
|
||||||
deccount = fend - fstart;
|
deccount = fend - fstart;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now consume what was processed */
|
/* Now consume what was processed (if not MSG_PEEK) */
|
||||||
if (flags & MSG_PEEK) {
|
if (flags & MSG_PEEK) {
|
||||||
MSG("*** Got MSG_PEEK ***\n");
|
DEBUG("_WS_recv(%d, _, %d) MSG_PEEK, not consuming\n", sockfd, len);
|
||||||
} else {
|
} else {
|
||||||
rfunc(sockfd, ws->rbuf, fstart - ws->rbuf + deccount + ws->newframe, flags);
|
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],
|
DEBUG("Saving carry bytes: %u,%u\n", ws->rcarry[0],
|
||||||
ws->rcarry[1]);
|
ws->rcarry[1]);
|
||||||
} else {
|
} else {
|
||||||
MSG("Waah2!\n");
|
RET_ERRO(EPROTO, "Too many carry bytes!\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
((char *) buf)[retlen] = '\x00';
|
((char *) buf)[retlen] = '\x00';
|
||||||
|
|
||||||
/*
|
TRACE("<< _WS_recv(%d) retlen %d\n", sockfd, retlen);
|
||||||
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);
|
|
||||||
*/
|
|
||||||
return retlen;
|
return retlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,7 +510,7 @@ ssize_t _WS_send(int sendf, int sockfd, const void *buf,
|
||||||
return (ssize_t) sfunc2(sockfd, buf, len);
|
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);
|
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);
|
rlen = (int) sfunc(sockfd, ws->sbuf, rawlen, flags);
|
||||||
|
|
||||||
if (rlen <= 0) {
|
if (rlen <= 0) {
|
||||||
/* Couldn't send, just return */
|
TRACE("<< _WS_send(%d, _, %d) send failed, returning\n", sockfd, len);
|
||||||
return rlen;
|
return rlen;
|
||||||
} else if (rlen < rawlen) {
|
} else if (rlen < rawlen) {
|
||||||
/* Spin until we can send a whole base64 chunck and frame end */
|
/* 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) {
|
} else if (clen == 0) {
|
||||||
MSG("_WS_send: got clen %d\n", clen);
|
MSG("_WS_send: got clen %d\n", clen);
|
||||||
} else if (!(sockflags & O_NONBLOCK)) {
|
} else if (!(sockflags & O_NONBLOCK)) {
|
||||||
MSG("_WS_send: clen %d\n", clen);
|
MSG("<< _WS_send: clen %d\n", clen);
|
||||||
return clen;
|
return clen;
|
||||||
}
|
}
|
||||||
if (i > 1000000) {
|
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);
|
} while (left > 0);
|
||||||
|
//DEBUG("_WS_send: spins until finished %d\n", i);
|
||||||
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 */
|
/* Scale return value for base64 encoding size */
|
||||||
retlen = (retlen*3)/4;
|
retlen = (retlen*3)/4;
|
||||||
|
|
||||||
/*
|
TRACE(">> _WS_send(%d, _, %d) retlen %d\n", sockfd, len, retlen);
|
||||||
DEBUG("*** send ");
|
|
||||||
for (i = 0; i < retlen; i++) {
|
|
||||||
DEBUG("%u,", (unsigned char) ((char *)buf)[i]);
|
|
||||||
}
|
|
||||||
DEBUG(" as '%s' (%d)\n", ws->sbuf+1, rlen);
|
|
||||||
*/
|
|
||||||
return (ssize_t) 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
|
* 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)
|
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||||
{
|
{
|
||||||
static void * (*func)();
|
static void * (*func)();
|
||||||
|
|
@ -562,37 +841,37 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||||
char * WSWRAP_PORT, * end;
|
char * WSWRAP_PORT, * end;
|
||||||
int ret, envport, bindport = htons(addr_in->sin_port);
|
int ret, envport, bindport = htons(addr_in->sin_port);
|
||||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
|
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);
|
ret = (int) func(sockfd, addr, addrlen);
|
||||||
|
|
||||||
if (addr_in->sin_family != AF_INET) {
|
if (addr_in->sin_family != AF_INET) {
|
||||||
// TODO: handle IPv6
|
// TODO: handle IPv6
|
||||||
DEBUG("bind, ignoring non-IPv4 socket\n");
|
TRACE("<< bind, ignoring non-IPv4 socket\n");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
WSWRAP_PORT = getenv("WSWRAP_PORT");
|
WSWRAP_PORT = getenv("WSWRAP_PORT");
|
||||||
if ((! WSWRAP_PORT) || (*WSWRAP_PORT == '\0')) {
|
if ((! WSWRAP_PORT) || (*WSWRAP_PORT == '\0')) {
|
||||||
// TODO: interpose on all sockets
|
// TODO: interpose on all sockets when WSWRAP_PORT not set
|
||||||
DEBUG("bind, not interposing: WSWRAP_PORT is not set\n");
|
TRACE("<< bind, not interposing: WSWRAP_PORT is not set\n");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
envport = strtol(WSWRAP_PORT, &end, 10);
|
envport = strtol(WSWRAP_PORT, &end, 10);
|
||||||
if ((envport == 0) || (*end != '\0')) {
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envport != bindport) {
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
MSG("bind, interposing on port: %d (fd %d)\n", envport, sockfd);
|
|
||||||
_WS_listen_fd = sockfd;
|
_WS_listen_fd = sockfd;
|
||||||
|
|
||||||
|
TRACE("<< bind, interposing on port: %d (fd %d)\n", envport, sockfd);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -601,25 +880,28 @@ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
|
||||||
int fd, ret, envfd;
|
int fd, ret, envfd;
|
||||||
static void * (*func)();
|
static void * (*func)();
|
||||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "accept");
|
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);
|
fd = (int) func(sockfd, addr, addrlen);
|
||||||
|
|
||||||
if (_WS_listen_fd == -1) {
|
if (_WS_listen_fd == -1) {
|
||||||
DEBUG("not interposing\n");
|
TRACE("<< accept: not interposing\n");
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_WS_listen_fd != sockfd) {
|
if (_WS_listen_fd != sockfd) {
|
||||||
DEBUG("not interposing on fd %d\n", sockfd);
|
TRACE("<< accept: not interposing on fd %d\n", sockfd);
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (_WS_connections[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 {
|
} else {
|
||||||
/* It's a port we're interposing on so allocate memory for it */
|
/* 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)))) {
|
if (! (_WS_connections[fd] = malloc(sizeof(_WS_connection)))) {
|
||||||
RET_ERROR(ENOMEM, "Could not allocate interposer memory\n");
|
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]->rcarry[0] = '\0';
|
||||||
_WS_connections[fd]->newframe = 1;
|
_WS_connections[fd]->newframe = 1;
|
||||||
|
|
||||||
|
/* Add to search list for select/pselect */
|
||||||
|
_WS_fds[_WS_nfds] = fd;
|
||||||
|
_WS_nfds++;
|
||||||
|
|
||||||
ret = _WS_handshake(fd);
|
ret = _WS_handshake(fd);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
free(_WS_connections[fd]);
|
free(_WS_connections[fd]);
|
||||||
_WS_connections[fd] = NULL;
|
_WS_connections[fd] = NULL;
|
||||||
errno = EPROTO;
|
errno = EPROTO;
|
||||||
|
TRACE("<< accept(%d, _, _): ret %d\n", sockfd, ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
MSG("interposing on fd %d (allocated memory)\n", fd);
|
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 close(int fd)
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
static void * (*func)();
|
static void * (*func)();
|
||||||
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "close");
|
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "close");
|
||||||
|
|
||||||
if (_WS_connections[fd]) {
|
if (_WS_connections[fd]) {
|
||||||
|
TRACE(">> close(%d)\n", fd);
|
||||||
free(_WS_connections[fd]);
|
free(_WS_connections[fd]);
|
||||||
_WS_connections[fd] = NULL;
|
_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);
|
MSG("finished interposing on fd %d (freed memory)\n", fd);
|
||||||
|
TRACE("<< close(%d)\n", fd);
|
||||||
}
|
}
|
||||||
return (int) func(fd);
|
return (int) func(fd);
|
||||||
}
|
}
|
||||||
|
|
@ -656,25 +958,54 @@ int close(int fd)
|
||||||
|
|
||||||
ssize_t read(int fd, void *buf, size_t count)
|
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);
|
return (ssize_t) _WS_recv(0, fd, buf, count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t write(int fd, const void *buf, size_t count)
|
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);
|
return (ssize_t) _WS_send(0, fd, buf, count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
|
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);
|
return (ssize_t) _WS_recv(1, sockfd, buf, len, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t send(int sockfd, const void *buf, size_t len, int 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);
|
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