forked from qwj418/Flora_Pac
-
Notifications
You must be signed in to change notification settings - Fork 8
/
flora_pac
executable file
·375 lines (319 loc) · 10.9 KB
/
flora_pac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#!/usr/bin/env python
# Flora_Pac by @leaskh
# www.leaskh.com, i@leaskh.com
# Optimized by @yaleh
# Based on chnroutes project (by Numb.Majority@gmail.com)
import re
import urllib2
import argparse
import math
import ipaddress
# tunable with primes for balancing of matching times and code size
# increase it for better performance
# decrease it for smaller size
global HASH_BASE, MASK_STEP, min_prefixlen, max_prefixlen
HASH_BASE = 3011
MASK_STEP = 2
min_prefixlen = 32
max_prefixlen = 0
def generate_balanced_proxy(proxies, balance):
if balance == 'no':
return "return '%s' ;" % (';'.join(proxies))
elif balance == 'local_ip':
return '''
var local_ip_balance = function(proxies) {
var i, k, l, myseg, s, _i;
myseg = parseInt(myIpAddress().split(".")[3]);
l = proxies.length;
k = myseg % l;
s = '';
for (i = _i = 0; 0 <= l ? _i < l : _i > l; i = 0 <= l ? ++_i : --_i) {
s += proxies[(k + i) % l];
}
return s;
};
''' + """
return local_ip_balance([%s]);
""" % (','.join(map(lambda p: "'%s'" % p, proxies)))
elif balance == 'host':
return '''
var target_host_balance = function(proxies, host) {
var hash_string, i, k, l, s, _i;
hash_string = function(s) {
var c, hash, _i, _len;
hash = 0;
for (_i = 0, _len = s.length; _i < _len; _i++) {
c = s[_i];
hash = (hash << 5) - hash + c.charCodeAt(0);
hash = hash & hash & 0xFFFF;
hash &= 0xFFFF;
}
return hash;
};
l = proxies.length;
k = hash_string(host) % l;
s = '';
for (i = _i = 0; 0 <= l ? _i < l : _i > l; i = 0 <= l ? ++_i : --_i) {
s += proxies[(k + i) % l];
}
return s;
};
''' + """
return target_host_balance([%s], host);
""" % (','.join(map(lambda p: "'%s'" % p, proxies)))
def generate_no_proxy(no_proxy):
s = ''
for n in no_proxy:
try:
# single IP address
ip = ipaddress.ip_address(u"%s" % n)
s += " ip == '%s' ||" % n
continue;
except ValueError:
pass
try:
# network with mask or mask prefix
net = ipaddress.ip_network(u"%s" % n)
s += " isInNet(ip, '%s', '%s') ||" % (net.network_address, net.netmask)
continue
except ValueError:
pass
# hostname
s += " host == '%s' ||" % n
return s
def generate_pac(proxies, balance, no_proxy):
# TODOs: fregment net, save prefix/24 to a 2D array with the 1st-D index of a mod-x key
results = merge_all(fetch_ip_data())
for net in results:
global min_prefixlen, max_prefixlen
i = net.prefixlen
if i < min_prefixlen:
min_prefixlen = i
if i > max_prefixlen:
max_prefixlen = i
print "PrefixLen: [%d, %d]" % (min_prefixlen, max_prefixlen)
# print "Hashing...\n"
hashed_results = hash_nets(fregment_nets(results), HASH_BASE)
pacfile = 'flora_pac.pac'
rfile = open(pacfile, 'w')
strLines = ("""
// Flora_Pac by @leaskh
// www.leaskh.com, i@leaskh.com
// Optimized by @yaleh
function FindProxyForURL(url, host) {
var HASH_BASE, MASK_STEP, a, dot2num, hash_masked_ip, hashed_nets, i, lookup_ip, max_prefixlen, min_prefixlen, num2dot, prefixlen2mask, rebuild_net, _i, _j, _len, _len1;
dot2num = function(dot) {
var d;
d = dot.split(".");
return ((((((+d[0]) * 256) + (+d[1])) * 256) + (+d[2])) * 256) + (+d[3]);
};
num2dot = function(ip) {
return [ip >>> 24, ip >>> 16 & 0xFF, ip >>> 8 & 0xFF, ip & 0xFF].join(".");
};
hash_masked_ip = function(ip, mask_len, mod_base) {
var i, net, offset, _i;
offset = 32 - mask_len;
net = ip >>> offset;
for (i = _i = 0; 0 <= offset ? _i < offset : _i > offset; i = 0 <= offset ? ++_i : --_i) {
net *= 2;
}
return net % mod_base;
};
prefixlen2mask = function(prefixlen) {
var imask;
imask = 0xFFFFFFFF << (32 - prefixlen);
return (imask >> 24 & 0xFF) + '.' + (imask >> 16 & 0xFF) + '.' + (imask >> 8 & 0xFF) + '.' + (imask & 0xFF);
};
rebuild_net = function(pair) {
var masks, result;
result = ['', ''];
result[0] = num2dot(pair[0] << (32 - pair[1]));
result[1] = prefixlen2mask(pair[1]);
return result;
};
lookup_ip = function(ip) {
var i, k, len, n, n_ip, _i, _len, _ref;
len = min_prefixlen;
n_ip = dot2num(ip);
while (len <= max_prefixlen) {
k = hash_masked_ip(n_ip, len, HASH_BASE);
_ref = hashed_nets[k];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
i = _ref[_i];
n = rebuild_net(i);
if (isInNet(ip,n[0],n[1])) {
return true;
}
}
len += MASK_STEP;
}
return false;
};
"""
)
strLines += """
HASH_BASE = %d;
MASK_STEP = %d;
min_prefixlen = %d;
max_prefixlen = %d;
""" % (HASH_BASE, MASK_STEP, min_prefixlen, max_prefixlen)
intLines = 0
for i in xrange(min_prefixlen, max_prefixlen+1, MASK_STEP):
strLines +="""
var m%d = %d;
""" % (i, i)
strLines += """
var empty_array = [];
var hashed_nets = [
"""
none_empty_count = 0
for i in range(len(hashed_results)):
# print "%d: %d" % (i, len(hashed_results[i]))
if len(hashed_results[i]) > 0:
none_empty_count += 1
strLines += "\n ["
for net in hashed_results[i]:
strLines += "\n [%d, m%d]," % (int(net.network_address) >> (32 - net.prefixlen),
net.prefixlen)
strLines += "\n ],"
else:
strLines += "\n empty_array,"
avg_len = float(len(results)) / none_empty_count
print "Avarage matching length: %f" % avg_len
steps = (max_prefixlen - min_prefixlen) / MASK_STEP + 1
print "Steps to match: %d" % steps
print "Matching cost est.: %f" % (avg_len * steps)
strLines = strLines + ("""
];
if (isPlainHostName(host)
|| (host == '127.0.0.1')
|| (host == 'localhost')
) {
return 'DIRECT';
}
var ip = dnsResolve(host);
if (ip == null || ip == '' || %s lookup_ip(ip)) {
return 'DIRECT';
}
%s
}
""" % (generate_no_proxy(no_proxy), generate_balanced_proxy(proxies, balance)))
rfile.write(strLines)
rfile.close()
print ("Rules: %d items.\n"
"Usage: Use the newly created %s as your web browser's automatic "
"proxy configuration (.pac) file."%(intLines, pacfile))
def merge_nets(net1, net2):
super_net1 = net1.supernet()
super_net2 = net2.supernet()
if super_net1 == super_net2 \
and super_net1.network_address == net1.network_address \
and super_net1.broadcast_address == net2.broadcast_address:
return super_net1
return None
def merge_all(networks):
i = 1
while i < len(networks):
if i == 0:
i += 1
continue
merged_net = merge_nets(networks[i-1], networks[i])
if merged_net is None:
i += 1
continue
networks[i-1] = merged_net
networks.pop(i)
if i > 1:
i -= 1
return networks
def fregment_net(net):
# return networks of mask 4, 8, 12, 16, 20, 24
# print "Fregment %s" % str(net)
subnets = []
target_prefixlen = (net.prefixlen - 1) / MASK_STEP * MASK_STEP + MASK_STEP
for a in net.subnets(target_prefixlen - net.prefixlen):
subnets.append(a)
return subnets
def fregment_nets(nets):
results = []
for net in nets:
results += fregment_net(net)
return results
def hash_address(address, mod_base):
# print "Hash %s" % str(address)
return int(address) % mod_base
def hash_nets(nets, mod_base):
hashed = [[] for i in range(mod_base)]
for net in nets:
i = hash_address(net.network_address, mod_base)
hashed[i].append(net)
return hashed
def fetch_ip_data():
#fetch data from apnic
print "Fetching data from apnic.net, it might take a few minutes, please wait..."
url=r'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest'
data=urllib2.urlopen(url).read()
cnregex=re.compile(r'apnic\|cn\|ipv4\|[0-9\.]+\|[0-9]+\|[0-9]+\|a.*',re.IGNORECASE)
cndata=cnregex.findall(data)
results=[]
for item in cndata:
unit_items=item.split('|')
starting_ip=unit_items[3]
num_ip=int(unit_items[4])
imask=0xffffffff^(num_ip-1)
#convert to string
imask=hex(imask)[2:]
mask=[0]*4
mask[0]=imask[0:2]
mask[1]=imask[2:4]
mask[2]=imask[4:6]
mask[3]=imask[6:8]
#convert str to int
mask=[ int(i,16 ) for i in mask]
mask="%d.%d.%d.%d"%tuple(mask)
#mask in *nix format
mask2=32-int(math.log(num_ip,2))
net = ipaddress.ip_network(u"%s/%s" % (starting_ip, mask))
results.append(net)
return results
if __name__=='__main__':
parser=argparse.ArgumentParser(description="Generate proxy auto-config rules.")
parser.add_argument('-x', '--proxy',
dest = 'proxy',
default = ['SOCKS 127.0.0.1:8964'],
nargs = '*',
help = "Proxy Server, accepts multple values for balancing, i.e.: "
"-x 'SOCKS 127.0.0.1:8964' 'SOCKS5 127.0.0.1:1984' 'PROXY 127.0.0.1:1989'")
parser.add_argument('-m', '--mask-step',
type = int,
dest = 'mask_step',
default = 2,
help = "Step size of mask fregment (default: %(default)s)")
parser.add_argument('-s', '--hash-base',
type = int,
dest = 'hash_base',
default = 3011,
help = 'Size of the address hash table (default: %(default)s)')
parser.add_argument("-b", '--balance',
choices=["no", "local_ip", "host"],
dest = 'balance',
default = "no",
help = "Balancing policy: "
"'no' for no balancing, "
"'local_ip' for balancing by local IP, "
"'host' for balancing by Web site's hostname "
"(default: %(default)s)")
parser.add_argument('-n', '--no-proxy',
dest = 'no_proxy',
nargs = '*',
default = ['192.168.0.0/24'],
help = "Don't proxy request to the specified IP or network, i.e.: "
"'192.168.0.0/24' '172.16.0.0/255.255.0.0 ' . "
"(default: %(default)s)")
args = parser.parse_args()
# global HASH_BASE, MASK_STEP
HASH_BASE = args.hash_base
MASK_STEP = args.mask_step
# for p in args.proxy:
# print p
generate_pac(args.proxy, args.balance, args.no_proxy)