1109998SmarkmThis is intended to be an example of a state-machine driven SSL application. It
2109998Smarkmacts as an SSL tunneler (functioning as either the server or client half,
3109998Smarkmdepending on command-line arguments). *PLEASE* read the comments in tunala.h
4109998Smarkmbefore you treat this stuff as anything more than a curiosity - YOU HAVE BEEN
5109998SmarkmWARNED!! There, that's the draconian bit out of the way ...
6109998Smarkm
7109998Smarkm
8109998SmarkmWhy "tunala"??
9109998Smarkm--------------
10109998Smarkm
11109998SmarkmI thought I asked you to read tunala.h?? :-)
12109998Smarkm
13109998Smarkm
14109998SmarkmShow me
15109998Smarkm-------
16109998Smarkm
17109998SmarkmIf you want to simply see it running, skip to the end and see some example
18109998Smarkmcommand-line arguments to demonstrate with.
19109998Smarkm
20109998Smarkm
21109998SmarkmWhere to look and what to do?
22109998Smarkm-----------------------------
23109998Smarkm
24109998SmarkmThe code is split up roughly coinciding with the detaching of an "abstract" SSL
25109998Smarkmstate machine (which is the purpose of all this) and its surrounding application
26109998Smarkmspecifics. This is primarily to make it possible for me to know when I could cut
27109998Smarkmcorners and when I needed to be rigorous (or at least maintain the pretense as
28109998Smarkmsuch :-).
29109998Smarkm
30109998SmarkmNetwork stuff:
31109998Smarkm
32109998SmarkmBasically, the network part of all this is what is supposed to be abstracted out
33109998Smarkmof the way. The intention is to illustrate one way to stick OpenSSL's mechanisms
34109998Smarkminside a little memory-driven sandbox and operate it like a pure state-machine.
35109998SmarkmSo, the network code is inside both ip.c (general utility functions and gory
36109998SmarkmIPv4 details) and tunala.c itself, which takes care of application specifics
37109998Smarkmlike the main select() loop. The connectivity between the specifics of this
38109998Smarkmapplication (TCP/IP tunneling and the associated network code) and the
39109998Smarkmunderlying abstract SSL state machine stuff is through the use of the "buffer_t"
40109998Smarkmtype, declared in tunala.h and implemented in buffer.c.
41109998Smarkm
42109998SmarkmState machine:
43109998Smarkm
44109998SmarkmWhich leaves us, generally speaking, with the abstract "state machine" code left
45109998Smarkmover and this is sitting inside sm.c, with declarations inside tunala.h. As can
46109998Smarkmbe seen by the definition of the state_machine_t structure and the associated
47109998Smarkmfunctions to manipulate it, there are the 3 OpenSSL "handles" plus 4 buffer_t
48109998Smarkmstructures dealing with IO on both the encrypted and unencrypted sides ("dirty"
49109998Smarkmand "clean" respectively). The "SSL" handle is what facilitates the reading and
50109998Smarkmwriting of the unencrypted (tunneled) data. The two "BIO" handles act as the
51109998Smarkmread and write channels for encrypted tunnel traffic - in other applications
52109998Smarkmthese are often socket BIOs so that the OpenSSL framework operates with the
53109998Smarkmnetwork layer directly. In this example, those two BIOs are memory BIOs
54109998Smarkm(BIO_s_mem()) so that the sending and receiving of the tunnel traffic stays
55109998Smarkmwithin the state-machine, and we can handle where this gets send to (or read
56109998Smarkmfrom) ourselves.
57109998Smarkm
58109998Smarkm
59109998SmarkmWhy?
60109998Smarkm----
61109998Smarkm
62109998SmarkmIf you take a look at the "state_machine_t" section of tunala.h and the code in
63109998Smarkmsm.c, you will notice that nothing related to the concept of 'transport' is
64109998Smarkminvolved. The binding to TCP/IP networking occurs in tunala.c, specifically
65109998Smarkmwithin the "tunala_item_t" structure that associates a state_machine_t object
66109998Smarkmwith 4 file-descriptors. The way to best see where the bridge between the
67109998Smarkmoutside world (TCP/IP reads, writes, select()s, file-descriptors, etc) and the
68109998Smarkmstate machine is, is to examine the "tunala_item_io()" function in tunala.c.
69109998SmarkmThis is currently around lines 641-732 but of course could be subject to change.
70109998Smarkm
71109998Smarkm
72109998SmarkmAnd...?
73109998Smarkm-------
74109998Smarkm
75109998SmarkmWell, although that function is around 90 lines of code, it could easily have
76109998Smarkmbeen a lot less only I was trying to address an easily missed "gotcha" (item (2)
77109998Smarkmbelow). The main() code that drives the select/accept/IO loop initialises new
78109998Smarkmtunala_item_t structures when connections arrive, and works out which
79109998Smarkmfile-descriptors go where depending on whether we're an SSL client or server
80109998Smarkm(client --> accepted connection is clean and proxied is dirty, server -->
81109998Smarkmaccepted connection is dirty and proxied is clean). What that tunala_item_io()
82109998Smarkmfunction is attempting to do is 2 things;
83109998Smarkm
84109998Smarkm  (1) Perform all reads and writes on the network directly into the
85109998Smarkm      state_machine_t's buffers (based on a previous select() result), and only
86109998Smarkm      then allow the abstact state_machine_t to "churn()" using those buffers.
87109998Smarkm      This will cause the SSL machine to consume as much input data from the two
88109998Smarkm      "IN" buffers as possible, and generate as much output data into the two
89109998Smarkm      "OUT" buffers as possible. Back up in the main() function, the next main
90109998Smarkm      loop loop will examine these output buffers and select() for writability
91109998Smarkm      on the corresponding sockets if the buffers are non-empty.
92109998Smarkm
93109998Smarkm  (2) Handle the complicated tunneling-specific issue of cascading "close"s.
94109998Smarkm      This is the reason for most of the complexity in the logic - if one side
95109998Smarkm      of the tunnel is closed, you can't simply close the other side and throw
96109998Smarkm      away the whole thing - (a) there may still be outgoing data on the other
97109998Smarkm      side of the tunnel that hasn't been sent yet, (b) the close (or things
98109998Smarkm      happening during the close) may cause more data to be generated that needs
99109998Smarkm      sending on the other side. Of course, this logic is complicated yet futher
100109998Smarkm      by the fact that it's different depending on which side closes first :-)
101109998Smarkm      state_machine_close_clean() will indicate to the state machine that the
102109998Smarkm      unencrypted side of the tunnel has closed, so any existing outgoing data
103109998Smarkm      needs to be flushed, and the SSL stream needs to be closed down using the
104109998Smarkm      appropriate shutdown sequence. state_machine_close_dirty() is simpler
105109998Smarkm      because it indicates that the SSL stream has been disconnected, so all
106109998Smarkm      that remains before closing the other side is to flush out anything that
107109998Smarkm      remains and wait for it to all be sent.
108109998Smarkm
109109998SmarkmAnyway, with those things in mind, the code should be a little easier to follow
110109998Smarkmin terms of "what is *this* bit supposed to achieve??!!".
111109998Smarkm
112109998Smarkm
113109998SmarkmHow might this help?
114109998Smarkm--------------------
115109998Smarkm
116109998SmarkmWell, the reason I wrote this is that there seemed to be rather a flood of
117109998Smarkmquestions of late on the openssl-dev and openssl-users lists about getting this
118109998Smarkmwhole IO logic thing sorted out, particularly by those who were trying to either
119109998Smarkmuse non-blocking IO, or wanted SSL in an environment where "something else" was
120109998Smarkmhandling the network already and they needed to operate in memory only. This
121109998Smarkmcode is loosely based on some other stuff I've been working on, although that
122109998Smarkmstuff is far more complete, far more dependant on a whole slew of other
123109998Smarkmnetwork/framework code I don't want to incorporate here, and far harder to look
124109998Smarkmat for 5 minutes and follow where everything is going. I will be trying over
125109998Smarkmtime to suck in a few things from that into this demo in the hopes it might be
126109998Smarkmmore useful, and maybe to even make this demo usable as a utility of its own.
127109998SmarkmPossible things include:
128109998Smarkm
129109998Smarkm  * controlling multiple processes/threads - this can be used to combat
130109998Smarkm    latencies and get passed file-descriptor limits on some systems, and it uses
131109998Smarkm    a "controller" process/thread that maintains IPC links with the
132109998Smarkm    processes/threads doing the real work.
133109998Smarkm
134109998Smarkm  * cert verification rules - having some say over which certs get in or out :-)
135109998Smarkm
136109998Smarkm  * control over SSL protocols and cipher suites
137109998Smarkm
138109998Smarkm  * A few other things you can already do in s_client and s_server :-)
139109998Smarkm
140109998Smarkm  * Support (and control over) session resuming, particularly when functioning
141109998Smarkm    as an SSL client.
142109998Smarkm
143109998SmarkmIf you have a particular environment where this model might work to let you "do
144109998SmarkmSSL" without having OpenSSL be aware of the transport, then you should find you
145109998Smarkmcould use the state_machine_t structure (or your own variant thereof) and hook
146109998Smarkmit up to your transport stuff in much the way tunala.c matches it up with those
147109998Smarkm4 file-descriptors. The state_machine_churn(), state_machine_close_clean(), and
148109998Smarkmstate_machine_close_dirty() functions are the main things to understand - after
149109998Smarkmthat's done, you just have to ensure you're feeding and bleeding the 4
150109998Smarkmstate_machine buffers in a logical fashion. This state_machine loop handles not
151109998Smarkmonly handshakes and normal streaming, but also renegotiates - there's no special
152109998Smarkmhandling required beyond keeping an eye on those 4 buffers and keeping them in
153109998Smarkmsync with your outer "loop" logic. Ie. if one of the OUT buffers is not empty,
154109998Smarkmyou need to find an opportunity to try and forward its data on. If one of the IN
155109998Smarkmbuffers is not full, you should keep an eye out for data arriving that should be
156109998Smarkmplaced there.
157109998Smarkm
158109998SmarkmThis approach could hopefully also allow you to run the SSL protocol in very
159109998Smarkmdifferent environments. As an example, you could support encrypted event-driven
160109998SmarkmIPC where threads/processes pass messages to each other inside an SSL layer;
161109998Smarkmeach IPC-message's payload would be in fact the "dirty" content, and the "clean"
162109998Smarkmpayload coming out of the tunnel at each end would be the real intended message.
163109998SmarkmLikewise, this could *easily* be made to work across unix domain sockets, or
164109998Smarkmeven entirely different network/comms protocols.
165109998Smarkm
166109998SmarkmThis is also a quick and easy way to do VPN if you (and the remote network's
167109998Smarkmgateway) support virtual network devices that are encapsulted in a single
168109998Smarkmnetwork connection, perhaps PPP going through an SSL tunnel?
169109998Smarkm
170109998Smarkm
171109998SmarkmSuggestions
172109998Smarkm-----------
173109998Smarkm
174109998SmarkmPlease let me know if you find this useful, or if there's anything wrong or
175109998Smarkmsimply too confusing about it. Patches are also welcome, but please attach a
176109998Smarkmdescription of what it changes and why, and "diff -urN" format is preferred.
177109998SmarkmMail to geoff@openssl.org should do the trick.
178109998Smarkm
179109998Smarkm
180109998SmarkmExample
181109998Smarkm-------
182109998Smarkm
183109998SmarkmHere is an example of how to use "tunala" ...
184109998Smarkm
185109998SmarkmFirst, it's assumed that OpenSSL has already built, and that you are building
186109998Smarkminside the ./demos/tunala/ directory. If not - please correct the paths and
187109998Smarkmflags inside the Makefile. Likewise, if you want to tweak the building, it's
188109998Smarkmbest to try and do so in the makefile (eg. removing the debug flags and adding
189109998Smarkmoptimisation flags).
190109998Smarkm
191109998SmarkmSecondly, this code has mostly only been tested on Linux. However, some
192109998Smarkmautoconf/etc support has been added and the code has been compiled on openbsd
193109998Smarkmand solaris using that.
194109998Smarkm
195109998SmarkmThirdly, if you are Win32, you probably need to do some *major* rewriting of
196109998Smarkmip.c to stand a hope in hell. Good luck, and please mail me the diff if you do
197109998Smarkmthis, otherwise I will take a look at another time. It can certainly be done,
198109998Smarkmbut it's very non-POSIXy.
199109998Smarkm
200109998SmarkmSee the INSTALL document for details on building.
201109998Smarkm
202109998SmarkmNow, if you don't have an executable "tunala" compiled, go back to "First,...".
203109998SmarkmRinse and repeat.
204109998Smarkm
205109998SmarkmInside one console, try typing;
206109998Smarkm
207109998Smarkm(i)  ./tunala -listen localhost:8080 -proxy localhost:8081 -cacert CA.pem \
208109998Smarkm              -cert A-client.pem -out_totals -v_peer -v_strict
209109998Smarkm
210109998SmarkmIn another console, type;
211109998Smarkm
212109998Smarkm(ii) ./tunala -listen localhost:8081 -proxy localhost:23 -cacert CA.pem \
213109998Smarkm              -cert A-server.pem -server 1 -out_totals -v_peer -v_strict
214109998Smarkm
215109998SmarkmNow if you open another console and "telnet localhost 8080", you should be
216109998Smarkmtunneled through to the telnet service on your local machine (if it's running -
217109998Smarkmyou could change it to port "22" and tunnel ssh instead if you so desired). When
218109998Smarkmyou logout of the telnet session, the tunnel should cleanly shutdown and show
219109998Smarkmyou some traffic stats in both consoles. Feel free to experiment. :-)
220109998Smarkm
221109998SmarkmNotes:
222109998Smarkm
223109998Smarkm - the format for the "-listen" argument can skip the host part (eg. "-listen
224109998Smarkm   8080" is fine). If you do, the listening socket will listen on all interfaces
225109998Smarkm   so you can connect from other machines for example. Using the "localhost"
226109998Smarkm   form listens only on 127.0.0.1 so you can only connect locally (unless, of
227109998Smarkm   course, you've set up weird stuff with your networking in which case probably
228109998Smarkm   none of the above applies).
229109998Smarkm
230109998Smarkm - ./tunala -? gives you a list of other command-line options, but tunala.c is
231109998Smarkm   also a good place to look :-)
232109998Smarkm
233109998Smarkm
234