2 Generic methods to retreive the IP address of the local machine.
6 @author: Raphael Slinckx
7 @copyright: Copyright 2005
9 @contact: U{raphael@slinckx.net<mailto:raphael@slinckx.net>}
15 import random, socket, logging
17 from twisted.internet import defer, reactor
19 from twisted.internet.protocol import DatagramProtocol
20 from twisted.internet.error import CannotListenError
21 from twisted.internet.interfaces import IReactorMulticast
23 from nattraverso.utils import is_rfc1918_ip, is_bogus_ip
27 Returns a deferred which will be called with a
28 2-uple (lan_flag, ip_address) :
30 - True if it's a local network (RFC1918)
31 - False if it's a WAN address
33 - ip_address is the actual ip address
35 @return: A deferred called with the above defined tuple
36 @rtype: L{twisted.internet.defer.Deferred}
38 # first we try a connected udp socket, then via multicast
39 logging.debug("Resolving dns to get udp ip")
40 result = reactor.resolve('A.ROOT-SERVERS.NET')
41 result.addCallbacks(_get_via_connected_udp, lambda x:_get_via_multicast())
44 def get_external_ip():
46 Returns a deferred which will be called with a
47 2-uple (wan_flag, ip_address):
49 - True if it's a WAN address
50 - False if it's a LAN address
51 - None if it's a localhost (127.0.0.1) address
52 - ip_address: the most accessible ip address of this machine
54 @return: A deferred called with the above defined tuple
55 @rtype: L{twisted.internet.defer.Deferred}
57 return get_local_ip().addCallbacks(_on_local_ip, _on_no_local_ip)
60 def _on_upnp_external_found(ipaddr):
62 Called when an external ip is found through UPNP.
64 @param ipaddr: The WAN ip address
65 @type ipaddr: an IP string "x.x.x.x"
69 def _on_no_upnp_external_found(error, ipaddr):
71 Called when the UPnP device failed to return external address.
73 @param ipaddr: The LAN ip address
74 @type ipaddr: an IP string "x.x.x.x"
76 return (False, ipaddr)
78 def _on_local_ip(result):
80 Called when we got the local ip of this machine. If we have a WAN address,
81 we return immediately, else we try to discover ip address through UPnP.
83 @param result: a tuple (lan_flag, ip_addr)
84 @type result: a tuple (bool, ip string)
86 local, ipaddr = result
90 logging.debug("Got local ip, trying to use upnp to get WAN ip")
91 import nattraverso.pynupnp
92 return nattraverso.pynupnp.get_external_ip().addCallbacks(
93 _on_upnp_external_found,
94 lambda x: _on_no_upnp_external_found(x, ipaddr))
96 def _on_no_local_ip(error):
98 Called when we could not retreive by any mean the ip of this machine.
99 We simply assume there is no connectivity, and return localhost address.
101 return (None, "127.0.0.1")
103 def _got_multicast_ip(ipaddr):
105 Called when we received the ip address via udp multicast.
107 @param ipaddr: an ip address
108 @type ipaddr: a string "x.x.x.x"
110 return (is_rfc1918_ip(ipaddr), ipaddr)
112 def _get_via_multicast():
114 Init a multicast ip address discovery.
116 @return: A deferred called with the discovered ip address
117 @rtype: L{twisted.internet.defer.Deferred}
118 @raise Exception: When an error occurs during the multicast engine init
121 # Init multicast engine
122 IReactorMulticast(reactor)
126 logging.debug("Multicast ping to retrieve local IP")
127 return _discover_multicast().addCallback(_got_multicast_ip)
129 def _get_via_connected_udp(ipaddr):
131 Init a UDP socket ip discovery. We do a dns query, and retreive our
132 ip address from the connected udp socket.
134 @param ipaddr: The ip address of a dns server
135 @type ipaddr: a string "x.x.x.x"
136 @raise RuntimeError: When the ip is a bogus ip (0.0.0.0 or alike)
138 udpprot = DatagramProtocol()
139 port = reactor.listenUDP(0, udpprot)
140 udpprot.transport.connect(ipaddr, 7)
141 localip = udpprot.transport.getHost().host
144 if is_bogus_ip(localip):
145 raise RuntimeError, "Invalid IP address returned"
147 return (is_rfc1918_ip(localip), localip)
149 class _LocalNetworkMulticast(DatagramProtocol):
150 def __init__(self, nonce):
151 from p2pool.util import variable
154 self.address_received = variable.Event()
156 def datagramReceived(self, dgram, addr):
157 """Datagram received, we callback the IP address."""
158 logging.debug("Received multicast pong: %s; addr:%r", dgram, addr)
159 if dgram != self.nonce:
161 self.address_received.happened(addr[0])
163 @defer.inlineCallbacks
164 def _discover_multicast():
166 Local IP discovery protocol via multicast:
167 - Broadcast 3 ping multicast packet with "ping" in it
169 - Retrieve the ip address from the returning packet, which is ours
172 nonce = str(random.randrange(2**64))
173 p = _LocalNetworkMulticast(nonce)
175 # 5 different UDP ports
176 ports = [11000+random.randint(0, 5000) for port in range(5)]
177 for attempt, port in enumerate(ports):
179 mcast = reactor.listenMulticast(port, p)
181 except CannotListenError:
183 print "Trying another multicast UDP port", port
190 yield mcast.joinGroup('239.255.255.250', socket.INADDR_ANY)
193 logging.debug("Sending multicast ping")
195 p.transport.write(nonce, ('239.255.255.250', mcast_port))
197 address, = yield p.address_received.get_deferred(5)
199 mcast.leaveGroup('239.255.255.250', socket.INADDR_ANY)
201 mcast.stopListening()
203 defer.returnValue(address)