Dibbler - a portable DHCPv6  1.0.2RC1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
  1. Source code information

This section describes various aspects of Dibbler compilation, usage and internal design.

5.1 Option values and filenames

DHCPv6 is a relatively new protocol and additional options are in a specification phase. It means that until standarisation process is over, they do not have any officially assigned numbers. Once standarization process is over (and RFC document is released), this option gets an official number.

There's pretty good chance that different implementors may choose diffrent values for those not-yet officialy accepted options. To change those values in Dibbler, you have to modify file Misc/DHCPConst.h and recompile server or client. Make sure that you build everything for scratch. Use make clean in Linux, Mac OS X and BSD and Clean up solution in Windows before you start building a new version.

In default build, Dibbler stores all information in the /var/lib/dibbler directory (Linux) or in the working directory (Windows). There are multiple files stored in those directories. However, sometimes there is a need to build Dibbler which uses different directory or filename. To do so, simply edit Misc/Portable.h file and rebuild everything.

5.2 Memory Management using SmartPtr

To effectively fight memory leaks, clever mechanism was introduced. Smart pointers are used to point to all dynamic structures, e.g. messages, options or client informations in server database. Smart pointer will free object by itself, when object is no longer needed. When this is happening? When last smart pointer stops pointing at the object. There is a tradeoff: normal pointers (*) should not be mixed with smart pointers.

Smart pointer implementation in Dibbler works similar as boost::shared_ptr, but it allows downcasting from derived to base type, using (Ptr*) operator.

Smart pointers are implemented as C++ class templates. Template is called SPtr<TYPE>.

Note: It has been renamed. Previous, obsolete name is SmartPtr<TYPE>. Please, do not use it anymore as its definitions was removed.

To quickly explain smart pointers usage, here's short code example:

 1 void foo()  {
 2   SPtr<TIPv6Addr> addr = new TIPv6Addr("ff02::1:2");
 3   SPtr<TIPv6Addr> tmp;
 4   if (!tmp) cout << "Null pointer" << endl;
 5   tmp = addr;
 6   std::cout << addr->getPlain();
 7 }

What's happened in those lines?

  • [1] – Function starts.
  • [2] – New TIPv6Addr object is created. Smart Pointer (SPtr<TIPv6Addr>) is also created to point at this object. Using normal pointer to achive the same goal would look like this: \ TIPv6Addr * addr = new TIPv6Addr("ff02::1:2");
  • [3] – Another pointer is created. It is equivalent of the classical pointer (TIPv6Addr * tmp), except the benefit of having it assigned the default value of NULL.
  • [4] – Simple check if pointer does not point to anything.
  • [5] – Smart pointers can be coppied in a easy way.
  • [6] – Using object pointed by smart pointer is simple
  • [7] – Here magic begins. addr and tmp are local variables, so they are destroyed here. But they are the only smart pointers which access TIPv6Addr object. So they are destroy that object.

Object remain in memory as long as there is at least one smart pointer which points to this object. Smart pointers can be easily derefernced. Just add * before them:

cout << *addr << endl;

SmartPtrs are often used to store various objects in a list. Cool part of this solution is that you can hold objects of various derived classes on one list in a very comfortable manner. There is an additional template defined to create and manipulate such lists. It is called TContainer. There's also useful macro defined to use this without typing too much. Here are two examples how to define list of addresses (both mean exactly the same):

TContainer< SPtr<TIPvAddr> > addrLst;
TContainer< SPtr<TIPv6Addr> > addrLst;

How to use this list? Oh well, another example:

1  TContainer< SPtr<TIPv6Addr> > addrLst;
2  SmartPtr<TIPv6Addr> ptr = ...;
3  SmartPtr<TIPv6Addr> tmp;
4  addrLst.clear();
5  addrLst.append(ptr);
6  addrLst.first();
7  tmp = addrLst.get();
8  cout << "List contains " << addrLst.count() << " elements" << endl;
9  addrLst.first();
10 while (tmp = addrLst.get())
11   cout << *tmp << endl;

And here is description what that code does:

  • [1] – Address list declaration.
  • [2,3] – SmartPtrs declarations. Just to show variable types.
  • [4] – List can be cleared. All pointers will be destroyed. If they were only pointers to point to some objects, those objects will be destroyed, too.
  • [5] – Append object pointed by ptr to the list.
  • [6] – Rewind list to the beginning.
  • [7] – Get next object from the list. If list is empty or last element was already got, NULL is returned.
  • [8] – An easy way to count elements on the list.
  • [9] – Rewind list to the beginning.
  • [10,11] – A cute example how to print all addresses on the list.

5.3 Logging

To log various informations, Log(LOGLEVEL) macros are defined. There are eight levels of logging:

  • Emergency – Used to report system wide emergency. Such conditions could not occur in the DHCPv6 client o server, so this logging level should not be used. Called with:
    Log(Emerg) << "..." << LogEnd
  • Alert – Used to alert an administrator about system wide alerts. This logging level should not be used in DHCPv6. Called with
    Log(Alert) << "..." << LogEnd
  • Critical – Used in situations critical to the application, e.g. application shutdown. Fatal errors should be logged on this level. Called with
    Log(Crit) << "..." << LogEnd
  • Error – Used to report error situations. For example, problems with binding sockets. Called with
    Log(Error) << "..." << LogEnd
  • Warning – Used to report RFC violations, e.g. missing required options, invalid parameters and so on. Called with
    << "..." << LogEnd
  • Notice – Used to report normal operations, e.g. address assignement or informations about received options. Called with
    Log(Notice) << "..." << LogEnd
  • Info – Used to report detailed information. DHCPv6 protocol knowledge might be needed to understand those messages. Called with
    Log(Info) << "..." << LogEnd
  • Debug – Used to report internal informations. Knowledge about Dibbler source code might be needed to understand those messages. Called with
    Log(Debug) << "..." << LogEnd

5.4 Naming convention

To avoid confussion, various prefixes are used in class and variable names. Class types begin with T (e.g. address class would be named TAddr), enumeration types begin with E (e.g. state enumaterion would be names EState). Dibbler is divided into 4 large functional blocks called managers (They are described in the following sections of this document): address maganger, interface manager, Configuration manager, and transmsission manager. Each of them uses different prefix: Addr, Iface, Cfg or Trans. There are also objects shared among them: messages (Msg prefix) and options (Opt prefix). Often there are two derived versions: related to client (Clnt prefix) or related to server (Clnt). Rel prefix is used to denote Relay related classes. Here are examples of some class names:

Also note that class members start with small letters (e.g. bool TOpt::isValid() ) and class members start with capital letters (e.g. bool TOpt::IsValid ).

5.5 Configuration file parsers

Note: Similar approach is used in server, client and relay. In following section when reference to a specific file is needed, client files are used. To find corresponding files related to server and relay, substitute Clnt with Srv or Rel.

Dibbler uses standard lexer/parser. Lexer is generated using flex. Parser is generated with bison++ (full source code for bison++ is provided with Dibbler sources). See ClntCfgMgr/ClntParser.y and ClntCfgMgr/ClntLexer.l for details. Make sure that you have flex installed (bison++ is provided with the dibbler source code). To generate parser and lexer code, type:

make bison (just once, to compile bison++)
make parser (each time you modify *.l or *.y files)

If you modify *.l or *.y files, pay close attention to output of make parser. In particular, make sure that there are no error messages that the grammar is inconsistent. Please refer to one of many bison tutorials that explain how to solve conflicts.

Note: When making the configuration parsers you might run into some prototype issues with yyFlexLexer::LexerInput(..) and yyFlexLexer::LexerOutput(..) due to incompatibilities between the 'FlexLex.h' file distributed with dibbler and the Flex on you system. These issuses can for the most parts be solved by simply replacing the 'CfgMgr/FlexLexer.h' distributed with dibbler with the one found on you system.

5.5.1 Parsing

Configuration file reading is done using Flex and bison++ tools. Flex is so called lexer. Its responsibility is to read config file and translate it into stream of tokens. (To be precise, Flex generates lexers, so it should be called lexer generator.) For example, this config file:

iface eth0 {
  class { pool 2000::1-2000::9 }

would be translated to following stream of tokens: [IFACE] [STRING:eth0] [{] [CLASS] [{] [POOL] [ADDR:2000::1] [-] [ADDR:2000::9] [}] [}]. This stream of token is then passed to parser. This parser is generated by bison++. Parser checks if that particular sequence of tokens makes sense. In this example, interface object will be created, which contains one class object, which contains one pool.

Is is sometimes very useful to define some parameter, usually associated with some level, on higher scope level. For example, if there are 3 classes, instead of defining the same valid-lifetime value on each of them, that parameter may be defined on the interface level or even at the top level. This is important to remember during parsing. Each subsequent element must inherit its parent properties (class object must inherit parameter values defined on the interface level).

Config file has various scopes: global, interface, ia, pd and others. In many cases a parameter can be defined in a global scope (e.g. dns-server option) that can be later overridden on a lower level scope (e.g. interface scope). Once the parser works its way through the parsed config file, it creates objects on a stack that represents scopes. That stack can be accesed with SrvParser::ParserOptStack structure. In particular, almost all operations are done on its last element (i.e. the current scope). That last element can be accessed with ParserOptStack.getLast(). The server uses TSrvParsGlobalOpt, TSrvParsIfaceOpt and TSrvParsClassOpt to represent scopes. The idea was to have the ability to properly derive values for options (e.g. defined in global scope and later use its value in interface scope), but that concept was never fully implemented.

The stack is then used to set options in various scopes. See TSrvCfgIface::setOptions(), TSrvCfgMgr::setGlobalOptions(), and TSrvCfgAddrClass::setOptions() (and similar for client).

To assist in this process, several methods were implemented in SrvParser: SrvParser::EndClassDeclaration(), SrvParser::EndIfaceDeclaration(), SrvParser::EndTAClassDeclaration(), SrvParser::EndPDDeclaration() and similar. For example, in server parser, following methods are called before and after interface definitions.

void SrvParser::StartIfaceDeclaration()
    // create new option (representing this interface) on the parser stack
    ParserOptStack.append(new TSrvParsGlobalOpt(*ParserOptStack.getLast()));

bool SrvParser::EndIfaceDeclaration()
    // create and add new interface to SrvCfgMgr
    // remove last option (representing this interface) from the parser stack
    return true;

5.5.2 Using parsed values

Lexer and parser are created in the Client Configuration Manager. See ClntCfgMgr/ClntCfgMgr.cpp. Following code is executed in the ClntCfgMgr constructor. Actual code is much more complicated, but unnecessary lines were removed for a clarification reasons.

yyFlexLexer lexer(&f,&clog);
ClntParser parser(&lexer);
result = parser.yyparse();

f and clog are normal C++ ifstream and ofstrem objects, associated with configuration file or a standard output. Configuration file is passed to the constructor of the entire TDHCPClient object, which is usually located in the main() function.

Example mentioned above works as follows:

  • Read all interfaces from the system (using System API). This is done in Interface Manager and is not important right now.
  • Create lexer object (it will read configuration file and convert it into stream of tokens)
  • create parser, which will interpret stream of tokens.
  • Match interfaces present in system with those specified in the configuration file.
  • Validate configuration file to check if there are no logical errors, like T1>T2, specified both stateless and request for ia, etc.

5.5.3 Embedded configuration

Note: This feature applies to the client only.

Another way of defining client configuration was introduced in the 0.5.0 release. Instead of reading configuration file, configuration can be hardcoded in the binary file itself. See MOD_CLNT_EMBEDDED_CFG flag description in Section 2.16 Modular features.

5.6 Coding guidelines

This section describes coding guidelines. They are useful if you happen to develop an extension, rewriting something or messing with Dibbler code in some other way. Note that Dibbler started a long time ago, so parts of those guidelines may seem a bit antiquated.

There are many areas of the existing code that do not follow this rules. There may be many reasons for that. I used to accept patches that fail to follow those guidelines. That will not happen anymore. Unless you are providing some extra cool feature, expect your patch to be rejected if you don't follow this rules. Dibbler have some large chunks of code merged. For example, the whole poslib library used to be a separare library.

Camel notation is a naming conventions when words are concatenated with first letter of each word (usually except the first one) is capitalized.

  1. Class names start with T, e.g. TOpt, TMsg. (Yes, yes, I know. Pascal sucks. I was young and dumb, when I started writing Dibbler :)
  2. Object, function and variables should use prefixes when appropriate. Following are used:
    • Clnt Srv Rel Req (server, client, relay, requestor)
    • Cfg Iface Addr Trans (specific managers within the code)
    • Lst (list)
  3. Variables and method names follow camel notation. First word is not capitalized in method names and variables. It is capitalized in class names and class variables. e.g. setAddr()
  4. Class method starts with lower case and follow camel notation, e.g. getAddr();
  5. Class variables start with upper case and follow camel notation and are ended with underscore, e.g. int IfaceIndex_; . Note: I recently decided to extend the notation to use underscore at the end, so most of the code does not follow that guideline yet.
  6. Each new method must be accompanied with doxygen comments. Doxygen comments for method should be placed in cpp files.
  7. Contributed code must not introduce new warnings from cppcheck. Please check your code before submitting patches.
  8. Try to use descriptive, but short names. Verb should be mentioned first (getAddr()), not last (do not use addrGet())
  9. Please use types defined in stdint.h, e.g. uint8_t, int16_t, uint32_t etc. Current code does not follow this guideline, but it will be refactored eventually.
  10. Please use uint8_t for any operations involving on-wire format.
  11. Please use smart pointers (SPtr<Type>) when possible. They work the same as boost::shared_ptr<Type>, but are better. They have better conversion operators. Also, not requiring 200MB of headers (that's how much latest libboost-dev took on my Debian) is often useful.
  12. Shared pointers (SPtr<Type>) can only be created: from other SPtr (automatic conversion) or using new() memory allocator, SPtr<foo> = new foo();.
  13. Lists are operated by methods: i.e. for Addr list, there should be methods addAddr(), firstAddr(),getAddr() etc.
  14. You can cast between different types of smart pointers, e.g. from SPtr<TOpt> to SPtr<TOptAddr>. Use conversion to (Ptr*). This is the only case when Ptr* may be used.
  15. Port-specific files MUST be places in dir named port-PORTNAME. Remember that Dibbler runs on many platforms, so think twice before you use anything OS-specific. Also, if you code something that works on one system only, please provide stub implementations for other OSes.
  16. Please do not add SrvOpt, ClntOpt or RelOpt classes. Options should be common for all components and very simple. Please do not put anything in doDuties(). This approach is deprecated and existing code will be cleaned up. Please put all the logic in appropriate managers (CfgMgr, IfaceMgr, TransMgr, etc.)
  17. Currently Options have Parent parameter that contains pointer to parent message. This may be removed some time in the future.
  18. There are 4 main managers: AddrMgr, CfgMgr, IfaceMgr and TransMgr. You can access each of them at any time using defined macros, e.g. ClntCfgMgr(), SrvIfaceMgr() and similar.
  19. Support for googletest was added recently. While not strictly required, googletest-based unit tests for new code is greatly appreciated. Once number of tests for existing code increases, they will become mandatory for contributed code as well.