1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

dhcp lease time

Discussion in 'Tomato Firmware' started by _wb_, Aug 1, 2014.

  1. _wb_

    _wb_ Networkin' Nut Member

    I know there is a static file that is generated at /tmp/var/lib/misc/dnsmasq.leases

    The first column of that file is the lease time in unix epoch but that file is not updated every 2 seconds like the GUI page status-devices.asp.

    I can't read those asp files and I have not been able to find where the lease time is updated and stored or how it keeps track if it.

    Anyone could shed some light on here?
  2. koitsu

    koitsu Network Guru Member

    Get some coffee, a soda, whatever. What you have asked requires that you dig through the TomatoUSB source code. It's open-source so feel free to do this as you need.

    The actual data comes from /var/tmp/dhcp/leases. But that file isn't written by dnsmasq every 2 seconds -- that would be absolutely horrible design and suck horribly bad performance-wise. So how does it all work?

    The way this works through the web GUI is extremely complicated and hard to explain. What many people don't understand is that the stuff that ends up in the GUI is obtained through extremely tedious and nonsensical methodologies, all "silently" or "behind the scenes" through Javascript and a series of C code that's often part of the built-in Tomato webserver (which is why the webserver cannot be easily replaced with something like Apache, nginx, thttpd, etc.). Much of this stuff Jonathan Zarate wrote/hard-coded and very little of it is commented.

    So let's start the reverse-engineering process by starting with the web GUI part and "working backwards".

    First look at the release/src/router/www/status-devices.asp source code. Within that, you will find a couple things to note:

    1. There appears to be some built-in logic within Javascript (client-side) that causes "something" to automatically refresh at a periodic interval, re-fetching data and updating the GUI as that data is received. This is the call to TomatoRefresh() you see around line 144.

    2. There is a magic variable that isn't mentioned anywhere else in that code called dhcpd_lease that is used to get most or all the data shown on the status-devices.asp page.

    The "automatic refresh" stuff refers to something called update.cgi, except that isn't actually a real CGI file (like a file on a filesystem) -- instead, it's a "special name" hard-coded into the main Tomato webserver. You can find references to it within release/src/router/httpd/tomato.c around line 269. What you see there is a gigantic struct of "magical names" (things you can GET/POST/etc., like in a URL) and what actual C function gets called when/if that "magic name" is referred to. For update.cgi, the function is wo_update(), which is at line 1629, and the HTTP request should be POST.

    wo_update() appears to be a very vague and strange function if you aren't familiar with it. It just seems to run some code somehow. But what code? The code it runs is what's passed in by the Javascript client (your web browser) during the TomatoRefresh() call. So going back to that part of the status-devices.asp code:

    var ref = new TomatoRefresh('update.cgi', 'exec=devlist', 0, 'status_devices_refresh');
    The important part here is the exec=devlist part. Guess what that does? It causes the webserver to internally submit a request to itself (similar to a GET request), where its given the name devlist. And guess where that is? Back in release/src/router/httpd/tomato.c, but this time used by the struct that's defined around line 222. Here we see:

            { "devlist",                    asp_devlist                     },
    The way this works is to call the C function asp_devlist(), which does something. We don't know what it does yet, so let's look at it. It's in release/src/router/httpd/devlist.c around line 78.

    This is what does the "heavy lifting". The asp_devlist() function is difficult to comprehend because of what all is going on under the hood.

    The important parts are at lines 209 -- where it sends a SIGUSR2 to the dnsmasq process, which causes it to dump out its leases into /var/tmp/dhcp/leases -- and line 227 -- where it does fopen() on /var/tmp/dhcp/leases and reads the contents, and proceeds to print them out (printing them back to the webserver, thus back to your web browser) to create the dhcpd_lease Javascript array.

    So your question then becomes: "so what makes this happen every 2 seconds?" Your browser does, believe it or not. So to recap, this is the ridiculous way it works:

    - Web browser visits http://router/status-devices.asp
    - Within Javascript, a refresh loop begins to happen (the user has no idea this is going on), where every 2 seconds the browser silently does a POST to http://router/update.cgi with some parameters being passed (the magic one being exec=devlist)
    - The Tomato webserver parses the GET/POST request and sees that it's hitting update.cgi
    - update.cgi causes the Tomato webserver to send an internal request to itself, calling function wo_update()
    - Throughout some roundabout methodologies, the exec=devlist portion is parsed, and determined that it should call the function asp_devlist()
    - asp_devlist()
    proceeds to do many things, but the most important of which is send a SIGUSR2 to the PID of dnsmasq, which causes dnsmasq to write its lease information to /var/tmp/dhcp/leases. (Note: there is some additional wait-state logic there which appears to be creating /var/tmp/dhcp/leases.! (that isn't a typo), which looks like dnsmasq creates that "while" it's writing the main leases file, then removes it when it's finished writing -- it's essentially an idiot's global semaphore/lock, so the TomatoUSB code waits for that file to go away before continuing on, if it exists)
    - The same function begins reading that file, parsing it, and printing back some Javascript that creates the Javascript array dhcpd_lease
    - All that data is fed back into the web browser/client that requested it
    - Once that's been received, the Javascript code (status-devices.asp) updates the fields in the GUI with the new data
    - Rinse lather repeat until you change to another page or close your browser window

    So if you ever want to get this data yourself, through a script running on the router for example, you'll need to do something like this:

    # Note: this will fail horribly if dnmasq is not running, so you might want to do it
    # differently, such as looking for /var/run/dnsmasq.pid first
    /bin/kill -USR2 `pidof dnsmasq`
    # Or whatever you are trying to do.
    /bin/cat /var/tmp/dhcp/leases
    The format of this file is explained in the asp_devlist() function, specifically these lines:

    if (sscanf(buf, "%lu %17s %15s %255s", &expires, mac, ip, hostname) != 4) continue;
    host = js_string((hostname[0] == '*') ? "" : hostname);
    web_printf("%c['%s','%s','%s','%s']", comma,
               (host ? host : ""), ip, mac, ((expires == 0) ? "non-expiring" : reltime(buf, expires)));
    Field 1 = expiration time (unsigned long integer, i.e. unsigned 32-bit), probably UNIX timestamp
    Field 2 = MAC address (17 byte string)
    Field 3 = IP address (15 byte string)
    Field 4 = Hostname (255 byte string)

    I'm blindly assuming the first field is a UNIX timestamp because of the later call to reltime(), which I am assuming does a simple subtraction operation to determine how much time is left on the lease (probably in seconds), e.g. now-expires = numberofsecondsleft.

    If the hostname is an asterisk (*) then there is no hostname known for this client (e.g. the DHCP client did not submit a hostname with its DHCP request)

    An expiration time of 0 means the lease is infinite.

    I'm sure all of this is documented in the dnsmasq source code though, as it's the daemon which creates the file.

    If you want to get this data from a script by doing an HTTP request to the router, it's a lot more complex -- there are a whole series of HTTP cookies that are needed/used, so you can't just blindly use something like curl or wget to get this data. Here's an example (legitimate) HTTP POST submission from my browser and the response from Tomato, including all headers (some XXX'd out due to authentication details; I really don't care about MACs since there's nothing anyone can do with them):

    Request with POST payload:
    POST /update.cgi HTTP/1.1
    Host: router
    User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:28.0) Gecko/20100101 Firefox/28.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Referer: http://router/status-devices.asp
    Content-Length: 41
    Content-Type: text/plain; charset=UTF-8
    Cookie: tomato_menu_status=devices.asp; tomato_status_overview_refresh=3; tomato_menu_bwm=monthly.asp; tomato_bw_rtab=vlan2; tomato_menu_advanced=ctnf.asp; tomato_menu_admin=scripts.asp; tomato_menu_basic=ddns.asp; tomato_menu_tools=survey.asp; tomato_shellcmd=openssl%2520version; tomato_menu_forward=basic.asp; tomato_menu_nas=usb.asp; tomato_status_devices_refresh=3; tomato_menu_ipt=realtime.asp; tomato_status_webmon=0; tomato_menu_vpn=pptp-server.asp; tomato_vpn_pptpd_notes_vis=1; tomato_bw_rcolor=0%2C1; tomato_bw_24tab=vlan2; tomato_bw_24refresh=1; tomato_bw_24color=0%2C1; tomato_tools_survey_refresh=0; tomato_status_overview_wl_0_vis=1; tomato_status_overview_wl_1_vis=1; tomato_basic_static_options_vis=0; tomato_bw_ravg=1
    Authorization: Basic XXX==
    Connection: keep-alive
    Pragma: no-cache
    Cache-Control: no-cache
    HTTP/1.0 200 OK
    Date: Sat, 02 Aug 2014 01:18:28 GMT
    Content-Type: text/javascript
    Cache-Control: no-cache, no-store, must-revalidate, private
    Expires: Thu, 31 Dec 1970 00:00:00 GMT
    Pragma: no-cache
    Connection: close
    arplist = [ ['','90:2B:34:57:E8:93','br0'],['','00:30:48:D2:22:D0','br0'],['x.x.x.x','00:01:5C:62:AC:46','vlan2']];
    wlnoise = [ -89,-99 ];
    dhcpd_static = ''.split('>');
    wldev = [ ['eth1','F8:E0:79:57:F8:B1',-28,1000,1000,149217,0]];
    dhcpd_lease = [];
    For someone using dnsmasq's DHCP server capability, dhcpd_lease should be an array containing the fields I mentioned (though look at the web_printf() line in the code, since that's what you'd get there, not the raw file format data).

    Where should I send a bill for my time? :)

    P.S. -- I have not the slightest clue what /tmp/var/lib/misc/dnsmasq.leases is -- I don't particularly care either, because what happens in the code above is what's applicable. Also just a reminder: /var is a symlink to /tmp/var (/tmp is RAM, remember), so really that file you found is /var/lib/misc/dnsmasq.leases. It may just be some kind of internal data point/storage file for dnsmasq. The actual leases are where I said they are above.
    Last edited: Aug 2, 2014
    Nathan Ellsworth and dc361 like this.

Share This Page