from contextlib import closing
from ...common import getlocaladdr
from ... import multitask
import sys, struct, socket, random, time, logging
try:
from os import urandom
except:
def urandom(count):
import random
return ''.join([chr(random.randint(0,255)) for x in range(count)])
logger = logging.getLogger('rfc3489')
defaultPort = 3478
defaultServers = ['stun.l.google.com:19302', 'sip.iptel.org', 'stun.xten.net', 'stun2.wirlab.net']
defaultServers = [(h, defaultPort if not p else int(p)) for h, i, p in [y.partition(':') for y in defaultServers]]
def Property(func):
return property(doc=func.__doc__, **func())
def _addr2str(value, family=socket.AF_INET):
'''Convert bytes to dotted-decimal representation.'''
return (family == socket.AF_INET) and '.'.join([str(ord(x)) for x in value[:4]]) \
or (family == socket.AF_INET6) and ':'.join(['%02x'%ord(x) for x in value[:16]]) \
or None
def _str2addr(value, family=socket.AF_INET):
'''Convert dotted-decimal representation to bytes.'''
return (family == socket.AF_INET) and ''.join([chr(int(x)) for x in value.split('.')]) \
or (family == socket.AF_INET6) and ''.join([(x and chr(int('0x%s'%x)) or '\x00') for x in value.split(':')]) \
or value
class Attribute(object):
'''A single attribute in STUN message. Only type (int) and value (str) are
valid fields in this object.'''
MAPPED_ADDRESS = 0x0001; CHANGE_REQUEST = 0x0003; SOURCE_ADDRESS = 0x0004
OTHER_ADDRESS = 0x0005; USERNAME = 0x0006; MESSAGE_INTEGRITY = 0x0008
ERROR_CODE = 0x0009; UNKNOWN_ATTRIBUTE = 0x000A; LIFETIME = 0x000D
'ALTERNATE_SERVER = 0x000E'; BANDWIDTH = 0x0010; DESTINATION_ADDRESS=0x0011
REMOTE_ADDRESS = 0x0012; DATA = 0x0013; REALM = 0x0014;
NONCE = 0x0015; XOR_MAPPED_ADDRESS= 0x0020; XOR_REFLECTED_FROM= 0x0023
PADDING = 0x0026; XOR_RESPONSE_ADDRESS=0x0027;
SERVER = 0x8022; ALTERNATE_SERVER = 0x8023; CACHE_TIMEOUT = 0x8026
FINGERPRINT = 0x8028;
knownTypes = [0x0001, 0x0004, 0x0005, 0x0006, 0x0008, 0x0009, 0x000A, 0x0014, 0x0015, 0x0020]
def __init__(self, type=None, value=None):
'''Construct an empty attribute or parsed one if value is supplied.'''
self.type, self.value = type, value
@property
def optional(self):
'''Whether this attribute is optional or not?'''
return (self.type & 0x8000) != 0
@Property
def address():
'''The address tuple (family, ip, port) where family is socket.AF_INET or AF_INET6,
ip is dotted IPv4 or IPv6 string and port is int. Must be accessed only for
address-based attributes such as MAPPED-ADDRESS, OTHER-ADDRESS and ALTERNATE-SERVER.'''
def fget(self):
ignore, family, port = struct.unpack('!BBH', self.value[:4])
family = (family == 0x01) and socket.AF_INET or (family == 0x02) and socket.AF_INET6 or socket.AF_UNSPEC
return (family, _addr2str(self.value[4:], family), port)
def fset(self, value):
family, address, port = value
address = _str2addr(address, family)
family = (family == socket.AF_INET) and 0x01 or (family == socket.AF_INET6) and 0x02 or 0x00
self.value = struct.pack('!BBH', 0, family, port) + address
return locals()
@Property
def xorAddress():
'''The address tuple (family, ip, port) with values similar to the address property,
but used only for XOR-MAPPED-ADDRESS, XOR-REFLECTED-FROM and XOR-RESPONSE-ADDRESS attributes.'''
def fget(self):
ignore, family, port = struct.unpack('!BBH', self.value[:4])
family = (family == 0x01) and socket.AF_INET or (family == 0x02) and socket.AF_INET6 or socket.AF_UNSPEC
if family == socket.AF_INET: value = struct.pack('!l', (struct.unpack('!l', self.value[4:8])[0] ^ Message.MAGIC))
else: raise ValueError, 'XOR-ADDRESS not implemented for IPv6'
return (family, _addr2str(value, socket.AF_INET), (port ^ ((Message.MAGIC & 0xffff0000) >> 16)) & 0x00ffff)
def fset(self, value):
family, address, port = value
address = _str2addr(address, family)
if family != socket.AF_INET: raise ValueError, 'XOR-ADDRESS not implemented for IPv6'
family = (family == socket.AF_INET) and 0x01 or (family == socket.AF_INET6) and 0x02 or 0x00
self.value = struct.pack('!BBHI', 0, family, port ^ ((Message.MAGIC & 0xffff0000) >> 16), struct.unpack('!I', address)[0] ^ Message.MAGIC)
return locals()
@Property
def error():
'''A tuple (number, text) representing error code for the ERROR-CODE attribute.'''
def fget(self):
ignore, cls, num = struct.unpack('!HBB', self.value[:4])
return ((cls & 0x07) * 100 + (num % 100), self.value[4:])
def fset(self, value):
cls, num = value[0] / 100, value[0] % 100
self.value = struct.pack('!HBB', 0, cls, num) + value[1]
return locals()
@Property
def unknown():
'''A list [type, type, ...] of attributes for the UNKNOWN-ATTRIBUTE.'''
def fget(self):
return [x for x in struct.unpack('!'+str(len(self.value)/2)+'H', self.value)]
def fset(self, value):
self.value = ''.join([struct.pack('!H', x) for x in value])
return locals()
def __repr__(self):
return '<%s type=0x%04x value=%r />'%(self.__class__.__name__, self.type, (self.type in [Attribute.MAPPED_ADDRESS, Attribute.ALTERNATE_SERVER]) and self.address or self.value)
class Message(object):
'''A STUN message definition. The properties method, type and tid are defined in the spec.
The attrs property is a list of STUN attributes in this Message object.'''
BINDING, ignore, ALLOCATE, SEND, DATA, SET_ACTIVE_DESTINATION = range(1,7)
REQUEST, INDICATION, RESPONSE, ERROR = tuple(range(0, 4))
MAGIC = 0x2112A442
def __init__(self, value=None):
'''Construct a Message. Attributes are method (12-bits), type (two-bits), tid
(12 bytes) and list of attr. Parse the value if given.'''
self.method = self.type = 0
self.tid, self.attrs = '', []
if value:
type, length, magic, self.tid = struct.unpack('!HHL12s', value[:20])
if (type & 0xC000) != 0:
raise ValueError, 'incorrect message type: %x'%type
if magic != Message.MAGIC:
raise ValueError, 'incorrect magic cookie: %x'%magic
if length != (len(value)-20):
raise ValueError, 'incorrect length: %d != %d'%(length, len(value)-20)
if (length & 0x0003) != 0:
raise ValueError, 'incorrect length %d, must be multiple of four'%length
self.method = (type & 0x000F) | ((type & 0x00E0) >> 1) | ((type & 0x3E00) >> 2)
self.type = ((type & 0x0100) >> 7) | ((type & 0x0010) >> 4)
value = value[20:]
while value and len(value)>0:
attrtype, attrlen = struct.unpack('!HH', value[:4])
self.attrs.append(Attribute(attrtype, value[4:4+attrlen]))
value = value[(4+attrlen+(4-attrlen%4)%4):]
def __str__(self):
'''Format a message into byte stream.'''
type = (self.method & 0x000F) | ((self.method & 0x0070) << 1) | ((self.method & 0x0F80) << 2) \
| ((self.type & 0x01) << 4) | ((self.type & 0x02) << 7)
if not self.tid: self.tid = urandom(12)
attrstr = ''
for attr in self.attrs:
value = str(attr.value)
attrstr += struct.pack('!HH', attr.type, len(value)) + value
if (len(value) % 4) != 0:
attrstr += ''.join([chr(0) for x in range(0, (4-len(value)%4)%4)])
result = struct.pack("!HHL12s", 0x3FFF & type, len(attrstr), Message.MAGIC, self.tid)
result += attrstr
return result
def __repr__(self):
'''User friendly display of the STUN Message.'''
result = '<%s method=%r type=%r tid=%r'%(self.__class__.__name__, self.method, self.type, self.tid)
if self.attrs:
result += '>\n ' + ('\n '.join([repr(x) for x in self.attrs])) + '\n</%s>'%self.__class__.__name__
else:
result += '/>'
return result
def __getitem__(self, name):
'''Return the first attribute matching the name (attribute type).'''
for attr in self.attrs:
if attr.type == name:
return attr
return None
def __setitem__(self, name, value):
'''Override or add an attribute with the name (attribute type) and value.
Note the different in set and get semantics. In set you set the value (str), but
get returns an Attribute object.'''
for attr in self.attrs:
if attr.type == name:
attr.value = value
return
self.attrs.append(Attribute(name, value))
def __contains__(self, name):
'''Check if the name (attribute type) exists in the message?'''
for attr in self.attrs:
if attr.type == name:
return True
return False
def request(sock, server=None, **kwargs):
'''Send a STUN client request with retransmissions and return the response.
This is a generator function, and can be called as
response, external = yield request(sock, ('stun.iptel.org', 3478))
It raises ValueError in case of failure and multitask.Timeout in case of timeout
or failure to connect TCP or invalid response received. For TCP, the sock remains
connected after successful return or exception from this function.
Arguments are as follows:
sock: the socket to use for sending request and receiving response.
server: optional server (ip, port), defaults to defaultServers[0]. For TCP if sock
is already connected, then server argument is ignored.
method: optional STUN method, defaults to Message.BINDING.
tid: optional transaction id, by default generates a new.
attrs: optional attributes, by default empty list [].
rto: optional RTO, defaults to 0.1 for UDP and 9.3 for TCP.
retry: optional retry count, defaults to 7 for UDP and 1 for TCP.
maxsize: optional maximum packet size, defaults to 1500.
handler: optional handler function, that receives any message that was received
but not handled by the request method.
The handler argument allows demultiplexing other types of received messages on the
same socket. Note that raising an exception is not good, because we still want to
wait for response instead of exiting. The handler is invoked as
handler(sock, remote, data) where data is raw data string and remote is usually
server (ip, port). If no handler is specified, then invalid data raises a ValueError.
'''
server = server or defaultServers[0]
handler = kwargs.get('handler', None)
maxsize = kwargs.get('maxsize', 1500)
m = Message()
m.method = kwargs.get('method', Message.BINDING)
m.type = Message.REQUEST
m.tid = kwargs.get('tid', urandom(12))
m.attrs = kwargs.get('attrs', [])
mstr = str(m)
if len(mstr) >= maxsize: raise ValueError, 'Cannot send packet of length>%d'%(maxsize)
if sock.type == socket.SOCK_STREAM:
remote = None
try: remote = sock.getpeername()
except: pass
if not remote:
try:
sock.connect(server)
remote = server
except:
raise multitask.Timeout()
tcp, rto, retry = True, kwargs.get('rto', 9.3), kwargs.get('retry', 1)
else:
tcp, rto, retry = False, kwargs.get('rto', 0.100), kwargs.get('retry', 7)
while retry>0:
retry = retry - 1
logger.debug('sending STUN request method=%d, len=%d, remaining-retry=%d', m.method, len(mstr), retry)
if tcp:
yield multitask.send(sock, mstr)
else:
yield multitask.sendto(sock, mstr, server)
try:
if tcp:
data = (yield multitask.recv(sock, maxsize, timeout=rto))
if not data: break
logger.debug('request() received data')
type, length, magic = struct.unpack('!HHL', data[:8])
if type & 0xC000 != 0 or magic != Message.MAGIC:
raise ValueError, 'invalid STUN response from server type=0x%x, magic=0x%x'%(type, magic)
if length > (maxsize-8):
raise ValueError, 'very large response length[%d]>%d'%(length+8, maxsize)
else:
data, remote = (yield multitask.recvfrom(sock, maxsize, timeout=rto))
if data:
try:
response = Message(data)
logger.debug('received STUN message method=%d, type=%d', response.method, response.type)
except:
logger.exception('received invalid STUN message len=%d', len(response))
if handler:
handler(sock, remote, data)
continue
else:
raise ValueError, 'Invalid response from server'
if response.tid != m.tid:
logger.debug('The tid does not match. ignoring')
if handler: handler(sock, remote, data)
continue
external = None
for attr in response.attrs:
if not attr.optional and attr.type not in Attribute.knownTypes:
raise ValueError, 'Attribute 0x%04x not understood in response'%attr.type
if response.type == Message.RESPONSE and response.method == Message.BINDING:
for attr in response.attrs:
if attr.type == Attribute.XOR_MAPPED_ADDRESS:
external = attr.xorAddress
elif attr.type == Attribute.MAPPED_ADDRESS:
external = attr.address
elif response.type == Message.ERROR:
error = None
for attr in response.attrs:
if attrs.type == Attribute.ERROR_CODE:
error = attrs.error
break
raise ValueError, 'Request failed with error %r'%error
if external:
external = external[1:]
raise StopIteration(response, external)
except multitask.Timeout:
rto = rto * 2
except StopIteration:
logger.debug('request() returning external= %s', external)
raise
except:
logger.exception('Some ValueError exception')
break
raise multitask.Timeout
def _createSocket():
'''Create a socket, bind and return (socket, (localip, localport)).'''
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
sock.bind(('', 0))
local = (getlocaladdr(sock), sock.getsockname()[1])
return (sock, local)
def discoverBehavior(servers=defaultServers):
'''Discover the NAT behavior and return the result as a tuple (type, mapping, filtering, external).
There are four types: public, blocked, good and bad which correspond to as follows:
public: mapping = filtering = 'Endpoing Independent', i.e., no NAT.
blocked: mapping = filtering = None, i.e., UDP is blocked.
good: mapping = 'Endpoint Independent', filtering='Address Independent', i.e., full-cone.
bad: anything else is a bad NAT, hence requires ICE or tunneling.
If the servers list is not provided, a default servers list (defaultServers)
is used. Each element in the servers should be 'host:port' of the server.
This is a generator function and can be invoked as
nattype, mapping, filtering, external = yield discoverBehavior()
The external result represents the external (ip, port) and is useful only if the mapping
is 'Address Independent' or 'Endpoint Independent'.
'''
sock, local = _createSocket()
mapping = filtering = external = None
for server in servers:
try:
response, external = (yield request(sock, server=server))
if external == local:
mapping = filtering = 'Endpoint Independent'
elif Attribute.OTHER_ADDRESS in response:
other = response[Attribute.OTHER_ADDRESS].address[1:]
response, external2 = (yield request(sock, (other[0], server[1])))
if external == external2:
mapping = 'Endpoint Independent'
else:
response, external3 = (yield request(sock, server=other))
mapping = external3 == external2 and 'Address Dependent' or 'Address and Port Dependent'
sock2, local2 = _createSocket()
try:
response, external4 = (yield request(sock2, server, attrs=[Attribute(Attribute.CHANGE_REQUEST, '\x00\x00\x00\x06')]))
filtering = 'Address Independent'
except multitask.Timeout:
try:
response, external5 = (yield request(sock2, server, attrs=[Attribute(Attribute.CHANGE_REQUEST, '\x00\x00\x00\x02')]))
filtering = 'Address Dependent'
except multitask.Timeout:
filtering = 'Address and Port Dependent'
else:
mapping = filtering = 'Unknown'
i = servers.index(server)
if i < len(servers) and servers[i+1][0] != server[0]:
other = servers[i+1]
response, external2 = (yield request(sock, other))
if external == external2:
mapping = 'Address Independent'
else:
mapping = 'Address and Port Dependent'
if mapping or filtering:
break
except:
logger.exception('Continuing after an exception or timeout with server=%r', server)
nattype = (not mapping or not filtering) and 'blocked' \
or mapping == filtering == 'Endpoint Independent' and 'public' \
or mapping.find('Independent')>=0 and filtering.find('Independent')>=0 and 'good' \
or 'bad'
raise StopIteration(nattype, mapping, filtering, external)
def server(sock1, **kwargs):
'''A simple server implementation to test the code or to use in real deployment.
The application should start the server as multitask.add(server(sock)).
The caller should make sure that the sock1 argument is a UDP or TCP socket
which is already bound. Additionally, sock2, sock3, and sock4 can be supplied
as keyword arguments and represent the socket to use for change-IP, change-port
and change IP+port commands respectively. Other keyword arguments are as follows:
timeout: optional acivity timeout (second) if relay is activated, defaults to 180.
external: optional external (ip, port) of the socket in case it is behind
a full-cone NAT and still acts as a (relay) server.
handler: optional function that gets invoked as handler(sock, remote, data) for
non-STUN data, and allows the application to demultiplex other types of data.
maxsize: optional maximum size of packet to handle, defaults to 1500.'''
sock2, sock3, sock4 = kwargs.get('sock2', None), kwargs.get('sock3', None), kwargs.get('sock4', None)
addr1 = getlocaladdr(sock1)
addr4 = sock4 and getlocaladdr(sock4) or None
timeout = kwargs.get('timeout', 180)
external = kwargs.get('external', addr1)
handler = kwargs.get('handler', None)
maxsize = kwargs.get('maxsize', 1500)
tcp = (sock1.type == socket.SOCK_STREAM)
binding = dict()
def respond(sock, data, remote):
if sock.type == socket.SOCK_STREAM:
yield multitask.send(sock, data)
else:
yield multitask.sendto(sock, data, remote)
def bindingRequest(sock, m, remote):
res = Message()
res.method, res.type, res.tid = Message.BINDING, Message.RESPONSE, m.tid
mapped = Attribute(Attribute.MAPPED_ADDRESS)
mapped.address = (sock.family, addr1[0], addr1[1])
res.attrs.append(mapped)
if Attribute.CHANGE_REQUEST not in m:
if addr4:
other = Attribute(Attribute.OTHER_ADDRESS)
other.address = (sock4.family, addr4[0], addr4[1])
res.attrs.append(other)
else:
change = m[Attribute.CHANGE_REQUEST]
sock = change.value == '\x00\x00\x00\x06' and sock4 or change.value == '\x00\x00\x00\x02' and sock3 or change.value == '\x00\x00\x00\x04' and sock2 or None
if sock:
yield respond(sock, str(res), remote)
raise StopIteration()
def allocateRequest(sock, m, remote):
fivetuple = (sock.type, getlocaladdr(sock), remote)
lifetime = timeout
if Attribute.LIFETIME in m:
lt = struct.unpack('!L', m[Attribute.LIFETIME].value)
if lt < lifetime: lifetime = lt
if fivetuple in binding:
newsock = binding[fivetuple]
if lifetime == 0:
del binding[fivetuple]
del binding[newsock]
else:
if lifetime > 0:
newsock = socket.socket(sock.family, sock.type)
newsock.bind(('0.0.0.0', 0))
binding[newsock] = fivetuple
binding[fivetuple] = newsock
res = Message()
res.method, res.type, res.tid = m.method, Message.RESPONSE, m.tid
mapped = Attribute(Attribute.MAPPED_ADDRESS)
mapped.address = (newsock.family, (external, newsock and newsock.getsockname()[1] or 0))
res.attrs.append(mapped)
res.attrs.append(Attribute(Attribute.LIFETIME, struct.pack('!L', lifetime)))
if lifetime == 0 and newsock:
newsock.close()
else:
if sock.type == socket.SOCK_STREAM:
multitask.add(relayaccepter(newsock, fivetuple))
else:
multitask.add(relayreceiver(newsock, fivetuple))
yield respond(sock, str(res), remote)
def relaytcpreceiver(sock, fivetuple):
pass
def relayaccepter(sock, fivetuple):
sock.listen(5)
while True:
try:
conn, remote = (yield multitask.accept(sock))
if conn:
logger.debug('relayaccepter().accept() from %r', remote)
sock.close()
binding[fivetuple] = conn
del binding[sock]
binding[conn] = fivetuple
multitask.add(relaytcpreceiver(conn, fivetuple, remote))
break
except:
break
logger.debug('relaytcpaccepter() exiting')
def relayreceiver(sock, fivetuple):
while True:
try:
data, remote = (yield multitask.recvfrom(sock, maxsize))
if data:
logger.debug('server().recvfrom() from %r', remote)
multitask.add(datahandler(sock1, data, remote))
except:
break
logger.debug('server() exiting')
def sendRequest(sock, m, remote):
fivetuple = (sock.type, getlocaladdr(sock), remote)
try:
if fivetuple not in binding:
raise ValueError, 'no turn binding found'
newsock = binding[fivetuple]
destaddr = Attribute.DESTINATION_ADDRESS in m and m[Attribute.DESTINATION_ADDRESS].address[1:] or None
data = Attribute.DATA in m and m[Attribute.DATA] or None
if sock.type == socket.SOCK_STREAM:
try:
remote = newsock.getpeername()
except:
remote = None
if not remote:
newsock.connect(destaddr)
remote = destaddr
yield multitask.send(newsock, data)
else:
yield multitask.sendto(newsock, data, destaddr)
result = True
except:
logger.exception('sendRequest() exception')
result = False
res = Message()
res.method, res.type, res.tid = m.method, (result and Message.RESPONSE or Message.ERROR), m.tid
if not result:
error = Attribute(Attribute.ERROR_CODE)
error.error = (400, 'cannot send request')
res.attrs.append(error)
yield respond(sock, str(res), remote)
def datahandler(sock, data, remote):
try:
m = Message(data)
func = m.type == Message.REQUEST and ( \
m.method == Message.BINDING and bindingRequest \
or m.method == Message.ALLOCATE and allocateRequest \
or m.method == Message.SEND and sendRequest \
) or None
if func:
yield func(sock, m, remote)
else:
raise ValueError, 'unhandled request or message'
except StopIteration:
logger.debug('datahandler: stop iteration')
raise
except:
logger.exception('datahandler() exception')
if handler:
handler(sock, remote, data)
def tcpreceiver(sock, remote):
while True:
data = (yield multitask.recv(sock, maxsize))
if not data: break
type, length, magic = struct.unpack('!HHL', data[:8])
valid = (type & 0xC000 == 0) and magic == Message.MAGIC and length<=(maxsize-8)
if valid:
yield datahandler(sock, data, remote)
logger.debug('tcpreceiver() finished data handler')
else:
handler(sock, data, remote)
if tcp: sock1.listen(5)
logger.debug('server listening on %r', addr1)
while True:
try: tcp = (sock1.type == socket.SOCK_STREAM)
except: break
try:
if tcp:
conn, remote = (yield multitask.accept(sock1, timeout=5))
if conn:
logger.debug('server().accept() from %r', remote)
multitask.add(tcpreceiver(conn, remote))
else:
data, remote = (yield multitask.recvfrom(sock1, maxsize, timeout=5))
if data:
logger.debug('server().recvfrom() from %r', remote)
multitask.add(datahandler(sock1, data, remote))
except multitask.Timeout:
continue
except:
break
logger.debug('server() exiting')
def _testServer():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
sock.bind(('0.0.0.0', 0))
multitask.add(server(sock))
sockaddr = getlocaladdr(sock)
multitask.add(_testDiscoverBehavior([sockaddr, defaultServers[0]]))
yield multitask.sleep(5)
sock.close()
def _testDiscoverBehavior(servers=None):
nattype, mapping, filtering, external = servers and (yield discoverBehavior(servers)) or (yield discoverBehavior())
assert (nattype, mapping, filtering) == ('bad', 'Address Independent', 'Unknown')
assert external[0] == '76.102.244.145'
logger.debug('nattype=%r, mapping=%r, filtering=%r, external=%r', nattype, mapping, filtering, external)
def _testRequest():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
sock.bind(('0.0.0.0', 0))
try:
local = getlocaladdr(sock)
response, external = yield request(sock, ('sip.iptel.org', defaultPort))
assert external[0] == '76.102.244.145'
except (ValueError, multitask.Timeout), E:
logger.exception('exception - ValueError or Timeout')
except:
logger.exception('exception')
def _testTcpRequest():
try:
sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock1.bind(('0.0.0.0', 0))
multitask.add(server(sock1))
sockaddr = getlocaladdr(sock1)
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock2.bind(('0.0.0.0', 0))
yield multitask.sleep(2)
response, external = (yield request(sock2, sockaddr))
print 'external=', external
sock1.close()
yield multitask.sleep(6)
print '_testTcpRequest() exiting'
except (ValueError, multitask.Timeout), E:
print 'exception - ValueError or Timeout', E
except:
print 'exception', sys.exc_info()
def _testRelay():
try:
sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock1.bind(('0.0.0.0', 0))
multitask.add(server(sock1))
sockaddr = getlocaladdr(sock1)
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock2.bind(('0.0.0.0', 0))
yield multitask.sleep(2)
response, mapped = request(sock2, sockaddr, method=Message.ALLOCATE)
print 'mapped=', mapped
sock1.close()
sock2.close()
yield multitask.sleep(6)
except:
print 'exception', sys.exc_info(), traceback.print_exc(file=sys.stdout)
if __name__ == "__main__":
import traceback
from ...common import getintfaddr, setlocaladdr
logging.basicConfig()
logger.setLevel(logging.CRITICAL)
setlocaladdr(getintfaddr('8.8.8.8'))
multitask.add(_testRequest())
multitask.add(_testDiscoverBehavior())
multitask.run()