1This is intended to be an example of a state-machine driven SSL application. It 2acts as an SSL tunneler (functioning as either the server or client half, 3depending on command-line arguments). *PLEASE* read the comments in tunala.h 4before you treat this stuff as anything more than a curiosity - YOU HAVE BEEN 5WARNED!! There, that's the draconian bit out of the way ... 6 7 8Why "tunala"?? 9-------------- 10 11I thought I asked you to read tunala.h?? :-) 12 13 14Show me 15------- 16 17If you want to simply see it running, skip to the end and see some example 18command-line arguments to demonstrate with. 19 20 21Where to look and what to do? 22----------------------------- 23 24The code is split up roughly coinciding with the detaching of an "abstract" SSL 25state machine (which is the purpose of all this) and its surrounding application 26specifics. This is primarily to make it possible for me to know when I could cut 27corners and when I needed to be rigorous (or at least maintain the pretense as 28such :-). 29 30Network stuff: 31 32Basically, the network part of all this is what is supposed to be abstracted out 33of the way. The intention is to illustrate one way to stick OpenSSL's mechanisms 34inside a little memory-driven sandbox and operate it like a pure state-machine. 35So, the network code is inside both ip.c (general utility functions and gory 36IPv4 details) and tunala.c itself, which takes care of application specifics 37like the main select() loop. The connectivity between the specifics of this 38application (TCP/IP tunneling and the associated network code) and the 39underlying abstract SSL state machine stuff is through the use of the "buffer_t" 40type, declared in tunala.h and implemented in buffer.c. 41 42State machine: 43 44Which leaves us, generally speaking, with the abstract "state machine" code left 45over and this is sitting inside sm.c, with declarations inside tunala.h. As can 46be seen by the definition of the state_machine_t structure and the associated 47functions to manipulate it, there are the 3 OpenSSL "handles" plus 4 buffer_t 48structures dealing with IO on both the encrypted and unencrypted sides ("dirty" 49and "clean" respectively). The "SSL" handle is what facilitates the reading and 50writing of the unencrypted (tunneled) data. The two "BIO" handles act as the 51read and write channels for encrypted tunnel traffic - in other applications 52these are often socket BIOs so that the OpenSSL framework operates with the 53network layer directly. In this example, those two BIOs are memory BIOs 54(BIO_s_mem()) so that the sending and receiving of the tunnel traffic stays 55within the state-machine, and we can handle where this gets send to (or read 56from) ourselves. 57 58 59Why? 60---- 61 62If you take a look at the "state_machine_t" section of tunala.h and the code in 63sm.c, you will notice that nothing related to the concept of 'transport' is 64involved. The binding to TCP/IP networking occurs in tunala.c, specifically 65within the "tunala_item_t" structure that associates a state_machine_t object 66with 4 file-descriptors. The way to best see where the bridge between the 67outside world (TCP/IP reads, writes, select()s, file-descriptors, etc) and the 68state machine is, is to examine the "tunala_item_io()" function in tunala.c. 69This is currently around lines 641-732 but of course could be subject to change. 70 71 72And...? 73------- 74 75Well, although that function is around 90 lines of code, it could easily have 76been a lot less only I was trying to address an easily missed "gotcha" (item (2) 77below). The main() code that drives the select/accept/IO loop initialises new 78tunala_item_t structures when connections arrive, and works out which 79file-descriptors go where depending on whether we're an SSL client or server 80(client --> accepted connection is clean and proxied is dirty, server --> 81accepted connection is dirty and proxied is clean). What that tunala_item_io() 82function is attempting to do is 2 things; 83 84 (1) Perform all reads and writes on the network directly into the 85 state_machine_t's buffers (based on a previous select() result), and only 86 then allow the abstact state_machine_t to "churn()" using those buffers. 87 This will cause the SSL machine to consume as much input data from the two 88 "IN" buffers as possible, and generate as much output data into the two 89 "OUT" buffers as possible. Back up in the main() function, the next main 90 loop loop will examine these output buffers and select() for writability 91 on the corresponding sockets if the buffers are non-empty. 92 93 (2) Handle the complicated tunneling-specific issue of cascading "close"s. 94 This is the reason for most of the complexity in the logic - if one side 95 of the tunnel is closed, you can't simply close the other side and throw 96 away the whole thing - (a) there may still be outgoing data on the other 97 side of the tunnel that hasn't been sent yet, (b) the close (or things 98 happening during the close) may cause more data to be generated that needs 99 sending on the other side. Of course, this logic is complicated yet futher 100 by the fact that it's different depending on which side closes first :-) 101 state_machine_close_clean() will indicate to the state machine that the 102 unencrypted side of the tunnel has closed, so any existing outgoing data 103 needs to be flushed, and the SSL stream needs to be closed down using the 104 appropriate shutdown sequence. state_machine_close_dirty() is simpler 105 because it indicates that the SSL stream has been disconnected, so all 106 that remains before closing the other side is to flush out anything that 107 remains and wait for it to all be sent. 108 109Anyway, with those things in mind, the code should be a little easier to follow 110in terms of "what is *this* bit supposed to achieve??!!". 111 112 113How might this help? 114-------------------- 115 116Well, the reason I wrote this is that there seemed to be rather a flood of 117questions of late on the openssl-dev and openssl-users lists about getting this 118whole IO logic thing sorted out, particularly by those who were trying to either 119use non-blocking IO, or wanted SSL in an environment where "something else" was 120handling the network already and they needed to operate in memory only. This 121code is loosely based on some other stuff I've been working on, although that 122stuff is far more complete, far more dependant on a whole slew of other 123network/framework code I don't want to incorporate here, and far harder to look 124at for 5 minutes and follow where everything is going. I will be trying over 125time to suck in a few things from that into this demo in the hopes it might be 126more useful, and maybe to even make this demo usable as a utility of its own. 127Possible things include: 128 129 * controlling multiple processes/threads - this can be used to combat 130 latencies and get passed file-descriptor limits on some systems, and it uses 131 a "controller" process/thread that maintains IPC links with the 132 processes/threads doing the real work. 133 134 * cert verification rules - having some say over which certs get in or out :-) 135 136 * control over SSL protocols and cipher suites 137 138 * A few other things you can already do in s_client and s_server :-) 139 140 * Support (and control over) session resuming, particularly when functioning 141 as an SSL client. 142 143If you have a particular environment where this model might work to let you "do 144SSL" without having OpenSSL be aware of the transport, then you should find you 145could use the state_machine_t structure (or your own variant thereof) and hook 146it up to your transport stuff in much the way tunala.c matches it up with those 1474 file-descriptors. The state_machine_churn(), state_machine_close_clean(), and 148state_machine_close_dirty() functions are the main things to understand - after 149that's done, you just have to ensure you're feeding and bleeding the 4 150state_machine buffers in a logical fashion. This state_machine loop handles not 151only handshakes and normal streaming, but also renegotiates - there's no special 152handling required beyond keeping an eye on those 4 buffers and keeping them in 153sync with your outer "loop" logic. Ie. if one of the OUT buffers is not empty, 154you need to find an opportunity to try and forward its data on. If one of the IN 155buffers is not full, you should keep an eye out for data arriving that should be 156placed there. 157 158This approach could hopefully also allow you to run the SSL protocol in very 159different environments. As an example, you could support encrypted event-driven 160IPC where threads/processes pass messages to each other inside an SSL layer; 161each IPC-message's payload would be in fact the "dirty" content, and the "clean" 162payload coming out of the tunnel at each end would be the real intended message. 163Likewise, this could *easily* be made to work across unix domain sockets, or 164even entirely different network/comms protocols. 165 166This is also a quick and easy way to do VPN if you (and the remote network's 167gateway) support virtual network devices that are encapsulted in a single 168network connection, perhaps PPP going through an SSL tunnel? 169 170 171Suggestions 172----------- 173 174Please let me know if you find this useful, or if there's anything wrong or 175simply too confusing about it. Patches are also welcome, but please attach a 176description of what it changes and why, and "diff -urN" format is preferred. 177Mail to geoff@openssl.org should do the trick. 178 179 180Example 181------- 182 183Here is an example of how to use "tunala" ... 184 185First, it's assumed that OpenSSL has already built, and that you are building 186inside the ./demos/tunala/ directory. If not - please correct the paths and 187flags inside the Makefile. Likewise, if you want to tweak the building, it's 188best to try and do so in the makefile (eg. removing the debug flags and adding 189optimisation flags). 190 191Secondly, this code has mostly only been tested on Linux. However, some 192autoconf/etc support has been added and the code has been compiled on openbsd 193and solaris using that. 194 195Thirdly, if you are Win32, you probably need to do some *major* rewriting of 196ip.c to stand a hope in hell. Good luck, and please mail me the diff if you do 197this, otherwise I will take a look at another time. It can certainly be done, 198but it's very non-POSIXy. 199 200See the INSTALL document for details on building. 201 202Now, if you don't have an executable "tunala" compiled, go back to "First,...". 203Rinse and repeat. 204 205Inside one console, try typing; 206 207(i) ./tunala -listen localhost:8080 -proxy localhost:8081 -cacert CA.pem \ 208 -cert A-client.pem -out_totals -v_peer -v_strict 209 210In another console, type; 211 212(ii) ./tunala -listen localhost:8081 -proxy localhost:23 -cacert CA.pem \ 213 -cert A-server.pem -server 1 -out_totals -v_peer -v_strict 214 215Now if you open another console and "telnet localhost 8080", you should be 216tunneled through to the telnet service on your local machine (if it's running - 217you could change it to port "22" and tunnel ssh instead if you so desired). When 218you logout of the telnet session, the tunnel should cleanly shutdown and show 219you some traffic stats in both consoles. Feel free to experiment. :-) 220 221Notes: 222 223 - the format for the "-listen" argument can skip the host part (eg. "-listen 224 8080" is fine). If you do, the listening socket will listen on all interfaces 225 so you can connect from other machines for example. Using the "localhost" 226 form listens only on 127.0.0.1 so you can only connect locally (unless, of 227 course, you've set up weird stuff with your networking in which case probably 228 none of the above applies). 229 230 - ./tunala -? gives you a list of other command-line options, but tunala.c is 231 also a good place to look :-) 232 233 234