]> code.delx.au - monosys/blob - scripts/proxy.py
Fixed small bug in mediawrap.sh
[monosys] / scripts / proxy.py
1 #!/usr/bin/env python
2
3 """
4 Proxy Utility
5 -------------
6
7 With mode=basic any incoming connections on listen_port will be proxied
8 to host:port. The proxy will only accept connections from hosts in the
9 [allowed] section.
10
11 With mode=proxy the first two lines of incoming connections must be
12 'host\nport\n'. Once again only connections from hosts in the [allowed]
13 section will be accepted. The proxy will connect to host:port and pass
14 bytes in both directions.
15
16 The final mode, mode=interceptor is designed to be combined with a firewall
17 rule and another instance running mode=proxy on another computer.
18 Proxies running in interceptor mode listen on all interfaces. They make
19 connections to the host:port specified in the config file, passing the
20 'capturedhost\ncapturedport\n' onto the destination. They then pass bytes
21 in both directions.
22
23
24 Example - Basic Forwarder
25 -------------------------
26
27 Config to forward all packets from port 8000 on localhost to google.com:80
28 Connections will be accepted from whatever IP address alpha.example.com
29 and beta.example.com point to.
30
31 [proxy]
32 mode = basic
33 listen_port = 8000
34 host = google.com
35 port = 80
36
37 [allowed]
38 host1 = alpha.example.com
39 host2 = beta.example.com
40
41
42
43 Example - Interceptor Proxy Combo
44 ---------------------------------
45
46 Capture all packets destined for port 1935 and send them to an interceptor
47 configured to listen on example.com:9997.
48 On Linux:
49 # iptables -t nat -A PREROUTING -p tcp --dport 1935
50 -j REDIRECT --to-ports 9997
51 # iptables -t nat -A OUTPUT -p tcp --dport 1935
52 -j REDIRECT --to-ports 9997
53
54 On Mac OS X:
55 # ipfw add 50000 fwd 127.0.0.1,9997 tcp from any to any dst-port 1935
56
57 Config to forward these connections to proxy.example.com
58
59 [proxy]
60 mode = interceptor
61 listen_port = 9997
62 host = proxy.example.com
63 port = 9997
64
65
66
67 Config file for proxy.example.com
68
69 [proxy]
70 mode = proxy
71 listen_port = 9997
72
73 [allowed]
74 host1 = alpha.example.com
75 host2 = beta.example.com
76
77
78 """
79
80
81 import asyncore
82 import ConfigParser
83 import os
84 import socket
85 import struct
86 import sys
87
88
89 if sys.platform == "linux2":
90 try:
91 socket.SO_ORIGINAL_DST
92 except AttributeError:
93 # There is a missing const in the socket module... So we will add it now
94 socket.SO_ORIGINAL_DST = 80
95
96 def get_original_dest(sock):
97 '''Gets the original destination address for connection that has been
98 redirected by netfilter.'''
99 # struct sockaddr_in {
100 # short sin_family; // e.g. AF_INET
101 # unsigned short sin_port; // e.g. htons(3490)
102 # struct in_addr sin_addr; // see struct in_addr, below
103 # char sin_zero[8]; // zero this if you want to
104 # };
105 # struct in_addr {
106 # unsigned long s_addr; // load with inet_aton()
107 # };
108 # getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr_in *)&dstaddr, &dstlen);
109
110 data = sock.getsockopt(socket.SOL_IP, socket.SO_ORIGINAL_DST, 16)
111 _, port, a1, a2, a3, a4 = struct.unpack("!HHBBBBxxxxxxxx", data)
112 address = "%d.%d.%d.%d" % (a1, a2, a3, a4)
113 return address, port
114
115
116 elif sys.platform == "darwin":
117 def get_original_dest(sock):
118 '''Gets the original destination address for connection that has been
119 redirected by ipfw.'''
120 return sock.getsockname()
121
122
123
124 class Proxy(asyncore.dispatcher):
125 def __init__(self, sock):
126 asyncore.dispatcher.__init__(self, sock)
127 self.other = None
128 self.buffer = ""
129
130 def meet(self, other):
131 self.other = other
132 other.other = self
133
134 def handle_error(self):
135 print >>sys.stderr, "Proxy error:", sys.exc_info()
136 self.handle_close()
137
138 def handle_read(self):
139 data = self.recv(8192)
140 if len(data) > 0:
141 self.other.buffer += data
142
143 def handle_write(self):
144 sent = self.send(self.buffer)
145 self.buffer = self.buffer[sent:]
146
147 def writable(self):
148 return len(self.buffer) > 0
149
150 def handle_close(self):
151 if not self.other:
152 return
153 print >>sys.stderr, "Proxy closed"
154 self.other.close()
155 self.close()
156 self.other = None
157
158 class ConnectProxy(asyncore.dispatcher):
159 def __init__(self, sock):
160 asyncore.dispatcher.__init__(self, sock)
161 self.buffer = ""
162
163 def handle_error(self):
164 print >>sys.stderr, "ConnectProxy error:", sys.exc_info()
165 self.handle_close()
166
167 def handle_read(self):
168 self.buffer += self.recv(8192)
169 pos1 = self.buffer.find("\n")
170 if pos1 < 0:
171 return
172 host = self.buffer[:pos1].strip()
173 pos1 += 1
174 pos2 = self.buffer[pos1:].find("\n")
175 if pos2 < 0:
176 return
177 pos2 += pos1
178 port = int(self.buffer[pos1:pos2].strip())
179
180 self.buffer = self.buffer[pos2+1:]
181 self.done(host, port)
182
183 def handle_write(self):
184 pass
185
186 def handle_close(self):
187 print >>sys.stderr, "Proxy closed"
188 self.close()
189
190 def done(self, host, port):
191 print >>sys.stderr, "Forwarding connection", host, port
192
193 # Create server proxy
194 server_connection = socket.socket()
195 server_connection.connect((host, port))
196 server = Proxy(server_connection)
197
198 # Morph and connect
199 self.__class__ = Proxy
200 server.meet(self)
201 server.buffer = self.buffer
202 self.buffer = ""
203
204
205 class BasicForwarder(asyncore.dispatcher):
206 def __init__(self, listen_port, host, port, allowed):
207 asyncore.dispatcher.__init__(self)
208 self.host = host
209 self.port = port
210 self.allowed = allowed
211 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
212 self.bind(("", listen_port))
213 self.listen(5)
214
215 def handle_error(self):
216 print >>sys.stderr, "BasicForwarder error:", sys.exc_info()
217
218 def handle_accept(self):
219 client_connection, source_addr = self.accept()
220 if source_addr[0] not in map(socket.gethostbyname, self.allowed):
221 print >>sys.stderr, "Rejected connection from", source_addr
222 client_connection.close()
223 return
224
225 print >>sys.stderr, "Accepted connection from", source_addr
226 server_connection = socket.socket()
227 server_connection.connect((self.host, self.port))
228
229 # Hook the sockets up to the event loop
230 client = Proxy(client_connection)
231 server = Proxy(server_connection)
232 server.meet(client)
233
234 class Forwarder(asyncore.dispatcher):
235 def __init__(self, listen_port, allowed):
236 asyncore.dispatcher.__init__(self)
237 self.allowed = allowed
238 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
239 self.bind(("", listen_port))
240 self.listen(5)
241
242 def handle_error(self):
243 print >>sys.stderr, "Forwarder error:", sys.exc_info()
244
245 def handle_accept(self):
246 client_connection, source_addr = self.accept()
247 if source_addr[0] not in map(socket.gethostbyname, self.allowed):
248 print >>sys.stderr, "Rejected connection from", source_addr
249 client_connection.close()
250 return
251
252 print >>sys.stderr, "Accepted connection from", source_addr
253 ConnectProxy(client_connection)
254
255 class Interceptor(asyncore.dispatcher):
256 def __init__(self, listen_port, host, port):
257 asyncore.dispatcher.__init__(self)
258 self.host = host
259 self.port = port
260 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
261 self.bind(("0.0.0.0", listen_port))
262 self.listen(5)
263
264 def handle_error(self):
265 print >>sys.stderr, "Interceptor error!", sys.exc_info()
266
267 def handle_accept(self):
268 # Get sockets
269 client_connection, source_addr = self.accept()
270 dest = get_original_dest(client_connection)
271 print >>sys.stderr, "Accepted connection from", source_addr
272 server_connection = socket.socket()
273 server_connection.connect((self.host, self.port))
274
275 # Hook them up to the event loop
276 client = Proxy(client_connection)
277 server = Proxy(server_connection)
278 server.buffer += "%s\n%d\n" % dest
279 server.meet(client)
280
281
282 def main(listen_port, host, port, mode, allowed):
283 if mode == "basic":
284 proxy = BasicForwarder(listen_port, host, port, allowed)
285 elif mode == "proxy":
286 proxy = Forwarder(listen_port, allowed)
287 elif mode == "interceptor":
288 proxy = Interceptor(listen_port, host, port)
289 else:
290 print >>sys.stderr, "Unknown mode:", mode
291 return 1
292 asyncore.loop()
293
294
295 if __name__ == "__main__":
296 try:
297 if sys.argv[1] == "-d":
298 daemon = True
299 config = sys.argv[2]
300 else:
301 daemon = False
302 config = sys.argv[1]
303 except (IndexError, ValueError):
304 print >>sys.stderr, "Usage: %s [-d] config" % sys.argv[0]
305 sys.exit(1)
306
307 try:
308 c = ConfigParser.RawConfigParser()
309 c.read(config)
310 except:
311 print >>sys.stderr, "Error parsing config!"
312 sys.exit(1)
313
314 def guard(func, message):
315 try:
316 return func()
317 except:
318 print >>sys.stderr, "Error:", message
319 raise
320 sys.exit(1)
321
322 mode = guard(lambda:c.get("proxy", "mode").lower(),
323 "mode is a required field")
324
325 listen_port = guard(lambda:c.getint("proxy", "listen_port"),
326 "listen_port is a required field")
327
328 if mode in ["basic", "interceptor"]:
329 text = "%%s is a required field for mode=%s" % mode
330 host = guard(lambda:c.get("proxy", "host"), text % "host")
331 port = guard(lambda:c.getint("proxy", "port"), text % "port")
332 else:
333 host = None
334 port = None
335
336 if mode in ["basic", "proxy"]:
337 allowed = guard(lambda:c.items("allowed"),
338 "[allowed] section is required for mode=%s" % mode)
339 allowed = [h for _,h in c.items("allowed")]
340 else:
341 allowed = None
342
343
344 if not daemon:
345 try:
346 main(listen_port, host, port, mode, allowed)
347 except KeyboardInterrupt:
348 print
349 else:
350 os.close(0)
351 os.close(1)
352 os.close(2)
353 os.open("/dev/null", os.O_RDONLY)
354 os.open("/dev/null", os.O_RDWR)
355 os.dup(1)
356
357 if os.fork() == 0:
358 # We are the child
359 try:
360 sys.exit(main(listen_port, host, port, mode, allowed))
361 except KeyboardInterrupt:
362 print
363 sys.exit(0)
364