Skip to content

Commit db061e0

Browse files
authored
pfctl support
1 parent b994971 commit db061e0

File tree

2 files changed

+43
-15
lines changed

2 files changed

+43
-15
lines changed

pproxy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from pproxy import proto
33

44
__title__ = 'pproxy'
5-
__version__ = "1.6.4"
5+
__version__ = "1.6.6"
66
__description__ = "Proxy server that can tunnel among remote servers by regex rules."
77
__author__ = "Qian Wenjie"
88
__license__ = "MIT License"

pproxy/proto.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import asyncio, socket, urllib.parse, time, re, base64, hmac, struct, hashlib
1+
import asyncio, socket, urllib.parse, time, re, base64, hmac, struct, hashlib, fcntl
22

33
HTTP_LINE = re.compile('([^ ]+) +(.+?) +(HTTP/[^ ]+)$')
44
packstr = lambda s, n=1: len(s).to_bytes(n, 'big') + s
@@ -232,25 +232,53 @@ async def http_channel(self, reader, writer, stat_bytes, _):
232232
finally:
233233
writer.close()
234234

235-
SO_ORIGINAL_DST = 80
236-
class Redirect(BaseProtocol):
237-
name = 'redir'
235+
class Transparent(BaseProtocol):
238236
def correct_header(self, header, auth, sock, **kw):
239-
try:
240-
buf = sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16)
241-
assert len(buf) == 16
242-
remote = (socket.inet_ntoa(buf[4:8]), int.from_bytes(buf[2:4], 'big'))
243-
assert sock.getsockname() != remote
244-
except Exception:
237+
remote = self.query_remote(sock)
238+
if remote is None or sock.getsockname() == remote:
245239
return False
246240
return auth and header == auth[:1] or not auth
247241
async def parse(self, reader, auth, authtable, sock, **kw):
248242
if auth:
249243
if (await reader.read_n(len(auth)-1)) != auth[1:]:
250-
raise Exception('Unauthorized Redir')
244+
raise Exception(f'Unauthorized {self.name}')
251245
authtable.set_authed()
252-
buf = sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16)
253-
return socket.inet_ntoa(buf[4:8]), int.from_bytes(buf[2:4], 'big'), b''
246+
remote = self.query_remote(sock)
247+
return remote[0], remote[1], b''
248+
249+
SO_ORIGINAL_DST = 80
250+
SOL_IPV6 = 41
251+
class Redirect(Transparent):
252+
name = 'redir'
253+
def query_remote(self, sock):
254+
try:
255+
#if sock.family == socket.AF_INET:
256+
if "." in sock.getsockname()[0]:
257+
buf = sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16)
258+
assert len(buf) == 16
259+
return socket.inet_ntoa(buf[4:8]), int.from_bytes(buf[2:4], 'big')
260+
else:
261+
buf = sock.getsockopt(SOL_IPV6, SO_ORIGINAL_DST, 28)
262+
assert len(buf) == 28
263+
return socket.inet_ntop(socket.AF_INET6, buf[8:24]), int.from_bytes(buf[2:4], 'big')
264+
except Exception:
265+
pass
266+
267+
class Pf(Transparent):
268+
name = 'pf'
269+
def query_remote(self, sock):
270+
try:
271+
src = sock.getpeername()
272+
dst = sock.getsockname()
273+
src_ip = socket.inet_pton(sock.family, src[0])
274+
dst_ip = socket.inet_pton(sock.family, dst[0])
275+
pnl = bytearray(struct.pack('!16s16s32xHxxHxx8xBBxB', src_ip, dst_ip, src[1], dst[1], sock.family, socket.IPPROTO_TCP, 2))
276+
if not hasattr(self, 'pf'):
277+
self.pf = open('/dev/pf', 'a+b')
278+
fcntl.ioctl(self.pf.fileno(), 0xc0544417, pnl)
279+
return socket.inet_ntop(sock.family, pnl[48:48+len(src_ip)]), int.from_bytes(pnl[76:78], 'big')
280+
except Exception:
281+
pass
254282

255283
async def parse(protos, reader, **kw):
256284
proto = next(filter(lambda p: p.correct_header(None, **kw), protos), None)
@@ -267,7 +295,7 @@ async def parse(protos, reader, **kw):
267295
return (proto,) + ret
268296
raise Exception(f'Unsupported protocol {header}')
269297

270-
MAPPINGS = dict(direct=Direct(), http=HTTP(), socks5=Socks5(), socks4=Socks4(), ss=Shadowsocks(), ssr=ShadowsocksR(), redir=Redirect(), ssl='', secure='')
298+
MAPPINGS = dict(direct=Direct(), http=HTTP(), socks5=Socks5(), socks4=Socks4(), ss=Shadowsocks(), ssr=ShadowsocksR(), redir=Redirect(), pf=Pf(), ssl='', secure='')
271299
MAPPINGS['socks'] = MAPPINGS['socks5']
272300

273301
def get_protos(rawprotos):

0 commit comments

Comments
 (0)