[Ns-developers] [ns3] socket API

Mathieu Lacage mathieu.lacage at sophia.inria.fr
Mon Apr 14 14:37:03 PDT 2008


hi,

Trying to implement a synchronous socket API on top of the ns3 API
turned out to be pretty useful, if only it showed some issues with the
underlying async API. Tom and I talked about some of these issues on
friday and I spent a while pondering over them and others so, here is a
summary of the parts of the socket API which are missing to implement a
full posix API on top. Some of the key issues I tried to address:

  - ability to implement a fully-conformant posix API on top

  - should make it possible to implement efficient versions of these
APIs. i.e., implementations which do not copy packet data from the
application to the socket layer.

  - allow the posix socket API to be extended with a version of
functions which take Ptr<Packet>, to allow _really_ efficient
synchronous applications which do not copy data from application to
socket.

There is quite a bit of content below but it would be nice to get
feedback on the basic approach and, then, I can try to prototype it in
the ns-3-simu branch to validate it.


1) need to implement a "pull" API in ns3::Socket. The assumption is that
the subclass of the ns3::Socket class we want to use will implement
internally a tx and an rx buffer: the synchronous wrapper on top of it
needs to access that buffer to implement the normal recv calls.

Proposal:

/**
 * return a _single_ packet by removing it from the internal rx 
 * buffer if it is smaller than maxSize. If the size of
 * the next packet to return is bigger than maxSize, return 0 and
 * set ENOMEM.
 * If you want to get the "from" address, it is stored as a tag
 * in the packet returned.
 * _Must_ be possible to call this method from the Recv callback.
 */
Ptr<Packet> Recv (uint32_t maxSize, uint32_t flags);

/**
 * Return number of bytes which can be returned from one or 
 * multiple calls to Recv.
 * Must be possible to call this method from the Recv callback.
 */
uint32_t GetRxAvailable (void) const;

The above two methods should be enough to implement a synchronous recv
function. If we implement them, we should remove the packet and address
arguments from the RecvCallback which would then be of type
Callback<void>.

2) the current Socket::Send method is quite painful to use and
implement. Proposal:

/**
 * return -1 if packet does not fit in tx buffer (current behavior is 
 * to send the part of the packet which fits in the tx buffer).
 * If the packet does not fit, the caller can split the packet to send
 * (based on output by GetTxAvailable) and re-attempt to send the
 * packet.
 * If you want to provide a per-packet "to" address, store it as a
 * special tag in the outgoing packet.
 */
int Send (Ptr<Packet> p, uint32_t flags);

/**
 * returns the number of bytes which can be sent in a single call
 * to Send. 
 */
uint32_t GetTxAvailable (void) const;

If we implement this, we should remove the SendTo method. Problem: if we
do this, we cannot specify the "to" address for
int Socket::SendTo (uint8_t *buffer, uint32_t size);
because there is no Ptr<Packet> to attach the "to" address to.

Solution: get rid of the Socket::Send* methods which take raw buffers
and implement them exclusively at the "synchronous" layer.

3) How should socket options and ioctls be supported ?
Proposal: 

in src/node:

class Ipv4SocketOptions
{
  /* Implement the linux IP_PKTINFO socket option. */
  virtual void EnablePktInfo (void) = 0;
  virtual void DisablePktInfo (void) = 0;
  virtual bool IsPktInfoEnabled (void) = 0;

  /* Implement the linux IP_OPTIONS socket option. */
  virtual void SetOptions (const uint8_t [40] buffer, uint32_t size);
  // return size of options data.
  virtual uint32_t GetOptions (uint8_t [40] buffer);

  /* implement IP_RECVTTL */
  virtual void EnableRecvTtl (void) = 0;
  virtual void DisableRecvTtl (void) = 0;
  virtual uint32_t IsRecvTtlEnabled (void) = 0;

  // all others...
};

class UdpSocketOptions : public Ipv4SocketOptions
{
public:
  // udp-specific options.
};

class TcpSocketOptions : public Ipv4SocketOptions {...};

All the above would be implemented in src/internet-node/ as aggregated
classes to each Socket. i.e., user code:

Ptr<Socket> sock = ...;
Ptr<Ipv4SocketOptions> ipv4 = sock->GetObject<Ipv4SocketOptions> ();
NS_ASSERT (ipv4 != 0);
ipv4->EnableRecvTtl ();

Another option here would be to just add all of the above methods to the
existing src/node/udp.h and src/node/tcp.h classes with an extra
Ptr<Socket> argument:

class Udp  ...
{
public:
  /* Implement the linux IP_PKTINFO socket option. */
  virtual void EnablePktInfo (Ptr<Socket> socket) = 0;
  virtual void DisablePktInfo (Ptr<Socket> socket) = 0;
  virtual bool IsPktInfoEnabled (Ptr<Socket> socket) = 0;
};

The former approach feels slightly more "right".

4) How is ancillary information supported ? It is attached as a tag to
each packet whether it is sent or received.

5) posix sockets are extended with:

int simu_recv (int fd, Ptr<Packet> packet, int flags);
int simu_send (int fd, Ptr<Packet> packet, int flags);
ssize_t simu_write (int fd, Ptr<Packet> packet);
ssize_t simu_read (int fd, Ptr<Packet> packet);


Mathieu



More information about the Ns-developers mailing list