cURL The wonderful HTTP plumbing to
Found at: gopher.blog.benjojo.co.uk:70/you-cant-curl-under-pressure
You cannot cURL under pressure
===
cURL. The wonderful HTTP plumbing tool that powers both a
lot of command line debugging and bash scripts, but also exists
as a strong foundation in our applications in the form of
libcurl.
The scale of adoption of libcurl / curl is actually
remarkable. It's mostly kept up to date with new protocols too,
ensuring that it's relevance is maintained. Putting it bluntly, there
is not really a better tool in the unix(-like) land for
making HTTP/HTTPS requests than cURL. (For windows I guess it's
`50/50` with WinHTTP and for OSX it's URLSession.)
tonns of stuff! Just look at the home page!
But the scope creep of cURL is also something to behold,
the program can do tonns of stuff! Just look at the home
page!
```
DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP,
IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP,
SFTP, SMB, SMBS, SMTP, SMTPS, Telnet and TFTP.
curl supports SSL certificates, HTTP POST, HTTP PUT,
FTP uploading, HTTP form based upload, proxies, HTTP/2,
cookies, user+password authentication
(Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and
Kerberos),
file transfer resume, proxy tunneling and more.
```
Holy shit! That's a lot of RFC's that have been
implemented. The help pages of a fully loaded cURL install are,
understandably, **very** long:
```
# curl --help | wc -l
221
# curl --manual | wc -l
4596
```
With cURL having this many features (with the general mass
of them being totally unknown to me, let alone how you use
them) got me thinking... What if you could do a game show style
challenge for them?
This brought back memories of the game that the now
defunct UsVsTh3m made called "You can't javascript under pressure".
a gif of the game
Sadly UsVsTh3m's content is gone, but the Internet Archive
holds a functioning copy of it:
http://games.usvsth3m.com/javascript-under-pressure/
http://games.usvsth3m.com/javascript-under-pressure/
I thought... Could you could turn the curl manual page
into a game?
Well no need to just think about it anymore. I present to
you:
you cannot curl under pressure logo
Interested on how this works?
Below is a technical write up of the infrastructure behind
this game, if you are stopping here then feel free to keep
track of my future shenanigans using my blog's RSS or following
me on Twitter.
---
# The infrastructure
The game itself is actually quite basic, it boils down to
this flow diagram:
basic flow diagram
To prevent users from tripping over themselves (or
eachother) and making their environment dirty, every time a user
completes a challenge they are switched to a new VM:
showing the almost invisible switch over in VMs
The way this works internally is a single `vm-router` takes
in the main websocket connection, and holds the game state
with the client. It then talks to a number of copies of a
test-supervisor.
`test-supervisor` holds a number of booted VM's in order to
quickly serve demand. When a challenge is over, the VM that was
being used for that user is killed along with the filesystem and
a new VM is booted in its place to prevent cross
contamination.
top down view of the web game
## Controlling Load
I've had a nasty habit in the past of making blog posts
that had interactive elements that the feature backends can't
keep up (even though the blog's 100% cache hit rate keeps
going).
The most recent case of this is when I wrote about a
QEMU vulnerability in my own site and the incoming visitors
rushing to try out that site had me asking friends for compute
capacity in a hurry to keep up with demand.
Since this game requires cycling over a lot of VMs, the
'slashdot effect' could really be lethal for this post. So I've
spent time this round to help things run smoothly.
One of the big bottlenecks is booting VMs. I spent a
considerable amount of time tweaking both the qemu parameters and linux
kernel config to try and cut down on any drivers or anything
else slowing down the state of getting a system booted and into
a shell.
I ended up taking dmesg dumps and moving them into
spreadsheets to highlight stages that were taking a long time.
the flawless dmesg spreadsheet
This is not a fool proof method as kernel does do some
stuff async on boot. However for my case it ended up taking a
5 second boot to shell down to a 2.3 second boot to shell.
The changes I ended up doing was:
* Switch from e1000 to `virtionet`, since the intel driver
takes up to 1 second to start.
* Switch from IDE/SATA to virtio block devices, and compile
the SATA driver out, since it takes sometimes upwards of 600ms,
even if there are no SATA devices.
* Pass through the hosts `/dev/urandom` to the VirtioRNG.
* Remove HID drivers that didn't relate to serial.
Feng Tang's talk slides from Linux Plumbers Conference
I found Feng Tang's talk slides from Linux Plumbers
Conference enlightening on getting the boot time down.
The 2.3 second boot time was without KVM acceleration. I
didn't want to enable KVM since I was nervous about giving access
to that to possibly random users on the internet. With KVM
acceleration boot to a usable shell was roughly 600 milliseconds.
Giving I had built all of this up with buildroot I ended
up with a nice small rootfs and kernel image at the end of
it.
```
root@yccup:/home/proxytest# ls -alh rootfs.ext2 bzImage
-rw-r--r-- 1 root root 5.9M Oct 5 22:30 bzImage
-rw-r--r-- 1 root root 20M Oct 5 22:30 rootfs.ext2
```
## Catching connections
Since these small VMs fundamentally need network access to
work correctly, some creativity was needed to ensure that things
could remain both secure (so that other players can't sabotage
other players) and easy to manage (I need to be able to
attribute connections to individual VMs)
While most of the solution for VMs is TUN/TAP devices
attached to a bridge, that would require giving the qemu processes
root for a short amount of time, something I didn't really want
to do in the case that the qemu layer was breached.
Instead I stuck on using the User Networking (SLIRP) in
qemu and using the REDIRECT target to redirect all outgoing
connections from qemu back into the `test-supervisor` for handling
connection flow with iptables redirect
Giving every Tor Hidden Service an IPv6 address
The iptables rules to do this are pretty simple, and I've
used this kind of setup before on my Giving every Tor Hidden
Service an IPv6 address post.
```
iptables -t nat -N proxytest
iptables -t nat -A proxytest -d 192.0.2.0/24 -p tcp --dport
80 -j REDIRECT --to-ports 9999
iptables -t nat -A proxytest -d 192.0.2.0/24 -p tcp --dport
443 -j REDIRECT --to-ports 9998
iptables -t nat -A proxytest -d 192.0.2.0/24 -p tcp --dport
25 -j REDIRECT --to-ports 9996
iptables -t nat -A proxytest -d 192.0.2.0/24 -p tcp --dport
21 -j REDIRECT --to-ports 9995
iptables -t nat -A proxytest -d 192.0.2.0/24 -p tcp --dport
1337 -j REDIRECT --to-ports 9993
iptables -t nat -A proxytest -p tcp -j REDIRECT --to-ports
9997
iptables -t nat -A proxytest -p udp -j REDIRECT --to-ports
9997
iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner
proxytest -j proxytest
iptables -t nat -A OUTPUT -p udp -m owner --uid-owner
proxytest -j proxytest
```
You can then use the `/proc//fd` to track down the
connection owners to a single VM process. Using that we can check
what challenge they are suppose to be doing and route them over
to the correct testing logic.
This logic can get a little ugly with FTP, a protocol
that deals with multiple connections (one control socket and
another data socket).
## Auto scaling
Since this system would easily get overwhelmed, the
vm-router has the ability to spawn more systems in the case of dire
need. This works with a little homemade PDU controlled + DHCP/PXE
Boot cluster I made a while back, but then had no use for:
the html tables UI of my mini cloud
Small brick sized PCs are started and shut down
automatically to meet demand should there be a traffic surge.
---
Like most of my blog posts, you can find the code that
powers the backend on my github:
https://github.com/benjojo/you-cant-curl-under-pressure
If you enjoyed this, you may enjoy the rest of the blog,
You can keep up to date with both the serious and non-serious
stuff I do by using the RSS feed or by following me on
Until next time!
/**
* Copyright (c) 2014 The xterm.js authors. All rights
reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT
License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any
person obtaining a copy
* of this software and associated documentation files (the
"Software"), to deal
* in the Software without restriction, including without
limitation the rights
* to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell
* copies of the Software, and to permit persons to whom
the Software is
* furnished to do so, subject to the following
conditions:
*
* The above copyright notice and this permission notice
shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes,
among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the
canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so
that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the
textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll
bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to
the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 100;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline {
text-decoration: underline;
}
function proposeGeometry(term) {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle =
window.getComputedStyle(term.element.parentElement);
var parentElementHeight =
parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0,
parseInt(parentElementStyle.getPropertyValue('width')));
var elementStyle = window.getComputedStyle(term.element);
var elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom:
parseInt(elementStyle.getPropertyValue('padding-bottom')),
right:
parseInt(elementStyle.getPropertyValue('padding-right')),
left:
parseInt(elementStyle.getPropertyValue('padding-left'))
};
var elementPaddingVer = elementPadding.top +
elementPadding.bottom;
var elementPaddingHor = elementPadding.right +
elementPadding.left;
var availableHeight = parentElementHeight -
elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor
- term._core.viewport.scrollBarWidth;
var geometry = {
cols: Math.floor(availableWidth /
term._core._renderCoordinator.dimensions.actualCellWidth),
rows: Math.floor(availableHeight /
term._core._renderCoordinator.dimensions.actualCellHeight)
};
return geometry;
}
function fit(term) {
var geometry = proposeGeometry(term);
if (geometry) {
if (term.rows !== geometry.rows || term.cols !==
geometry.cols) {
term._core._renderCoordinator.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}
function apply(terminalConstructor) {
terminalConstructor.prototype.proposeGeometry = function ()
{
return proposeGeometry(this);
};
terminalConstructor.prototype.fit = function () {
fit(this);
};
}
//# sourceMappingURL=fit.js.map
var bootterm = new Terminal();
bootterm.open(document.getElementById('terminal-boot'));
fit(bootterm);
window.onresize = function(){
fit(onieterm);
fit(bootterm);
};
var tickerEnabled = false;
var gameTimerTick = function() {
var el = document.querySelectorAll("#game-timer")[0];
el.style = "background: #ffa5a5; height:
50px;margin-bottom: 10px;padding: 5px;";
var html = '"
Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto ColorEmoji";">';var seconds = Math.abs( Math.round((startTime -new(Date))/1000,1))var dmin = Math.floor( seconds/60)var dseconds = seconds%60html = html + "Time Elapsed: " + dmin + ":" +dseconds + "
el.innerHTML = html;
if (tickerEnabled) {
setTimeout(gameTimerTick,500)
}
}
var request = new XMLHttpRequest();
request.open('GET', 'https://yccup.benjojo.co.uk/check', true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
var data = JSON.parse(this.response);
if (data.Avail < 2) {
triggerboot = document.getElementById("play");
triggerboot.style = "display: none;";
bootterm.write("The backend for this post is
currently overloaded try reloading");
} else {
triggerboot = document.getElementById("play");
triggerboot.onclick = function(){
// let's go!
var buttonel =
document.querySelectorAll("#play")[0];
buttonel.style = "display: none;"
var exampleSocket = new
WebSocket("wss://yccup.benjojo.co.uk/serve");
exampleSocket.onopen = function (event)
{
exampleSocket.send("quite.");
startTime = new(Date);
tickerEnabled = true
setTimeout(gameTimerTick,100)
};
exampleSocket.onmessage = function
(event) {
bootterm.write(event.data);
}
exampleSocket.onclose = function (event)
{
tickerEnabled = false;
}
bootterm.onData(function (event) {
exampleSocket.send(event);
});
bootterm.onTitleChange(function (event) {
var el =
document.querySelectorAll("#game-status")[0];
el.style = "background:
lightgreen;height: 100px;margin-bottom: 10px;padding: 5px;";
var bits = event.split("|");
var html = '
style="font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"HelveticaNeue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UISymbol","Noto Color Emoji";">' + bits[0] + "" + bits[1] +"";el.innerHTML = html;console.log(event);});};}} else {// We reached our target server, but it returned anerrortriggerboot = document.getElementById("play");triggerboot.style = "display: none;";bootterm.write("The backend for this post is currentlybroken try reloading");}};request.onerror = function() {// There was a connection error of some sorttriggerboot = document.getElementById("play");triggerboot.style = "display: none;";bootterm.write("The backend for this post is currentlybroken try reloading");};request.send();