LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - Networking
User Name
Password
Linux - Networking This forum is for any issue related to networks or networking.
Routing, network cards, OSI, etc. Anything is fair game.

Notices


Reply
  Search this Thread
Old 05-13-2003, 07:58 PM   #1
Malibyte
Member
 
Registered: Dec 2001
Location: California, USA
Distribution: Ubuntu, Debian
Posts: 128

Rep: Reputation: 15
Parsing dhclient.leases


Hey all -

I need to parse the individual fields in /var/lib/dhcp/dhclient.leases to allow my firewall script to work with dhclient (RedHat 8.x/9.0). I'd invite any help on a parsing routine...I'm not a sed/awk expert; I've managed to parse a couple of fields, but the trick is that the file has to be read from the latest (last) line backwards (to obtain only the last instance of each field - there are often multiple leases in the history) to get the latest lease info. Anyone have a little script they wouldn't mind sharing, or do I need to re-invent the wheel....? I need to get the lease info from this file and not from the /etc/network-scripts/... files. Thanks.

Much appreciated....Bob
 
Old 04-03-2004, 07:42 AM   #2
travishein
LQ Newbie
 
Registered: Apr 2004
Location: Canada
Posts: 11

Rep: Reputation: 0
Hey, I recently also needed to parse the dhclient.leases file.
I needed a quick and dirty solution to extract the smtp-server option for setting up my smtproutes file for qmail.
Although this is an old thread, here is what I came up in part of an evening. I use the output of this program in a shell script. You invoke it with a single command line parameter for the name of the option to be read from the current dhcp lease.

--- cut ---
Code:
#include <string>
#include <map>
#include <list>
#include <fstream>
#include <iostream>

using namespace std;
// a list of m


// all the stuff that can occur in a lease
struct dhcp_lease {
    // the name / value pairs for options for this lease
    string interface;
    string address;
    map<string, list<string> > options;
    string renew;
    string rebind;
    string expire;
    dhcp_lease() {}
};

// all the leases from the dhclient.leases file
list<dhcp_lease> leases;

// get an option from the dhclient.leases file
int main (int argc, char** argv) {

// require command line parameter for the name of the option to get
    string getOption = "";

    int order = 0;
    --argc; ++argv;
    for (int i = 0; i < argc; i++) {
        if (argv[i][0] == '-') {

        }
        else {
            switch (++order) {
                case 1:
                    getOption = argv[i];
                    break;


            }
        }
    }

    if (getOption == "") {
        cout << "Please specify an option to query for.\n";
        return 0;
    }

// try to open the dhclient.leases file
    ifstream leasesFile;

    leasesFile.open("/var/state/dhcp/dhclient.leases");

    if (!leasesFile) {
        cout << "Cannot open dhcp leases file.\n";
        return 1;
    }
//    cout << "opened dhclient leases file\n";

    //string rawLease; // the stuff between the { and }

    string line = "";
    char c;
    bool inLease = false;
    int leaseCount = 0;
    dhcp_lease currentLease;

    while (leasesFile) {
        leasesFile.get(c);
        switch (c) {
            case '{':
                inLease = true;
//                cout << "starting lease " << leaseCount + 1 << "\n";
                break;

            case '}':
                inLease = false;
//                cout << "finished reading lease " << ++leaseCount << "\n";
                leases.push_front(currentLease);
                currentLease = dhcp_lease();
                break;
            case '\n':
                if (inLease) {
                    if (line.length() > 0) {
                        int startOfs = line.find_first_not_of(" \t");
                        int endOfs = line.find_last_of(";");
                        // trim extra stuff off from line
                        line = line.substr(startOfs, endOfs-startOfs);

                        if (line.find("option") != string::npos ) {
                            // drop the option keyword
                            line = line.substr(7);
                            size_t ofs = line.find_first_of(" ");

                            // extract the option name
                            string key = line.substr(0,  ofs);

                            // extract the comma delimited values

                            list<string> values;
                            line = line.substr(ofs+1);
                            while (line.length()) {
                                ofs = line.find_first_of(',');
                                string valStr;
                                if (ofs == string::npos) {
                                    //ofs = line.length();
                                    ofs = 0;
                                    valStr = line;
                                    line = "";
                                }
                                else {
                                    valStr = line.substr(0, ofs);
                                    line = line.substr(ofs+1);
                                }
                                values.push_back(valStr);
                            } // while
                            pair<string, list<string> > p(key,values);
                            currentLease.options.insert(p);
                        }
                        else if (line.find("renew") != string::npos) {
                            //cout << "lease renew: \n";
                        }
                        else if (line.find("rebind") != string::npos) {
                            //cout << "lease rebind: \n";
                        }
                        else if (line.find("expire") != string::npos) {
                            //cout << "lease expire: \n";
                        }

                        // reset the line string
                        line = "";
                    }
                }
                break;

            default:
            if (inLease) {
                line += c;
            }
        }
    } // while lease file has values

//    cout << "read " << leases.size() << " leases.\n";

// retreive the last lease in the file (tokenize entire contents by { and } delimiters
    if (leases.size() > 0) {
        list<dhcp_lease>::iterator itr = leases.begin();
        dhcp_lease lease = *itr;

//        cout << "our lease has " << lease.options.size() << " options.\n";

        // get the requested option
        map<string, list<string> >::iterator valItr = lease.options.find(getOption);
        if (valItr != lease.options.end()) {
            pair<string, list<string> > p = *valItr;
            list<string> valList = p.second;
            list<string>::iterator itmItr = valList.begin();
            cout << *itmItr << endl;
        }
        else {
            // this option could not be found in the lease
            return 2;
        }
    }

}
--- cut ---

Last edited by travishein; 09-06-2009 at 08:52 PM. Reason: Formatted to add code tags..
 
Old 04-03-2004, 03:23 PM   #3
Malibyte
Member
 
Registered: Dec 2001
Location: California, USA
Distribution: Ubuntu, Debian
Posts: 128

Original Poster
Rep: Reputation: 15
Thanks. Appreciate the post!
 
Old 05-05-2005, 04:40 PM   #4
sparkix
LQ Newbie
 
Registered: May 2005
Location: Toronto
Distribution: FC3/Knoppix/DSLinux
Posts: 9

Rep: Reputation: 0
How to read the last few lines

I recently updated my firewall to do exactly what you are trying. Here is what I came up with:

Layout of my dhclient leases (/var/lib/dhcp/dhclient-eth0.leases) file:

interface "eth0";
fixed-address aaa.bbb.ccc.ddd;
filename "rogers01.cfg";
option subnet-mask 255.255.254.0;
option dhcp-lease-time 64800;
etc... (17 lines in total for each lease)

This appears at the top of my firewall script.

FILE="/var/lib/dhcp/dhclient-eth0.leases"

IPADDR=$(tail -n 16 $FILE | grep '-m' '1' 'fixed-address' | awk '{print $2}' | sed -e 's/;//')
NAMESERVER=$(tail -n 16 $FILE | grep '-m' '1' 'domain-name-servers' | awk '{print $3}' | sed -e 's/,.*//')
DHCP_SERVER=$(tail -n 16 $FILE | grep '-m' '1' 'dhcp-server-identifier' | awk '{print $3}' | sed -e 's/;//')

This grabs the values right from the lease file. Then my exit hooks script runs the firewall each time new information arrives.

I had to investigate this because occasionally my lease file has binary data in it. I don't know why but it always shows up just before a lease is written. This script is not tripped up by this.

However, if anyone has had this happen I would still like to fix the lease corruption issue.
(i.e. the lease has a line like this ---> "^@^@^@^@^@^@^@^@^@^@^@^@^@lease {")

Thanks
 
Old 07-13-2009, 05:28 PM   #5
sparkix
LQ Newbie
 
Registered: May 2005
Location: Toronto
Distribution: FC3/Knoppix/DSLinux
Posts: 9

Rep: Reputation: 0
I know this is a deader-than-dead thread, but... here is an update.

I recently had my firewall that automatically parses the leases file fail. For some reason, a recent lease was written that had an extra line in the lease that caused "tail -n 16" to not read enough lines. So I made the number of lines that it reads a variable and set it to 20. This worked great for about a month. Then I got a bit of binary crap again and the script puked. It seems the sweet spot is to read the last 17 lines of the file. I'll check back in 4 years to let you know how the script is fairing
 
Old 09-06-2009, 09:49 PM   #6
travishein
LQ Newbie
 
Registered: Apr 2004
Location: Canada
Posts: 11

Rep: Reputation: 0
Wow, that takes me back. I look at that old solution I had and can't believe what I was thinking. I'm sure at the time it made sense. I think I was trying to find just one option or something for setting up a SMTP server to know where to route messages to with that DHCP option for smtp-servers.

Though the brute force parse the file still seems to work for me, it starts from the start of the file and parses each lease, and just works with the last one (i guess someday I had planned to do something when the lease changed)

But for now when I see how your modern example would want to invoke a dhclient hooks script that would want to consume several environment variables we would read from the dhclient leases, I now modified my old C++ tool/hack to handle multiple options on the parameter, and even to specify an alternate location of the dhclient leases file.

So now, if you save the snipped below as dhclient_lease_parse.cpp

Code:
/**
* dhclient lease parser by Travis Hein
* licensed under Apache License, Version 2.0
*/
#include <string>
#include <map>
#include <list>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <string.h>

using namespace std;

string DEFAULT_LEASE_FILE="/var/state/dhcp/dhclient.leases";

// all the stuff that can occur in a lease
struct dhcp_lease {
    // the name / value pairs for options for this lease
    string interface;
    string address;
    map<string, list<string> > options;
    string renew;
    string rebind;
    string expire;
    dhcp_lease() {}
};

// see: http://www.hardforum.com/archive/index.php/t-979477.html
inline string replaceAll( const string& s, const string& f, const string& r ) {
	if ( s.empty() || f.empty() || f == r || s.find(f) == string::npos ) {
		return s;
	}
	ostringstream build_it;
	size_t i = 0;
	for ( size_t pos; ( pos = s.find( f, i ) ) != string::npos; ) {
		build_it.write( &s[i], pos - i );
		build_it << r;
		i = pos + f.size();
	}
	if ( i != s.size() ) {
		build_it.write( &s[i], s.size() - i );
	}
	return build_it.str();
}

// see: http://www.codepedia.com/1/CppToUpperCase
std::string StringToUpper(std::string myString)
{
  const int length = myString.length();
  for(int i=0; i!=length ; ++i)
  {
    myString[i] = std::toupper(myString[i]);
  }
  return myString;
}

/**
* get an option(s) from the last entry in the dhclient.leases file.
* When no parameters specified, defaults to fixed-address, domain-name-servers routers
*/
int main (int argc, char** argv) {
	
	// because we don't know for sure how far up the "lease {" token will be.
	// we just parse every lease in the file; the assumption being the leases file will be small.
	list<dhcp_lease> leases;	
	
	// the options to extract and set as environment variables.
	list<string> options;
	string leasesFileName = DEFAULT_LEASE_FILE; 
	int order = 0;
	--argc; ++argv;
	for (int i = 0; i < argc; i++) {
		if (argv[i][0] == '-') {
			string sw = argv[i];
			
			// allow a different leases file to be specified with -f <filename>
			if (sw == "-f") {
				leasesFileName = argv[++i];		
			}
		}
		else {
			switch (++order) {
				string option = argv[i]; 
				options.push_back(option);
				break;
			}
		}
	} // for (parameters)

	if (options.size()  == 0) {
		// use default of these options to fetch 
		options.push_back("fixed-address");
		options.push_back("domain-name-servers");
		options.push_back("routers");
	}

		// try to open the dhclient.leases file
    ifstream leasesFile;
		
    leasesFile.open(leasesFileName.c_str());

    if (!leasesFile) {
			cout << "Cannot open dhcp leases file: " << leasesFileName << "\n";
      return 1;
    }
		
    string line = "";
    char c;
    bool inLease = false;
    int leaseCount = 0;
    dhcp_lease currentLease;

    while (leasesFile) {
			leasesFile.get(c);
			switch (c) {
					case '{':
							inLease = true;
							break;

					case '}':
							inLease = false;
							leases.push_front(currentLease);
							currentLease = dhcp_lease();
							break;
							
					case '\n':
						if (inLease) {
							if (line.length() > 0) {
								int startOfs = line.find_first_not_of(" \t");
								int endOfs = line.find_last_of(";");
								// trim extra stuff off from line
								line = line.substr(startOfs, endOfs-startOfs);

								if (line.find("option") != string::npos ) {
									// drop the option keyword
									line = line.substr(7);
									size_t ofs = line.find_first_of(" ");

									// extract the option name
									string key = line.substr(0,  ofs);

									// extract the comma delimited values

									list<string> values;
									line = line.substr(ofs+1);
									while (line.length()) {
										ofs = line.find_first_of(',');
										string valStr;
										if (ofs == string::npos) {
											ofs = 0;
											valStr = line;
											line = "";
										}
										else {
											valStr = line.substr(0, ofs);
											line = line.substr(ofs+1);
										}
										values.push_back(valStr);
									} // while
									pair<string, list<string> > p(key,values);
									currentLease.options.insert(p);
								}
								else if (line.find("fixed-address") != string::npos) {
									// the fixed-address isn't really an option, but its something we usually want to read.
									// so for convenience later on, we stuff this in as an option.
									string valstr = line.substr(line.find("fixed-address") + 13);
									// trim extra stuff off from line
									int startOfs = valstr.find_first_not_of(" \t");
									int endOfs = valstr.find_last_of(";");
									valstr = valstr.substr(startOfs, endOfs-startOfs);
									list<string> values;
									values.push_back(valstr);									
									pair<string, list<string> > p("fixed-address", values);
									currentLease.options.insert(p);
								}
								else if (line.find("renew") != string::npos) {
									// just grab the stuff after token and save it as string into struct
									currentLease.renew = line.substr(line.find("renew") + 5);
								}
								else if (line.find("rebind") != string::npos) {
									currentLease.rebind = line.substr(line.find("rebind") + 6);
								}
								else if (line.find("expire") != string::npos) {
									currentLease.expire = line.substr(line.find("expire") + 6);
								}

								// reset the line string
								line = "";
						}
					} // if in a lease
					break;

				default:
				if (inLease) {
						line += c;
				}
			}
    } // while lease file has values

		// retreive the last lease in the file (tokenize entire contents by { and } delimiters
		// (which is now the first one in our list of parsed leases)
    if (leases.size() > 0) {			
			list<dhcp_lease>::iterator itr = leases.begin();
			dhcp_lease lease = *itr;

			// get the requested option(s)
			
			list<string>::iterator optionsIterator = options.begin();
			while (optionsIterator != options.end() ) {
				string getOption = *optionsIterator;
				
				map<string, list<string> >::iterator valItr = lease.options.find(getOption);
				
				// the usual unix delimiter is ":". Usually we only have one value though.
				if (valItr != lease.options.end()) {
						pair<string, list<string> > p = *valItr;
						list<string> valList = p.second;
						list<string>::iterator itmItr = valList.begin();

						// stuff this thing we were asked to get as an anvironment variable.
						// transform the option name to env variable name
						//  - convert all "-" to "_"
						//  - uppercase the string
						
						string envName = replaceAll(getOption, "-", "_"); 
						envName = StringToUpper(envName);
						string envStr = envName + "=" + *itmItr;
						cout << envStr << ";" << endl;

// setting environment variables only works if we invoke child process ourselves
//						char tmpstr[255] = "\0";
//						strncpy(tmpstr, envStr.c_str(),255);
//						putenv(tmpstr);

				}
				else {
					// this option could not be found in the lease
					
				}
				optionsIterator++;
			} // each option
	}
}
then

g++ -o dhclient_lease_parse dhclient_lease_parse.cpp

then place this into the path (such as copy dhclient_lease_parse to /usr/local/bin/

for example, running dhclient_lease_parse with no arguments spits out

./dhclient_lease_parse -f /var/lib/dhcp3/dhclient-eth0.lease
FIXED_ADDRESS=192.168.1.100;
DOMAIN_NAME_SERVERS=192.168.1.1;
ROUTERS=192.168.1.1;

So this can be eval'd by a shell script,

and the invoking the script now becomes:

Code:
#!/bin/sh

# uses the dhclient_lease_parse
# build that with 
# g++ -o dhclient_lease_parse dhclient_lease_parse.cpp
# and copy the dhclient_lease_parse to /usr/local/bin or put on the path

PARSER="/usr/local/bin/dhclient_lease_parse";
#PARSER="./dhclient_lease_parse";

FILE="/var/lib/dhcp/dhclient-eth0.leases";
#FILE="/var/lib/dhcp3/dhclient-eth0.lease";

# evaluate the output of the dhclint_lease_parser, which is VAR=aValue;  formatted things.

eval "$(${PARSER} -f ${FILE} fixed-address domain-name-servers dhcp-server-identifier)"

# the following variables are set from the eval

echo "IPADDR     : ${FIXED_ADDRESS}";
echo "NAMESERVER : ${DOMAIN_NAME_SERVERS}";
echo "DHCP_SERVER: ${DHCP_SERVER_IDENTIFIER}";
Hope that's a bit more helpful than the original posted utility.
 
Old 08-29-2010, 03:44 PM   #7
TJ99
LQ Newbie
 
Registered: Jan 2009
Posts: 2

Rep: Reputation: 0
Minor correction on the argument parsing...

Quote:
Originally Posted by travishein View Post
Wow, that takes me back. I look at that old solution I had and can't believe what I was thinking. I'm sure at the time it made sense. I think I was trying to find just one option or something for setting up a SMTP server to know where to route messages to with that DHCP option for smtp-servers.

.
Thanks very much for the code!

However it didn't parse the command line arguments as I thought it would, so I changed the arg parsing to the following;

for (int i = 0; i < argc; i++) {
if (argv[i][0] == '-') {
string sw = argv[i];

// allow a different leases file to be
// specified with -f <filename>
if (sw == "-f") {
leasesFileName = argv[++i];
}
}
else {
string option = argv[i];
options.push_back(option);
}
} // for (parameters)

Basically I got rid of the switch() statement, so now you can also do:

dhclient_lease_parse domain-name dhcp-message-type -f /var/lib/dhclient/dhclient-eth0.leases

and it will just extract the ones you want, which is what I believe the intent of the code originally was.

Cheers!
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
DHCPD no free leases HiOctane21 Linux - Networking 6 10-02-2013 11:21 PM
DHCPD Has No free leases? FishGills Linux - Networking 7 01-24-2009 06:49 PM
find out all free leases from dhcpd saavik Linux - Networking 2 07-27-2005 02:03 AM
dhcp not writing leases oberon-ken-obi Linux - Newbie 8 04-14-2005 01:44 PM
dnsmasq cleaning up leases file TheRealDeal Linux - Networking 0 03-02-2004 11:12 PM

LinuxQuestions.org > Forums > Linux Forums > Linux - Networking

All times are GMT -5. The time now is 03:25 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration