10 from tunchannel import tun_fwd
12 class TunChannel(object):
14 Helper box class that implements most of the required boilerplate
15 for tunnelling cross connections.
17 The class implements a threaded forwarder that runs in the
18 testbed controller process. It takes several parameters that
19 can be given by directly setting attributes:
21 tun_port/addr/proto: information about the local endpoint.
22 The addresses here should be externally-reachable,
23 since when listening or when using the UDP protocol,
24 connections to this address/port will be attempted
27 peer_port/addr/proto: information about the remote endpoint.
28 Usually, you set these when the cross connection
29 initializer/completion functions are invoked (both).
31 tun_key: the agreed upon encryption key.
33 listen: if set to True (and in TCP mode), it marks a
34 listening endpoint. Be certain that any TCP connection
35 is made between a listening and a non-listening
36 endpoint, or it won't work.
38 with_pi: set if the incoming packet stream (see tun_socket)
39 contains PI headers - if so, they will be stripped.
41 ethernet_mode: set if the incoming packet stream is
42 composed of ethernet frames (as opposed of IP packets).
44 udp: set to use UDP datagrams instead of TCP connections.
46 tun_socket: a socket or file object that can be read
47 from and written to. Packets will be read when available,
48 remote packets will be forwarded as writes.
49 A socket should be of type SOCK_SEQPACKET (or SOCK_DGRAM
50 if not possible), a file object should preserve packet
51 boundaries (ie, a pipe or TUN/TAP device file descriptor).
53 trace_target: a file object where trace output will be sent.
54 It cannot be changed after launch.
55 By default, it's sys.stderr
59 # Some operational attributes
61 self.ethernet_mode = True
64 # These get initialized when the channel is configured
65 # They're part of the TUN standard attribute set
69 # These get initialized when the channel is connected to its peer
70 self.peer_proto = None
74 # These get initialized when the channel is connected to its iface
75 self.tun_socket = None
77 # same as peer proto, but for execute-time standard attribute lookups
82 self._terminate = [] # terminate signaller
83 self._exc = [] # exception store, to relay exceptions from the forwarder thread
84 self._connected = threading.Event()
85 self._forwarder_thread = None
88 self.stderr = sys.stderr
90 # Generate an initial random cryptographic key to use for tunnelling
91 # Upon connection, both endpoints will agree on a common one based on
93 self.tun_key = ( ''.join(map(chr, [
96 for r in (random.SystemRandom(),) ])
97 ).encode("base64").strip() )
101 return "%s<%s %s:%s %s %s:%s>" % (
102 self.__class__.__name__,
104 self.tun_addr, self.tun_port,
106 self.peer_addr, self.peer_port,
111 udp = self.tun_proto == "udp"
112 if not udp and self.listen and not self._forwarder_thread:
113 if self.listen or (self.peer_addr and self.peer_port and self.peer_proto):
118 if not self._forwarder_thread:
122 if self._forwarder_thread:
126 if self._forwarder_thread:
127 self._connected.wait()
128 for exc in self._exc:
130 eTyp, eVal, eLoc = exc
131 raise eTyp, eVal, eLoc
134 if self._forwarder_thread:
135 if not self._terminate:
136 self._terminate.append(None)
137 self._forwarder_thread.join()
140 # Launch forwarder thread with a weak reference
141 # to self, so that we don't create any strong cycles
142 # and automatic refcounting works as expected
143 self._forwarder_thread = threading.Thread(
144 target = self._forwarder,
145 args = (weakref.ref(self),) )
146 self._forwarder_thread.start()
149 def _forwarder(weak_self):
151 weak_self().__forwarder(weak_self)
155 # store exception and wake up anyone waiting
156 self._exc.append(sys.exc_info())
157 self._connected.set()
160 def __forwarder(weak_self):
161 # grab strong reference
166 peer_port = self.peer_port
167 peer_addr = self.peer_addr
168 peer_proto= self.peer_proto
170 local_port = self.tun_port
171 local_addr = self.tun_addr
172 local_proto = self.tun_proto
175 ether_mode = self.ethernet_mode
176 with_pi = self.with_pi
178 if local_proto != peer_proto:
179 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (local_proto, peer_proto)
181 udp = local_proto == 'udp'
184 if (udp or not listen) and (not peer_port or not peer_addr):
185 raise RuntimeError, "Misconfigured peer for: %s" % (self,)
187 if (udp or listen) and (not local_port or not local_addr):
188 raise RuntimeError, "Misconfigured TUN: %s" % (self,)
190 TERMINATE = self._terminate
191 cipher_key = self.tun_key
192 tun = self.tun_socket
195 raise RuntimeError, "Unconnected TUN channel %s" % (self,)
199 rsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
202 rsock.bind((local_addr,local_port))
205 # wait a while, retry
208 rsock.bind((local_addr,local_port))
209 rsock.connect((peer_addr,peer_port))
210 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
212 # accept tcp connections
213 lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
216 lsock.bind((local_addr,local_port))
219 # wait a while, retry
222 lsock.bind((local_addr,local_port))
224 rsock,raddr = lsock.accept()
225 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
227 # connect to tcp server
228 rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
231 rsock.connect((peer_addr,peer_port))
234 # wait a while, retry
237 rsock.connect((peer_addr,peer_port))
238 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
240 # notify that we're ready
241 self._connected.set()
243 # drop strong reference
248 ether_mode = ether_mode,
249 cipher_key = cipher_key,
251 TERMINATE = TERMINATE,
259 def create_tunchannel(testbed_instance, guid, devnull = []):
261 TunChannel factory for metadata.
262 By default, silences traceing.
264 You can override the created element's attributes if you will.
267 # just so it's not open if not needed
268 devnull.append(open("/dev/null","w"))
269 element = TunChannel()
270 element.stderr = devnull[0] # silence tracing
271 testbed_instance._elements[guid] = element
273 def preconfigure_tunchannel(testbed_instance, guid):
275 TunChannel preconfiguration.
277 It initiates the forwarder thread for listening tcp channels.
279 Takes the public address from the operating system, so it should be adequate
280 for most situations when the TunChannel forwarder thread runs in the same
281 process as the testbed controller.
283 element = testbed_instance._elements[guid]
285 # Find external interface, if any
286 public_addr = os.popen(
288 "| grep $(ip route | grep default | awk '{print $3}' "
289 "| awk -F. '{print $1\"[.]\"$2}') "
290 "| head -1 | awk '{print $2}' "
291 "| awk -F : '{print $2}'").read().rstrip()
292 element.tun_addr = public_addr
294 # Set standard TUN attributes
295 if not element.tun_port and element.tun_addr:
296 element.tun_port = 15000 + int(guid)
299 if element.peer_proto:
301 if not element.tun_addr or not element.tun_port:
303 elif not element.peer_addr or not element.peer_port:
306 # both have addresses...
307 # ...the one with the lesser address listens
308 listening = element.tun_addr < element.peer_addr
309 element.listen = listening
312 def postconfigure_tunchannel(testbed_instance, guid):
314 TunChannel preconfiguration.
316 Initiates the forwarder thread for connecting tcp channels or
317 udp channels in general.
319 Should be adequate for most implementations.
321 element = testbed_instance._elements[guid]
327 def crossconnect_tunchannel_peer_init(proto, testbed_instance, tun_guid, peer_data,
328 preconfigure_tunchannel = preconfigure_tunchannel):
330 Cross-connection initialization.
331 Should be adequate for most implementations.
333 For use in metadata, bind the first "proto" argument with the connector type. Eg:
335 conn_init = functools.partial(crossconnect_tunchannel_peer_init, "tcp")
337 If you don't use the stock preconfigure function, specify your own as a keyword argument.
339 tun = testbed_instance._elements[tun_guid]
340 tun.peer_addr = peer_data.get("tun_addr")
341 tun.peer_proto = peer_data.get("tun_proto") or proto
342 tun.peer_port = peer_data.get("tun_port")
343 tun.tun_key = min(tun.tun_key, peer_data.get("tun_key"))
344 tun.tun_proto = proto
346 preconfigure_tunchannel(testbed_instance, tun_guid)
348 def crossconnect_tunchannel_peer_compl(proto, testbed_instance, tun_guid, peer_data,
349 postconfigure_tunchannel = postconfigure_tunchannel):
351 Cross-connection completion.
352 Should be adequeate for most implementations.
354 For use in metadata, bind the first "proto" argument with the connector type. Eg:
356 conn_init = functools.partial(crossconnect_tunchannel_peer_compl, "tcp")
358 If you don't use the stock postconfigure function, specify your own as a keyword argument.
360 # refresh (refreshable) attributes for second-phase
361 tun = testbed_instance._elements[tun_guid]
362 tun.peer_addr = peer_data.get("tun_addr")
363 tun.peer_proto = peer_data.get("tun_proto") or proto
364 tun.peer_port = peer_data.get("tun_port")
366 postconfigure_tunchannel(testbed_instance, tun_guid)
370 def wait_tunchannel(testbed_instance, guid):
372 Wait for the channel forwarder to be up and running.
374 Useful as a start function to assure proper startup synchronization,
375 be certain to start TunChannels before applications that might require them.
377 element = testbed_instance.elements[guid]