fix nattraverso imports
[p2pool.git] / nattraverso / ipdiscover.py
1 """
2 Generic methods to retreive the IP address of the local machine.
3
4 TODO: Example
5
6 @author: Raphael Slinckx
7 @copyright: Copyright 2005
8 @license: LGPL
9 @contact: U{raphael@slinckx.net<mailto:raphael@slinckx.net>}
10 @version: 0.1.0
11 """
12
13 __revision__ = "$id"
14
15 import random, socket, logging, itertools
16
17 from twisted.internet import defer, reactor
18
19 from twisted.internet.protocol import DatagramProtocol
20 from twisted.internet.error import CannotListenError
21 from twisted.internet.interfaces import IReactorMulticast
22 from twisted.python import log
23
24 from nattraverso.utils import is_rfc1918_ip, is_bogus_ip
25
26 @defer.inlineCallbacks
27 def get_local_ip():
28     """
29     Returns a deferred which will be called with a
30     2-uple (lan_flag, ip_address) :
31         - lan_flag:
32             - True if it's a local network (RFC1918)
33             - False if it's a WAN address
34         
35         - ip_address is the actual ip address
36     
37     @return: A deferred called with the above defined tuple
38     @rtype: L{twisted.internet.defer.Deferred}
39     """
40     # first we try a connected udp socket, then via multicast
41     logging.debug("Resolving dns to get udp ip")
42     try:
43         ipaddr = yield reactor.resolve('A.ROOT-SERVERS.NET')
44     except:
45         pass
46     else:
47         udpprot = DatagramProtocol()
48         port = reactor.listenUDP(0, udpprot)
49         udpprot.transport.connect(ipaddr, 7)
50         localip = udpprot.transport.getHost().host
51         port.stopListening()
52         
53         if is_bogus_ip(localip):
54             raise RuntimeError, "Invalid IP address returned"
55         else:
56             defer.returnValue((is_rfc1918_ip(localip), localip))
57     
58     logging.debug("Multicast ping to retrieve local IP")
59     ipaddr = yield _discover_multicast()
60     defer.returnValue((is_rfc1918_ip(ipaddr), ipaddr))
61
62 @defer.inlineCallbacks
63 def get_external_ip():
64     """
65     Returns a deferred which will be called with a
66     2-uple (wan_flag, ip_address):
67         - wan_flag:
68             - True if it's a WAN address
69             - False if it's a LAN address
70             - None if it's a localhost (127.0.0.1) address
71         - ip_address: the most accessible ip address of this machine
72     
73     @return: A deferred called with the above defined tuple
74     @rtype: L{twisted.internet.defer.Deferred}
75     """
76     
77     try:
78         local, ipaddr = yield get_local_ip()
79     except:
80         defer.returnValue((None, "127.0.0.1"))
81     if not local:
82         defer.returnValue((True, ipaddr))
83     logging.debug("Got local ip, trying to use upnp to get WAN ip")
84     import nattraverso.pynupnp
85     try:
86         ipaddr2 = yield nattraverso.pynupnp.get_external_ip()
87     except:
88         defer.returnValue((False, ipaddr))
89     else:
90         defer.returnValue((True, ipaddr2))
91
92 class _LocalNetworkMulticast(DatagramProtocol):
93     def __init__(self, nonce):
94         from p2pool.util import variable
95         
96         self.nonce = nonce
97         self.address_received = variable.Event()
98     
99     def datagramReceived(self, dgram, addr):
100         """Datagram received, we callback the IP address."""
101         logging.debug("Received multicast pong: %s; addr:%r", dgram, addr)
102         if dgram != self.nonce:
103             return
104         self.address_received.happened(addr[0])
105
106 @defer.inlineCallbacks
107 def _discover_multicast():
108     """
109     Local IP discovery protocol via multicast:
110         - Broadcast 3 ping multicast packet with "ping" in it
111         - Wait for an answer
112         - Retrieve the ip address from the returning packet, which is ours
113     """
114     
115     nonce = str(random.randrange(2**64))
116     p = _LocalNetworkMulticast(nonce)
117     
118     for attempt in itertools.count():
119         port = 11000 + random.randint(0, 5000)
120         try:
121             mcast = reactor.listenMulticast(port, p)
122         except CannotListenError:
123             if attempt >= 10:
124                 raise
125             continue
126         else:
127             break
128     
129     try:
130         yield mcast.joinGroup('239.255.255.250', socket.INADDR_ANY)
131         
132         logging.debug("Sending multicast ping")
133         for i in xrange(3):
134             p.transport.write(nonce, ('239.255.255.250', port))
135         
136         address, = yield p.address_received.get_deferred(5)
137     finally:
138         mcast.stopListening()
139     
140     defer.returnValue(address)