Skip to content

How to use the Python library

G. B. Versiani edited this page Apr 2, 2015 · 7 revisions

Introduction

STUN-based client and servers are much easier to implement when you have a message encoder/decoder. This library has been written aiming to provide you a simple STUN message encoder/decoder.

This documentation describes the Python library.

The Python library

In short, stunmsg is also a Python library for encoding and decoding STUN messages. The Python API provides the same support available on the C and C++ libraries, but it has been adapted to ease the Python programming.

Dependencies

stunmsg depends on the following libraries:

  • _stunmsg_c.so library (generated by setup.py);

Encoding STUN messages

The usage is straightforward:

from stunmsg import StunMsg

def send_stun_request( ... ):
  # Create the STUN message object
  msg = StunMsg(StunMsg.BINDING_REQUEST, tsx_id)

  # Appends a SOFTWARE attribute
  msg.appendattr(StunMsg.ATTR_SOFTWARE, software_name)

  # Appends a PRIORITY attribute
  msg.appendattr(StunMsg.ATTR_PRIORITY, 0x6e0001ff)

  # Appends a ICE-CONTROLLED attribute
  msg.appendattr(StunMsg.ATTR_ICE_CONTROLLED, 0x932ff9b151263b36L)

  # Appends a USERNAME attribute
  msg.appendattr(StunMsg.ATTR_USERNAME, username)

  # Appends a MESSAGE-INTEGRITY attribute
  msg.appendattr(StunMsg.ATTR_MESSAGE_INTEGRITY, key)

  # Appends a FINGERPRINT attribute as last attribute
  msg.appendattr(StunMsg.ATTR_FINGERPRINT)

  # Now, send the message
  send(msg.data)

Decoding STUN messages

The decoding part is simple, but it will depend on the socket type you're working on.

Datagram sockets

For datagram sockets you usually allocate a max permitted memory block size, and simply begin parsing it. In this case, you can do the following:

from stunmsg import StunMsg

def receive_message(dgram_sock, ... ):
  
  # Receive STUN messages up to 2k
  data, addr = dgram_sock.recvfrom(2*1024)

  # Create the StunMsg object
  msg = StunMsg(data=data)

  # Check if this is a valid STUN message
  if not msg.verify():
    raise Exception('Not a STUN packet')

  # Read the message type
  if msg.type == StunMsg.BINDING_REQUEST:
    # do something
    pass
  elif msg.type == StunMsg.BINDING_RESPONSE:
    # do something
    pass
  # ...
  else:
    send_unknown_method(msg)
    return

  # Iterate over the message attributes
  for attr_type, attr_value in msg.iterattrs():
    if attr_type == StunMsg.ATTR_SOFTWARE:
      print "SOFTWARE: ", attr_value
    elif attr_type == StunMsg.ATTR_USERNAME:
      print "USERNAME: ", attr_value
    elif attr_type == StunMsg.ATTR_XOR_MAPPED_ADDRESS:
      addr, port = attr_value
      print "XOR-MAPPED-ADDRESS: ", addr, port
    elif attr_type == StunMsg.ATTR_MESSAGE_INTEGRITY:
      # Attribute value is a function to validate the incoming
      # message that receives the key needed to check the message
      check_function = attr_value
      print "MESSAGE-INTEGRITY: ", check_function(key)
    elif attr_type == StunMsg.ATTR_FINGERPRINT:
      # Attribute value is a boolean flag indicating whether the
      # fingerprint validation has passed
      check_result = attr_value
      print "FINGERPRINT: ", check_result
    # etc...

Stream sockets

First receive the STUN message header (20 bytes), then the rest of the message:

from stunmsg import StunMsg

def receive_message(stream_sock ...):

  # Receive the STUN message header first
  stun_header = sock_stream.recv(20)

  # Now, get the whole message size
  msg_len = StunMsg.getlength(stun_header)

  # And receive the remaining message bytes
  if msg_len > 20:
    stun_attrs = sock_stream.recv(msg_len - 20)
    msg = StunMsg(buf=stun_header+stun_attrs)
  else:
    # If message length is 20 bytes, it's a header-only message
    msg = StunMsg(buf=stun_header)

  # Handle the message ...