The 'new' architecture implemented after the code was split to
ohybridproxy+zonestitcher works as follows: There are essentially 5
components to a DNS 'toy':

- main (that deals with setting up the other parts + starts event loop)

- socket abstraction (socket.c) - responsible for dealing with (inbound)
  UDP/TCP connections and the DNS packets there.

- generic request abstraction (io.c) - it handles 'requests', which
  compromise of one or more queries, that are started and stopped.

- cache of results / pending requests (cache.c) - storage of rr lists of
  what we have found from somewhere

- backend that actually handles query, and possibly starts sub-queries as well

Actual call flow:

- main calls backend-specific setup code

- main calls io_run(), and then (socket.c) creates sockets and starts uloop

- when new request shows up, (~per-session/message) request structure is
  added and it's regiestered with the cache (cache_register_request). That
  triggers either start of request (eventual io_req_start), wait for other
  request, or immediate reply if result is cached (io_send_reply)

.. assuming we started new request ..

- (io.c) io_req_start calls io_query_start, and then b_query_start (which
  may fail immediately, or not)

.. backend runs the queries, now and then calling io_query_stop to indicate
a single query has completed, and storing results in the rrlist of cache
entry ..

- (io.c) when last io_query_stop is received, cache_entry_completed is
  called and that results in io_send_reply to the pending request(s); it
  leads to clean-up path in socket.c even if there is nothing to be said. 

