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