10 from tunchannel import tun_fwd, udp_establish, tcp_establish
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/cipher: 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/cipher: 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 with_pi: set if the incoming packet stream (see tun_socket)
34 contains PI headers - if so, they will be stripped.
36 ethernet_mode: set if the incoming packet stream is
37 composed of ethernet frames (as opposed of IP packets).
39 tun_socket: a socket or file object that can be read
40 from and written to. Packets will be read when available,
41 remote packets will be forwarded as writes.
42 A socket should be of type SOCK_SEQPACKET (or SOCK_DGRAM
43 if not possible), a file object should preserve packet
44 boundaries (ie, a pipe or TUN/TAP device file descriptor).
46 trace_target: a file object where trace output will be sent.
47 It cannot be changed after launch.
48 By default, it's sys.stderr
52 # Some operational attributes
53 self.ethernet_mode = True
56 # These get initialized when the channel is configured
57 # They're part of the TUN standard attribute set
60 self.tun_cipher = 'AES'
62 # These get initialized when the channel is connected to its peer
63 self.peer_proto = None
66 self.peer_cipher = None
68 # These get initialized when the channel is connected to its iface
69 self.tun_socket = None
71 # same as peer proto, but for execute-time standard attribute lookups
76 self._terminate = [] # terminate signaller
77 self._exc = [] # exception store, to relay exceptions from the forwarder thread
78 self._connected = threading.Event()
79 self._forwarder_thread = None
82 self.stderr = sys.stderr
84 # Generate an initial random cryptographic key to use for tunnelling
85 # Upon connection, both endpoints will agree on a common one based on
87 self.tun_key = os.urandom(32).encode("base64").strip()
91 return "%s<%s %s:%s %s %s:%s %s>" % (
92 self.__class__.__name__,
94 self.tun_addr, self.tun_port,
96 self.peer_addr, self.peer_port,
101 # self.tun_proto is only set if the channel is connected
102 # launch has to be a no-op in unconnected channels because
103 # it is called at configuration time, which for cross connections
104 # happens before connection.
106 if not self._forwarder_thread:
110 if self._forwarder_thread:
114 if self._forwarder_thread:
115 self._connected.wait()
116 for exc in self._exc:
118 eTyp, eVal, eLoc = exc
119 raise eTyp, eVal, eLoc
122 if self._forwarder_thread:
123 if not self._terminate:
124 self._terminate.append(None)
125 self._forwarder_thread.join()
128 # Launch forwarder thread with a weak reference
129 # to self, so that we don't create any strong cycles
130 # and automatic refcounting works as expected
131 self._forwarder_thread = threading.Thread(
132 target = self._forwarder,
133 args = (weakref.ref(self),) )
134 self._forwarder_thread.start()
137 def _forwarder(weak_self):
139 weak_self().__forwarder(weak_self)
143 # store exception and wake up anyone waiting
144 self._exc.append(sys.exc_info())
145 self._connected.set()
148 def __forwarder(weak_self):
149 # grab strong reference
154 peer_port = self.peer_port
155 peer_addr = self.peer_addr
156 peer_proto= self.peer_proto
157 peer_cipher=self.peer_cipher
159 local_port = self.tun_port
160 local_addr = self.tun_addr
161 local_proto = self.tun_proto
162 local_cipher= self.tun_cipher
165 ether_mode = self.ethernet_mode
166 with_pi = self.with_pi
168 if local_proto != peer_proto:
169 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (local_proto, peer_proto)
171 if local_cipher != peer_cipher:
172 raise RuntimeError, "Peering cipher mismatch: %s != %s" % (local_cipher, peer_cipher)
174 if not peer_port or not peer_addr:
175 raise RuntimeError, "Misconfigured peer for: %s" % (self,)
177 if not local_port or not local_addr:
178 raise RuntimeError, "Misconfigured TUN: %s" % (self,)
180 TERMINATE = self._terminate
181 cipher_key = self.tun_key
182 tun = self.tun_socket
183 udp = local_proto == 'udp'
186 raise RuntimeError, "Unconnected TUN channel %s" % (self,)
188 if local_proto == 'udp':
189 rsock = udp_establish(TERMINATE, local_addr, local_port,
190 peer_addr, peer_port)
191 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
192 elif local_proto == 'tcp':
193 rsock = tcp_establish(TERMINATE, local_addr, local_port,
194 peer_addr, peer_port)
195 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
197 raise RuntimeError, "Bad protocol for %s: %r" % (self,local_proto)
199 # notify that we're ready
200 self._connected.set()
202 # drop strong reference
205 print >>sys.stderr, "Connected"
208 ether_mode = ether_mode,
209 cipher_key = cipher_key,
211 TERMINATE = TERMINATE,
213 cipher = local_cipher
220 def create_tunchannel(testbed_instance, guid, devnull = []):
222 TunChannel factory for metadata.
223 By default, silences traceing.
225 You can override the created element's attributes if you will.
228 # just so it's not open if not needed
229 devnull.append(open("/dev/null","w"))
230 element = TunChannel()
231 element.stderr = devnull[0] # silence tracing
232 testbed_instance._elements[guid] = element
234 def preconfigure_tunchannel(testbed_instance, guid):
236 TunChannel preconfiguration.
238 It initiates the forwarder thread for listening tcp channels.
240 Takes the public address from the operating system, so it should be adequate
241 for most situations when the TunChannel forwarder thread runs in the same
242 process as the testbed controller.
244 element = testbed_instance._elements[guid]
246 # Find external interface, if any
247 public_addr = os.popen(
249 "| grep $(ip route | grep default | awk '{print $3}' "
250 "| awk -F. '{print $1\"[.]\"$2}') "
251 "| head -1 | awk '{print $2}' "
252 "| awk -F : '{print $2}'").read().rstrip()
253 element.tun_addr = public_addr
255 # Set standard TUN attributes
256 if not element.tun_port and element.tun_addr:
257 element.tun_port = 15000 + int(guid)
259 def postconfigure_tunchannel(testbed_instance, guid):
261 TunChannel preconfiguration.
263 Initiates the forwarder thread for connecting tcp channels or
264 udp channels in general.
266 Should be adequate for most implementations.
268 element = testbed_instance._elements[guid]
272 def crossconnect_tunchannel_peer_init(proto, testbed_instance, tun_guid, peer_data,
273 preconfigure_tunchannel = preconfigure_tunchannel):
275 Cross-connection initialization.
276 Should be adequate for most implementations.
278 For use in metadata, bind the first "proto" argument with the connector type. Eg:
280 conn_init = functools.partial(crossconnect_tunchannel_peer_init, "tcp")
282 If you don't use the stock preconfigure function, specify your own as a keyword argument.
284 tun = testbed_instance._elements[tun_guid]
285 tun.peer_addr = peer_data.get("tun_addr")
286 tun.peer_proto = peer_data.get("tun_proto") or proto
287 tun.peer_port = peer_data.get("tun_port")
288 tun.peer_cipher = peer_data.get("tun_cipher")
289 tun.tun_key = min(tun.tun_key, peer_data.get("tun_key"))
290 tun.tun_proto = proto
292 preconfigure_tunchannel(testbed_instance, tun_guid)
294 def crossconnect_tunchannel_peer_compl(proto, testbed_instance, tun_guid, peer_data,
295 postconfigure_tunchannel = postconfigure_tunchannel):
297 Cross-connection completion.
298 Should be adequeate for most implementations.
300 For use in metadata, bind the first "proto" argument with the connector type. Eg:
302 conn_init = functools.partial(crossconnect_tunchannel_peer_compl, "tcp")
304 If you don't use the stock postconfigure function, specify your own as a keyword argument.
306 # refresh (refreshable) attributes for second-phase
307 tun = testbed_instance._elements[tun_guid]
308 tun.peer_addr = peer_data.get("tun_addr")
309 tun.peer_proto = peer_data.get("tun_proto") or proto
310 tun.peer_port = peer_data.get("tun_port")
311 tun.peer_cipher = peer_data.get("tun_cipher")
313 postconfigure_tunchannel(testbed_instance, tun_guid)
315 def prestart_tunchannel(testbed_instance, guid):
317 Wait for the channel forwarder to be up and running.
319 Useful as a pre-start function to assure proper startup synchronization,
320 be certain to start TunChannels before applications that might require them.
322 element = testbed_instance.elements[guid]