penafieljlm / PyXAPI

Extensions to the python2 sockets module that add 'recvmsg' and 'sendmsg'

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

This is PyXAPI version 0.1
==========================

PyXAPI package consists of two modules: `socket_ext' and `rfc3542'. The
second one is optional (see file INSTALL.txt for more details).

  1. `socket_ext' extends the Python module `socket'. `socket' objects have
     two new methods: `recvmsg' and `sendmsg'. It defines `ancillary data'
     objects and some functions related to. `socket_ext' module also provides
     functions to manage interfaces indexes defined in RFC3494 and not
     available from standard Python module `socket'.

  2. `rfc3542' is a full implementation of RFC3542 (Advanced Sockets
     Application Program Interface (API) for IPv6).

License information
===================

PyXAPI is free software. See the file COPYING for copying conditions.

Requirements
============

See file INSTALL.txt.

The `socket_ext' module
=======================

Introduction
------------

`socket_ext', is an extension of Python module `socket'. It adds two
methods to the standard Python socket object: `recvmsg' and `sendmsg' which
are wrappers for the well known UNIX system calls `recvmsg' and `sendmsg'
respectively. As a first example, let us show how to send data via a TCP
connected socket:

from socket_ext import *
s = socket(AF_INET, SOCK_STREAM)
s.connect((host, port))
s.sendmsg(('This is a test',))

which is equivalent to:

from socket import *
s = socket(AF_INET, SOCK_STREAM)
s.connect((host, port))
s.send('This is a test')

Note that first argument of `sendmsg' is a tuple of string. It is the
scatter/gather array passed to the UNIX system call `sendmsg' (see UNIX
manual for more details). We could also have done:

s.sendmsg(('This is ', 'a test'))

Now to receive data (`s' same as above):

addr, data, adata, flags = s.recvmsg((14,))

Argument of method `recvmsg' is a tuple of integers. Each integer is the
size of the corresponding element of the scatter/gather array passed to
the UNIX system call `recvmsg'.
`addr' is the destination address as a Python socket (IPv4) address, i.e
a 2-tuple `(host, port)'.
`data' is a tuple of strings. Suppose we have received the following data:
`'This is a test'', then `data' is the tuple `('This is a test',)'.
If we did:

addr, data, adata, flags = s.recvmsg((12, 2))

`data' would be the tuple `('This is a te', 'st')'
`adata' is a tuple of ancillary data (see below). In this example, there
are no ancillary data, so `adata' is the empty tuple.
Finally `flags' is flags on received message. It is usually used to do error
checking as follows:

if flags & MSG_TRUNC:
   print 'data are truncated'
if flags & MSG_CTRUNC:
   print 'ancillary data are truncated'

Ancillary data
--------------

  `socket_ext' module defines a new object (of type CMSGType) to handle
ancillary data. To create such an object:

from socket_ext import *
c = cmsg()

An ancillary data object has four methods: `set', `set_from_data', `get'
and `CMSG_DATA'. For example, if you want to build a ancillary data object
with `IP_TTL' option, you should proceed as follows:

import struct
from socket_ext import *
ttl = struct.pack('L', 1)
c = cmsg()
c.set(SOL_IP, IP_TTL, ttl)

First argument of method `set' is the originating protocol, second argument
is the protocol-specific type and last argument is the raw data as a Python
string.

If you want to access to ancillary data fields, use method `get':

>>> print c.get()
((16, 0, 4), '\x01\x00\x00\x00')

If you just want to get data:

>>> print '%r' % c.CMSG_DATA()
'\x01\x00\x00\x00'

or in a more realistic way:

ttl = struct.unpack('L',  c.CMSG_DATA())[0]

The last method, `set_from_data' is used when you have to initialize a
ancillary data from raw data (Python string). This method is not really
needed because socket method `recvmsg' does all the job for you (see below).
Ancillary data objects have also four attributes:

>>> print c.cmsg_len, c.cmsg_level, c.cmsg_type, '%r' % c.cmsg_data
16 0 4 '\x01\x00\x00\x00'

Note: `c.cmsg_data' is strictly equivalent to `c.CMSG_DATA()'.

  `socket_ext' module defines also two methods which are wrappers for macros
`CMSG_SPACE' and `CMSG_LEN' (refer to UNIX manual for a full explanation):

>>> print CMSG_SPACE(struct.calcsize('L')), CMSG_LEN(struct.calcsize('L'))
16 16

It is now easy to send data with ancillary data `c':

s = socket(AF_INET, SOCK_DGRAM)
s.sendmsg((host, port), ('This is ', 'a test'), (c,))

You can notice that ancillary data (third argument) is a tuple. It is because
it is possible to send several ancillary data simultaneously. We need also to
give destination address (first argument) because `s' is an unconnected socket.
  Processing ancillary data received is no more difficult:

alen =  CMSG_SPACE(struct.calcsize('L'))
addr, data, adata, flags = s.recvmsg((1024,), alen)
for a in adata:
    if a.cmsg_level == SOL_IP and a.cmsg_type == IP_TTL:
        print 'TTL: %u' % struct.unpack('L',  c.cmsg_data)[0]

The second parameter of `recvmsg' method is the length of the buffer which
contains received ancillary data. We expect an integer, so data part of
ancillary data has size equal to `struct.calcsize('L')'. We then use macro
`CMSG_SPACE' to calculate ancillary data size (see UNIX manual for a full
description of this macro).

Interface methods:
------------------

While RFC3493 (Basic Socket Interface Extensions for IPv6) defines some
functions to manage interfaces indexes, standard Python socket module
does not provide them (they are useful in `rfc3542' module). They have been
added in `socket_ext' module:

>>> print if_nametoindex('fxp0')
1
>>> print if_indextoname(3)
gif0
>>> print if_nameindex()
[(1, 'fxp0'), (2, 'lp0'), (3, 'gif0'), (4, 'lo0'), (5, 'ppp0'), (6, 'sl0')]

Please refer to  RFC3493, section 4 (Interface Identification) for a full
description of these functions.

The `rfc3542' module
====================

Introduction
------------

  This module is a full implementation of RFC3542 (Advanced Sockets
Application Program Interface (API) for IPv6) and (in general) needs
`socket_ext module previously described; every program calling this module
should contain the following lines:

from socket_ext import *
from rfc3542 import *

You must have Python socket module compiled with IPv6 support (see file
INSTALL.txt) if you plan to use this (optional) module.
  Of course, we shall not explain or comment RFC3542. Reader is strongly
encouraged to read this document BEFORE going further.
  For each option, `rfc3542' module defines an appropriate new object. Each
object (`icmp6_filter' excepted) has three methods: `set', `set_from_data'
and `get'. Every object has a `data' attribute which is the object as a (raw)
Python string). This attribute is in particular used when passing an object
to `set/getsockopt' methods (see below).

`icmp6_filter' objects
----------------------

  When an ICMPv6 raw socket is created, it will by default pass all ICMPv6
message types to the application. If an application wants only receive
ICMPv6 Echo Reply:

f = icmp6_filter() # object is created
f.ICMP6_FILTER_SETBLOCKALL();
f.ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY);
s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)
s.setsockopt(IPPROTO_ICMPV6, ICMP6_FILTER, f.data)

`hoplimit' objects
------------------

  To specify hop limit as ancillary data, you can proceed like that:

h = hoplimit() # object is created
h.set(16) # setting hop limit to 16
c = cmsg()
c.set(IPPROTO_IPV6, IPV6_HOPLIMIT, h.data)
s = socket(AF_INET6, SOCK_DGRAM)
s.sendmsg((host, port), ('This is ', 'a test'), (c,))

To receive hop limit as ancillary data, you must enable IPV6_RECVHOPLIMIT
socket option:

s.setsockopt(IPPROTO_IPV6, IPV6_RECVHOPLIMIT, 1)

and proceed as follows:

h = hoplimit()
alen = CMSG_SPACE(h.size) # computing ancillary data size, see below
addr, data, adata, flags = s.recvmsg((1024,), alen)
for a in adata:
    if a.cmsg_level == IPPROTO_IPV6 and a.cmsg_type == IPV6_HOPLIMIT:
        h.set_from_data(a.cmsg_data)
        print 'hop limit: %u' % h.get()

To help programmer to compute ancillary data size, each object (except
`inet6_rth objects, see below) has a `size' attribute. This attribute
is the size in bytes of the option (not object) and is usually passed
to CMSG_SPACE macro.

`tclass' objects
----------------

  To specify traffic class as ancillary data, you can proceed like that:

h = tclass() # object is created
h.set(0x08)
c = cmsg()
c.set(IPPROTO_IPV6, IPV6_TCLASS, h.data)
s = socket(AF_INET6, SOCK_DGRAM)
s.sendmsg((host, port), ('This is ', 'a test'), (c,))

To receive traffic class as ancillary data, you must enable IPV6_RECVTCLASS
socket option:

s.setsockopt(IPPROTO_IPV6, IPV6_RECVTCLASS, 1)

and proceed as follows:

t = tclass()
alen = CMSG_SPACE(t.size)
addr, data, adata, flags = s.recvmsg((1024,), alen)
for a in adata:
    if a.cmsg_level == IPPROTO_IPV6 and a.cmsg_type == IPV6_TCLASS:
        t.set_from_data(a.cmsg_data)
        print 'traffic class: 0x%.2x' % t.get()

Note: option `IPV6_TCLASS' is not implemented on all Linux platforms.

`in6_pktinfo' objects
---------------------

  To specify pktinfo as ancillary data, you can proceed like that:

p = in6_pktinfo() # object is created
p.set('::', if_nametoindex('eth0')) # (unspecified address, interface eth0)
c = cmsg()
c.set(IPPROTO_IPV6, IPV6_PKTINFO, p.data)
s = socket(AF_INET6, SOCK_DGRAM)
s.sendmsg((host, port), ('This is ', 'a test'), (c,))

To receive in6_pktinfo as ancillary data, you must enable IPV6_RECVPKTINFO
socket option:

s.setsockopt(IPPROTO_IPV6, IPV6_RECVPKTINFO, 1)

and proceed as follows:

p = in6_pktinfo()
alen = CMSG_SPACE(p.size)
addr, data, adata, flags = s.recvmsg((1024,), alen)
for a in adata:
    if a.cmsg_level == IPPROTO_IPV6 and a.cmsg_type == IPV6_PKTINFO:
        p.set_from_data(a.cmsg_data)
	p = p.get()
        print "pktinfo: addr='%s', if=%s" % (p[0], if_indextoname(p[1]))

`nexthop' objects
-----------------

  To specify nexthop as ancillary data, you can proceed like that:

p = nexthop() # object is created
p.set('3ffe::1') # setting the next hop address
c = cmsg()
c.set(IPPROTO_IPV6, IPV6_NEXTHOP, p.data)
s = socket(AF_INET6, SOCK_DGRAM)
s.sendmsg((host, port), ('This is ', 'a test'), (c,))

Note: this is a privileged option. This option does not have any meaning for
multicast destinations.

`inet6_rth' objects
-------------------

  To specify routing header (of type 0 (`IPV6_RTHDR_TYPE_0')) as ancillary
data, you can proceed like that:

r = inet6_rth() # object is created
r = inet6_rth_init(r, IPV6_RTHDR_TYPE_0, 2) # initialization (2 nodes)
inet6_rth_add(r, '3ffe::1') # adding first intermediate node
inet6_rth_add(r, '3ffe::2') # adding second intermediate node

The four above lines of code can be replaced by:

r = inet6_rth()
r.set(IPV6_RTHDR_TYPE_0, '3ffe::1', '3ffe::2')

We have kept the `set' method for `inet6_rth' objects in order to have the
same methods for all `inet6_rth' objects.

c = cmsg()
c.set(IPPROTO_IPV6, IPV6_RTHDR, r.data)
s = socket(AF_INET6, SOCK_DGRAM)
s.sendmsg((host, port), ('This is ', 'a test'), (c,))

To receive routing header as ancillary data, you must enable IPV6_RECVRTHDR
socket option:

s.setsockopt(IPPROTO_IPV6, IPV6_RECVRTHDR, 1)

and proceed as follows:

r = inet6_rth()
alen = inet6_rth_space(IPV6_RTHDR_TYPE_0, 10) # expecting up to 10 nodes
alen = CMSG_SPACE(alen)
addr, data, adata, flags = s.recvmsg((1024,), alen)
for a in adata:
    if a.cmsg_level == IPPROTO_IPV6 and a.cmsg_type == IPV6_RTHDR:
        r.set_from_data(a.cmsg_data)
	r = r.get()
        print 'header:\n  nxt=%d, len=%d, type=%d, segleft=%d' % r[0][:-1]
        print 'segments:'
        for s in r[1:]:
            print '  %s' % s

To calculate ancillary data size, we used here function `inet6_rth_space'
(refer to RFC3542 for full description) instead of attribute `size'. In fact
`inet6_rth' objects have no `size' attribute because their size depends on
number of intermediate nodes.
  Output will be something like that:

header:
  nxt=17, len=4, type=0, segleft=2
segments:
  3ffe::1
  3ffe::2

  To be full compliant with RFC3542, other inet6_rth_XXX routines are also
available. If we suppose that `r' is the same `inet6_rth' object as above:

>>> print inet6_rth_segments(r)
2
>>> print inet6_rth_getaddr(r, 0)
3ffe::1
>>> inet6_rth_reverse(r, r)
>>> print r.get()
((0, 4, 0, 2, 0), '3ffe::2', '3ffe::1')

`ip6_mtuinfo' objects
---------------------

  To determine the current path MTU value for the destination of a given
CONNECTED socket(see RFC3542, section 11.4):

s = socket(AF_INET6, SOCK_STREAM)
s.connect(('::1', 20000))
m = ip6_mtuinfo() # object is created
data = s.getsockopt(IPPROTO_IPV6, IPV6_PATHMTU, m.size)
m.set_from_data(data)
print 'MTU: %u' % m.get()[1]

Output will be as follows:

MTU: 16384

Here, `16384' is the MTU of the loopback interface. For other use of
`ip6_mtuinfo' objects, refer to section 11 of RFC3542.

Note: option `IPV6_PATHMTU' is not (yet) implemented on Linux platforms.

Hop-by-Hop and Destination Options
----------------------------------

  Module `rfc3542' provides the seven inet6_opt_XXX functions defined in
RFC3542. They are:`inet6_opt_init', `inet6_opt_append', `inet6_opt_finish',
`inet6_opt_set_val', `inet6_opt_next', `inet6_opt_find' and
`inet6_opt_get_val'.
  To see how to use these methods from `rfc3542' module, reader is invitated
to read the Python script `inet6_opt.py' located in the `Test' directory of
the distribution. This script is the Python version of the example showed in
RFC3542 (see RFC3542 (p71-74), section 22: Appendix C: Examples Using the
inet6_opt_XXX() Functions).

An example when sending/receiving several ancillary data
--------------------------------------------------------

  To send hop limit and pktinfo simultaneously:

# building hop limit ancillary data
h = hoplimit()
h.set(16)
ch = cmsg()
ch.set(IPPROTO_IPV6, IPV6_HOPLIMIT, h.data)

# building pktinfo ancillary data
p = in6_pktinfo()
p.set('::', if_nametoindex('eth0'))
cp = cmsg()
cp.set(IPPROTO_IPV6, IPV6_PKTINFO, p.data)

s = socket(AF_INET6, SOCK_DGRAM)
s.sendmsg((host, port), ('This is ', 'a test'), (ch, cp))

  To receive hop limit and pktinfo as ancillary data:

s.setsockopt(IPPROTO_IPV6, IPV6_RECVHOPLIMIT, 1)
s.setsockopt(IPPROTO_IPV6, IPV6_RECVPKTINFO, 1)

h = hoplimit()
p = in6_pktinfo()
alen = CMSG_SPACE(h.size) + CMSG_SPACE(p.size)

addr, data, adata, flags = s.recvmsg((1024,), alen)
for a in adata:
    if a.cmsg_level != IPPROTO_IPV6:
        continue
    if a.cmsg_type == IPV6_HOPLIMIT:
        h.set_from_data(a.cmsg_data)
        print 'hop limit: %u' % h.get()
    elif a.cmsg_type == IPV6_PKTINFO:
        p.set_from_data(a.cmsg_data)
	p = p.get()
        print "pktinfo: addr='%s', if=%s" % (p[0], if_indextoname(p[1]))

Implementation note:
====================

  `socket_ext' (resp. `rfc3542') module is composed of C extension module
`_socket_ext.c' (resp. `_rfc3542.c') located in `Modules' subdirectory and
a Python module `socket_ext.py' (resp `rfc3542.py') which acts as a front
end  to the C extension module and is located in top level directory.

Author
======

Yves Legrandgerard
email: ylg@pps.jussieu.fr
url: http://www.pps.jussieu.fr/~ylg/PyXAPI

About

Extensions to the python2 sockets module that add 'recvmsg' and 'sendmsg'

License:GNU General Public License v2.0


Languages

Language:C 76.2%Language:Python 14.7%Language:M4 8.5%Language:Makefile 0.5%