[Ns-developers] Status ns-3 quagga porting

Mathieu Lacage mathieu.lacage at sophia.inria.fr
Thu Jun 5 13:29:57 PDT 2008


hi liu,



On Tue, 2008-06-03 at 22:59 +0800, Liu Jian wrote:
> Hello NS-3 community,
> 
> last week i read through code of libnl for better understanding netlink socket.
> After the NetlinkSocket class was defined, the main work for me is to implemente
> the NetlinkSocket::send()/recv(). 

ok.

> 
> the first step is to define the types of routing objects(rt_attr, rt_msg, ifinfo_msg,etc, 
> which quagga used in netlink route/addr/link).fisrtly i planned to define these as XXXHeader
> class like NetlinkMsgHeader, but i found it was not convenient when parsing the payload
> of netlink msg(eg. when fetching next route msg or netlink attribute one by one), 
> i define them in c style as structure and enum types in file netlink-route-types.h, so
> that the packet parsing would be easily to be done, after removing the NetlinkMsgHeader,
> get the payload start address pointer and parse it with the c style way used by libnl, then
> add different operations for different route msg types.
> 
> now for quagga only, there only handle 3 types of operations add_route/del_route, add_addr
> /del_route, set_link.(next step to add ipv4 APIs to fullfill them and  socket testing).

I tried to look at the details of these aspects of the netlink protocol
today and, I have to confess that it is a bit hairy so, I went back and
tried to first figure out how the netlink protocol works and how it can
be used for some simple things.

First, I noticed that we have a nice rfc for this at
ftp://ftp.rfc-editor.org/in-notes/rfc3549.txt which will, undoubtedly,
contain errors because the kernel code is the only authoritative source,
but, it is a good start. Another nice link is this:
http://people.redhat.com/nhorman/papers/netlink.pdf

The first type of messages I looked at are the messages which you can
use to get a list of network interfaces (devices) in a system. These are
defined in section 2.3.3.1. As florian and you suggested, looking over
the libnl sources helped a lot to figure out how this works. So, here is
a small summary of how these messages work.

1) construct a message header with type RTM_GETLINK and flags NLM_F_ROOT
and NLM_F_MATCH. message body contains a single 8 bit family field set
to AF_UNSPEC. send message.

2) read reply from kernel: a set of concatenated RTM_NEWLINK messages
with flag NLM_F_MULTI terminated by a message of type NLMSG_DONE. Each
RTM_NEWLINK message contains first an "Interface service message
template" (as defined in 2.3.3.1) followed by a set of TLV-encoded
attributes: IFLA_IFNAME, IFLA_TXQLEN, IFLA_OPERSTATE, IFLA_LINKMODE,
IFLA_MTU, IFLA_MAP, and, optionally, any of the IFLA_LINK, IFLA_MASTER,
IFLA_QDISC, IFLA_ADDRESS, IFLA_BROADCAST, IFLA_STATS. (I am reading
rtnl_fill_ifinfo in linux-2.6.25/net/code/rtnetlink.c). Each NEWLINK
message is aligned to the nearest 4 byte boundary (as per NLMSG_ALIGNTO)
while each attribute is stored as a (type,length,data) triplet with type
and length stored at 16 bit integers and data a variable length data
field aligned to the nearest 4 byte boundary (see NLA_HDRLEN,
linux-2.6.25/include/linux/netlink.h)

Although the rfc seems to imply a network byte order, the implementation
looks like it uses the host native byte order.

Now, as an exercise, let's look at how we could implement this without
having to copy/paste all of the linux kernel netlink implementation.

The first thing to observe is that the messages exchanged are really
made of:
1) a set of consecutive netlink message headers+body. If there is more
than one header, all but the last are flagged with NLM_F_MULTI and the
last one is of type NLMSG_DONE.
2) each body always starts with a message-specific "template" followed
by a set of message-specific TLV-encoded attributes.

I looked a bit at the current API in src/node/netlink-msg-header.h and I
would like to suggest a couple of modifications which will, hopefully,
make this whole parsing thing much easier to deal with:

1) Conceptually, I think that it makes sense to have only one Header
subclass and implement _all_ parsing of _all_ messages in this single
class.
2) With that said, I would suggest something along the lines of:

class NetlinkAttributeValue
{
public:
  NetlinkAttributeValue ();

  void SetString (std::string value);
  void SetU32 (uint16_t type, uint32_t value);
  void SetU16 (uint16_t type, uint16_t value);
  void SetU8 (uint16_t type, uint8_t value);
  void SetAddress (Address address);

  enum Type_e {
   UNSPEC, // invalid initial value.
   STRING,
   U32,
   U16,
   U8,
   ADDRESS
  };
  enum Type_e GetType (void) const;
  std::string GetString (void) const;
  uint32_t GetU32 (void) const;
  uint16_t GetU16 (void) const;
  uint8_t GetU8 (void) const;
  Address GetAddress (void) const;
private:
  enum Type_e m_type;
  uint32_t m_u32;
  uint16_t m_u16;
  uint8_t m_u8;
  std::string m_string;
};

struct NetlinkAttribute 
{
  uint16_t type;
  NetlinkAttributeValue value;
};

class InterfaceTemplate : public Header
{
public:
  InterfaceTemplate ();
  // all the boring serialization/deserialization.
  static TypeId GetTypeId (void);
  virtual TypeId GetInstanceTypeId (void) const;
  virtual void Print (std::ostream &os) const;
  virtual uint32_t GetSerializedSize (void) const;
  virtual void Serialize (Buffer::Iterator start) const;
  virtual uint32_t Deserialize (Buffer::Iterator start);

  enum {
    UP = (1<<0),
    BROADCAST = (1<<1),
    DEBUG = (1<<2),
    ...
  };

  void SetDeviceType (uint16_t type);
  void SetInterfaceIndex (uint32_t index);
  void SetDeviceFlags (uint32_t index);
  void SetChangeMask (uint32_t mask);

  uint16_t GetDeviceType (void) const;
  uint32_t GetInterfaceIndex (void) const;
  uint32_t GetDeviceFlags (void) const;
  uint32_t GetChangeMask (void) const;
private:
  uint16_t m_deviceType;
  uint32_t m_interfaceIndex;
  uint32_t m_deviceFlags;
  uint32_t m_changeMask;
};

class AddressTemplate : public Header
{
public:
  // all the boring serialization/deserialization.
  static TypeId GetTypeId (void);
  virtual TypeId GetInstanceTypeId (void) const;
  virtual void Print (std::ostream &os) const;
  virtual uint32_t GetSerializedSize (void) const;
  virtual void Serialize (Buffer::Iterator start) const;
  virtual uint32_t Deserialize (Buffer::Iterator start);

  enum {
    F_SECONDARY = 0x01,
    F_PERMANENT = 0x80,
    F_DEPRECATED = 0x20,
    F_TENTATIVE = 0x40
  };
  enum {
    SCOPE_UNIVERSE = 0,
    SCOPE_SITE = 200,
    SCOPE_LINK = 253,
    SCOPE_HOST = 254
  };

  uint8_t GetFamily (void) const;
  uint8_t GetLength (void) const;
  uint8_t GetFlags (void) const;
  uint8_t GetScope (void) const;
  uint32_t GetInterfaceIndex (void) const;

  void SetFamily (uint8_t family);
  void SetLength (uint8_t length);
  void SetFlags (uint8_t flags);
  void SetScope (uint8_t scope);
  void SetInterfaceIndex (uint32_t index);
private:
  uint8_t m_family;
  uint8_t m_length;
  uint8_t m_flags;
  uint8_t m_scope;
  uint32_t m_index;
};

class NetlinkMessage : public Header
{
public:
  NetlinkMessage ();
  // all the boring serialization/deserialization.
  static TypeId GetTypeId (void);
  virtual TypeId GetInstanceTypeId (void) const;
  virtual void Print (std::ostream &os) const;
  virtual uint32_t GetSerializedSize (void) const;
  virtual void Serialize (Buffer::Iterator start) const;
  virtual uint32_t Deserialize (Buffer::Iterator start);

  enum {
    F_REQUEST = 1,
    F_MULTI = 2,
    F_ACK = 4,
    // ...
  };

  void SetFlags (uint16_t v);
  void SetSeq (uint32_t v);
  void SetPid (uint32_t v);

  // the type argument is one of RTM_NEWLINK,RTM_DELLINK,RTM_GETLINK
  void SetInterfaceTemplate (uint16_t type, InterfaceTemplate template);
  // assert if type != RTM_NEWADDR,RTM_DELADDR,RTM_GETADDR
  void SetAddressTemplate (uint16_t type, AddressTemplate template);
  
  void AddAttribute (NetlinkAttribute attr);

  uint16_t GetFlags (void) const;
  uint16_t GetType (void) const;
  uint32_t GetSeq (void) const;
  uint32_t GetPid (void) const;

  // assert if m_type != one of RTM_NEWLINK,RTM_DELLINK,RTM_GETLINK
  InterfaceTemplate InterfaceTemplate (void) const;
  // assert if type != RTM_NEWADDR,RTM_DELADDR,RTM_GETADDR
  AddressTemplate SetAddressTemplate (void) const;

  uint32_t GetNAttributes (void) const;
  NetlinkAttribute GetAttribute (uint32_t index) const;


private:
  uint32_t m_type;
  uint16_t m_flags;
  uint32_t m_seq;
  uint32_t m_pid;
  InterfaceTemplate m_interfaceTemplate;
  AddressTemplate m_addressTemplate;
  std::vector<NetlinkAttribute> m_attributes;
};


A sample application for early testing could do something like this:

// send request for list of all interfaces.
Ptr<Socket> sock = ...;
Ptr<Packet> p = Create<Packet> ();
NetlinkMessage msg;
msg.SetSeq (0);
msg.SetPid (0);
msg.SetFlags (NetlinkMessage::F_ROOT | NetlinkMessage::F_MATCH);
msg.SetInterfaceTemplate (RTM_GETLINK, InterfaceTemplate ());
p->AddHeader (msg);
sock->Send (p);

// invoked when data is received from socket.
static void MyDataRecv (Ptr<Packet> p)
{
  NetlinkMessage msg;
  p->RemoveHeader (msg);
  bool first = true;
  while (first || msg.GetType () != NLMSG_DONE) {
    NS_ASSERT (msg.GetType () == RTM_NEWLINK);
    InterfaceTemplate interface = msg.GetInterfaceTemplate ();
    // print interface content
    // print attributes in msg.GetAttribute ()
    p->RemoveHeader (msg);
    first = false;
  }
}

The above classes are just a sketch but it should be not too hard to
extend the idea to a RouteTemplate class as per section 3.1.1. Before
getting there, it would, however, be nice to implement the
InterfaceTemplate outlined above, as a proof of concept to make sure
that I did not miss anything.

best regards,
Mathieu



More information about the Ns-developers mailing list