BIND 10 #1540: dhcp code refactor: Pkt6 and Options for DHCPv6

BIND 10 Development do-not-reply at isc.org
Fri Feb 17 20:10:43 UTC 2012


#1540: dhcp code refactor: Pkt6 and Options for DHCPv6
-------------------------------------+-------------------------------------
                   Reporter:  tomek  |                 Owner:  tomek
                       Type:         |                Status:  reviewing
  enhancement                        |             Milestone:  Sprint-
                   Priority:  major  |  DHCP-20120220
                  Component:  dhcp   |            Resolution:
                   Keywords:         |             Sensitive:  0
            Defect Severity:  N/A    |           Sub-Project:  DHCP
Feature Depending on Ticket:         |  Estimated Difficulty:  0
        Add Hours to Ticket:  0      |           Total Hours:  0
                  Internal?:  0      |
-------------------------------------+-------------------------------------

Comment (by tomek):

 Replying to [comment:6 stephen]:
 > IfaceMgr::send() - (V6 version) Use a "static_int<char*>" to cast the
 data for v.iov_base instead of the C-style cast.
 That's not that simple. getData() returns const void* while iov_base
 structure has field of type void*. static_cast from const void* to void*
 does not work. I used const_cast<void*>.

 Using C++ style casts while dealing with system API is awkward. Around
 line 635 there is following conversion:

     struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);

 CMSG_DATA is a macro defined in system headers. On my Linux box, it is
 defined as
 ((unsigned char *) ((struct cmsghdr *) (cmsg) + 1))
 so CMSG_DATA() is of unsigned char * type. The only way to cast that to
 struct in_pktinfo* is to use reinterpret_cast, which is only slightly
 better than pure C cast. I would be less readable, though. I left C style
 cast for now. Let me know if you prefer reinterpret_cast instead.

 > IfaceMgr::send() - (V6 version) Remove the comment about ticket #1237.
 Also, can the Linux-specific part be put into a separate function
 (possibly in a separate file)?  It would serve to isolate the Linux-
 specific code from the rest of the function.
 We can do this, but I would very much like to avoid that. It would require
 putting msghdr and iovec structures as parameters. I'm not sure if those
 are defined in the same way on all supported systems (and if they are
 defined at all). That method would be mentioned in iface_mgr.h that is
 frequently used in many places. Instead of separating Linux code, we would
 spread it all around. The other alternative would be to pass msghdr and
 iovec pointers as void*, but that would be an ugly kludge.

 > IfaceMgr::receive4() - The "while" loop looks awkward with two "++s"
 increments.  Converting to "for" loop and inverting the test of the
 address family would simplify it.  Also, as this appears to be separate
 from the rest of the code, can't it be placed into a separate method?
 This code will be removed when support for receiving data on more than one
 interface (using select()) will be implemented. Changed to for loop. Added
 comment with reference to ticket #1555.

 > IfaceMgr::receive4() - a lot of the declarations look similar to that in
 other methods - is there anything that could be made common?
 Again, creating functions for them would require using some possibly Linux
 specific structures in header.

 We could define a function (not a member of the IfaceMgr class) that would
 have a scope limited to iface_mgr.cc, but that would be breaking down OO
 paradigm (and a good coding) for just a minor benefit.

 > IfaceMgr::receive4() - "pkt" should have the data type Pkt4Ptr (and
 should be declarated where it is initialized).
 >
 > IfaceMgr::receive4() - return variable should be of type Pkt4Ptr.
 Done.

 > IfaceMgr::receive4() - same comments made above concerning declaraing
 variables closer to their use apply.
 Done.
 >
 > IfaceMgr::receive4() - no need for a blank line after calling recvmsg()
 and the test on result.
 >
 > IfaceMgr::receive4() - suggest place the Linux-specific code in a
 separate function (in a separate file).
 See above regarding polluting headers with Linux-specific structures.

 > IfaceMgr::receive6() - most of the comments made for receive4()
 (including the conversion of the loop over the sockets from "while" to
 "for", and variable initialization) apply here.
 >
 > IfaceMgr::receive6() - the "no interfaces detected" message should be
 made only if "ifaces_.empty()" is true.  Currently it can be thrown if no
 interface has IPV6 capability (which is somewhat different).
 Updated the message.

 > IfaceMgr::receive6() - casts in the calls to setXxxxAddr should be
 C++-style.
 I think reinterpret_cast is the only suitable cast method.

 > IfaceMgr::getSocket() - in the method signature, "const Pkt6&" appears
 to be the more usual style than "Pkt6 const&".
 >
 > IfaceMgr::getSocket() - (both versions) in the "for" loop, as the
 conditions aren't too complicated, inverting and combining the conditions
 in a single "if" test and placing the "return" in the body avoids the need
 for "continue" statements.
 Done.

 >
 >
 > '''src/lib/dhcp/libdhcp++.{cc,h}'''[[BR]]
 > unpackOptions6() - should not be a space between "end" and ")" in the
 first "if" test.
 >
 > unpackOptions4(): using pre-increments is generally better than post-
 increments.
 That is true regarding objects. However, unpackOptions4 post-increments
 only plain integers, so there is no difference in performance. Increasing
 offset after byte was consumer is also more natural.

 > unpackOptions6()/unpackOptions4() - in the call to options.insert(), you
 could use std::make_pair as a less-verbose way of creating a pair to
 insert into "options".
 Great trick! I was not aware of the make_pair.

 > !OptionFactoryRegister() - should be no space preceding the "*" in the
 factory type.  Spaces needed around "!=" in the "if" statements.
 >
 > !OptionFactoryRegister() - throws an exception if the option type is
 greater than 254 - although "end" option has a value of 255.  If "end" can
 never be passed to this method, there should be a comment explaining why.
 Added appropriate comment. Also added exception in case of another special
 value 0 (that's padding option).


 > '''src/lib/dhcp/option.h'''[[BR]]
 > Typedef of !OptionBufferPtr does not need spaces after "<" and before
 ">".
 >
 > Typedef of !OptionCollection does not need to be split across two lines.
 Also, no need for a space before the ">".
 >
 > A comment should be added to the effect that it is assumed all over the
 code that !OptionBuffer is a vector.  (In several places an iterator over
 !OptionBuffer is dereferenced and assumed to point to a set of contiguous
 bytes that are interpreted as a data type such as uint16_t.  If the
 underlying !OptionBuffer data type were changed, this might not be true.)
 Added appropriate comment.

 > General: revisiting the Option class, I was struck by the separation of
 functions depending on "Universe".  What is the argument against an Option
 (abstract) base class with Option4 and Option6 derived classes?
 Hierarchy bloat and code duplication. Many soon to be implemented options,
 like integer arrays are shared between v4 and v6. Implementing them
 separate would need to code the same methods twice (or use ugly multiple
 inheritance).

 > '''src/lib/dhcp/option.cc'''[[BR]]
 > pack4() - since the constructor only rejects the V4 option type if type
 is greater than 255, there is a possibility that the Option object could
 hold an "end" option.  In this case, pack4() will attempt to add a length
 byte after it.
 Good catch. Also added check that 0 (PAD option) is invalid as well.
 >
 > pack4() - should add a sanity check for option size when writing the
 length byte for a V4 option - len() returns a uint16_t so could
 potentially overflow the field.
 Updated check in pack4()

 > pack() - "return" should have the argument enclosed in brackets.
 >
 > getOption()/delOption() - argument type should be uint16_t, not
 "unsigned short".
 Done.

 > addOption() - consider use of make_pair().
 Done.

 > '''src/lib/dhcp/option6_addrlst.cc'''[[BR]]
 > Remove comment "this wrapping is *ugly*..." ... unless you think it is
 still ugly, in which case complete the comment :-)
 My sense of good taste (or lack of thereof) must have changed recently. I
 don't consider
 calling 4 methods (getAddress().to_v6().to_bytes().data()) to get an
 actual value of address from address object ugly anymore.

 > Option6AddrLst::unpack() - use V6ADDRESS_LEN in the "if" statement
 instead of 16. (More informative and consistent with use later on in the
 method.)
 >
 > Option6AddrLst::unpack() - would prefer the result of "distance % 16" to
 be explicitly compared against 0 (instead of using the implicit conversion
 to bool): the check is whether the remainder is non-zero and an explicit
 numerical check reinforces that.
 Done.

 > '''src/lib/dhcp/option6_ia.cc'''[[BR]]
 > Option6IA::unpack() - there is a warning if the distance between the
 start and end iterators is less than 12; what if it is greater than 12?
 Also, could this value be a symbolic constant (c.f. OPTION6_IAADR_LEN in
 Option6IAAddr::unpack())
 Nothing wrong happens if it is greater than 12. That only means that sub-
 options are
 present (which is usually the case for IA_NA and IA_PD as they are
 containers for addresses and prefixes). Distance equal 12 means no sub-
 options.

 > '''src/lib/dhcp/pkt4.h'''[[BR]]
 > The comments for the member variable data_ use double slashes instead of
 the triple slashes required by Doxygen and used for other member
 variables.
 Fixed.
 >
 > '''src/lib/dhcp/pkt6.h'''[[BR]]
 > getBuffer() header - should say "This buffer is only valid while the
 Pkt4 object is valid."
 It already said that. I think you meant "Pkt6" :-) Fixed.

 >
 > len() header - the header comment is not clear.  The "brief" explanation
 is that it returns the size of the packet, but the text implies that it
 returns the length of the options fields - and treats the header as one of
 them.  Also, why does options_ have to be set - if it is clear doesn't it
 mean that the packet has no options?
 Comment clarified.

 > A consistent style should be used for the inline expansions of one-line
 methods.  In some cases the expansion is on the same line as the
 declaration: in other cases it is split across three lines.
 I think I fixed them all.

 > Does ifindex_ deserve the "brief" line in its explanatory comments?
 I don't understand this comment. Do you want me to remove "@brief
 interface index" from
 description of int ifindex_ field? I wanted it to be there to explain
 (even when browsing
 Doxygen documentation for the whole class that shows only brief
 descriptions) that ifindex
 stands for "interface index", not conditional index ("if index").

 > There are apparently two ways of identifying the interface - the name
 and the index.  Are these truly independent or is there a unified way of
 accessing the relevant interface?
 Keeping them separated is really the way to go. Usually they are equally
 unique, but in some
 cases ifindex can change. It was the case in some older Ubuntu boxes that
 had race some kind of race condition. Also, in some systems (Windows XP)
 interface names can be really weird, like "Połączenie sieci
 bezprzewodowej" (pol. Wireless network link). Note spaces and national
 characters. It is also unfortunate that those names are language specific
 and change between languages. In such bizarre environments it is better to
 rely on interface indexes. There are also cases when interface can
 disappear and reappear (e.g. any USB interface) and there is no guarantee
 that it will get the same index. There are valid use cases, when we also
 want to refer non-existing interface by its name, e.g. when CPE device
 boots and wifi link initialization takes a long time. Also, depending on
 the context it is more convenient to use one identification or the other.
 When dealing with configuration, it is more likely that we will use
 interface names. On the other hand, socket operations more likely will use
 ifindex to refer specific interface.

 The bottom line is that we need both.

 > No need for spaces after the opening parenthesis and beflore the closing
 parenthesis in some EXPECT_EQ checks.
 I removed several instances of unnecessary spaces in various tests. I hope
 I found them all.

 > '''src/lib/dhcp/tests/iface_mgr_unittest.cc'''[[BR]]
 > sendReceive6 test - the loop setting the dummy payload should have
 spaces around binary operators.
 Done.

 > '''src/lib/dhcp/tests/libdhcp++_unittest.cc'''[[BR]]
 > packOptions4 and packOptions6 tests - no need for the space before the
 ">" in the "opts.insert" lines.
 Used make_pair instead. Shorter and cleaner.

 > packOptions6 test - the EXPECT_NO_THROW test can be on a single line.
 Fixed. On a side note, I think EXPECT_NO_THROW macro is kind of pointless.
 If there is exception thrown during test, the test will fail anyway. The
 only benefit of using it is that the (failed) test will continue
 execution.

 >
 > No need for spaces after the opening parenthesis and before the closing
 parenthesis in some EXPECT_EQ checks.
 Done.

 > '''src/lib/dhcp/tests/option6_addrlst_unittest.cc'''[[BR]]
 > No need for spaces after the opening parenthesis and before the closing
 parenthesis in some EXPECT_EQ checks.
 >
 >
 > '''src/lib/dhcp/tests/option6_ia_unittest.cc'''[[BR]]
 > suboptions_pack test - "sub1" declaration does not need to sbe split
 across two lines.
 >
 > suboptions_unpack test - not sure what the cryptic comment "48 bytes"
 means.
 Clarified.

 > suboptions_unpack test - in the data declaration, binary operators
 should have a space around them.
 Done.

 > '''src/lib/dhcp/tests/option6_iaaddr_unittest.cc'''[[BR]]
 > basic test - in the initialization loop, binary operators should have a
 space around them.
 Done.

 > '''src/lib/dhcp/tests/option_unittest.cc'''[[BR]]
 > No need for spaces after the opening parenthesis and before the closing
 parenthesis in some EXPECT_EQ checks.
 Done.

 > '''src/lib/dhcp/tests/pkt6_unittest.cc'''[[BR]]
 > constructor test - No space before the "*" in the declaration of pkt1.

 >
 > No need for spaces after the opening parenthesis and before the closing
 parenthesis in some EXPECT_EQ checks.
 >
 > capture() - C++ style is to put the "*" after the return type, not
 before the function name.
 >
 > capture() - can we line up the assignment of data elements?  However, it
 would be clearer to statically initialize the array.
 This code was actually automatically generated. Lined up assignments and
 added comment in iface_msg_unitest (in case we will ever use it again to
 convert captured packets into parsing tests).

 > packUnpack test - "3*Option" - should be spaces around the "*".
 Done.

 > addGetDelOptions test -  C++ style is to put the "*" after the return
 type, not before the function name (declaration of "parent".)  Also should
 be no space before the closing parenthesis.
 Done.

 > '''src/lib/util/buffer.h'''[[BR]]
 > The change appears OK, but tests/buffer_unittest.cc should be extended
 to check that the constructor using a vector works.
 Implemented. On a side note, I'm now 100% addicted to TDD. Buffer
 constructor actually didn't set position properly and test found that out.

 Also renamed some tests to reflect that they are for inputBuffer.

 > '''!ChangeLog'''[[BR]]
 > Text for the entry is OK.

-- 
Ticket URL: <http://bind10.isc.org/ticket/1540#comment:8>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development


More information about the bind10-tickets mailing list