本来没怎么关注这个东西,支持少非主流,IP over TCP,也是因为一些奇怪的事突然有了这方面需求了,才开始了解它。
目前在 Linux,服务器似乎只有 SoftEther 这个实现。这软件也是挺拼的,把各种流行的非主流的 VPN 协议都给实现了一遍,跨平台,GPL。还提供傻瓜化的 GUI 管理,中日英三语界面,简直就是部署 VPN 的大杀器。只是一来太重,二来对 RADIUS 支持不全,不太符合需求。
因为内部走的是 PPP,如果只是调用 pppd 并转发数据,由 pppd 负责用户认证等麻烦事,自己实现一个目测也不是太复杂。加上微软的官方文档甚详,近来又一直想学一下 Twisted 但又不知如何入手,决定来写一个试试。
托 Twisted 的简单易用,挺顺利地初步完成了这个 SSTP 服务器。
代码放在 GitHub 和 PyPI 上了,sorz/sstp-server 。
供参考,详见sstpd --help
:
sudo apt-get install python-dev python-pip python-twisted sudo pip install sstp-server sudo sstpd -c cert.pem --local 10.0.0.1 --remote 10.0.0.0/24
关于证书,请参考 HTTPS 证书相关教程。
别忘了建/etc/ppp/options.sstpd
,一个例子:
name sstpd require-mschap-v2 nologfd nodefaultroute ms-dns 8.8.8.8 ms-dns 8.8.4.4
需要 IPv6 支持的,可加参数--listen ::
。
折腾过程中遇到的主要障碍还是关于 PPP 的。简单地转发是不行的,因为每个 SSTP 包中只允许放入一个 PPP frame,需要自行分离出每个 frame。
PPP 这个古老的协议比预想中的要复杂。Windows 在 SSTP 中使用的其实是这个 HDLC-like Framing。找了一份 SSTP 客户端的代码,它是自行对两种格式进行了转换。好在后来发现 pppd 其实自身就支持 HDLC(sync 参数),于是成功偷懒。
但后来发现 HDLC 似乎需要 Linux 内核支持(CONFIG_PPP_SYNC_TTY = yes
)。
更新:
果然偷懒失败了,用 HDLC 取巧的方法是不行的,偶尔会有 frame 被截断,后面就全乱了。于是还是老老实实地照着 RFC 1662 来做 framing,自行 (un)escape 一些字符。但是这样性能变得很糟糕,试着把这部分用 C 扩展重写了一遍。想来这还是我第一次出于解决问题的需要写 C 呢……
感谢 @deba12 指出了这个问题,并协助测试、改善性能。
目前这个实现其实是不完整的,没有实现 Crypto binding 部分,导致其可能遭受中间人攻击。
使用了 SSL 还会遭受中间人攻击?微软在文档末尾提供了一个这种攻击的情境,挺有意思的。
攻击者建立一个假 Wi-Fi AP,然后诱骗用户连接。
Wi-Fi 使用 802.11 EAP 进行认证,用户以为他是在登录 Wi-Fi,但实际上,攻击者将这个认证请求转发给了 SSTP(PPP) 服务器!用户确实在和真的认证服务器在对话,只不过认证的不是 Wi-Fi 而是 SSTP 服务。
Crypto binding 可以防止这种攻击,想详细了解请参见微软文档。
但实现这个有些复杂,我这边的使用情境暂时没有这个需求,就先放一放了。
(懒你就直说 _(:з」∠)_
Computer Networks 这门课,需要用到 Wireshark 这个抓包软件查看网络流量。
写了这玩意,通过广播一些特制的数据,可以在其他人的机子上刷出点有趣(?)的东西:
如图,一眼就能看出不正常.. 其实就是乱发 HTTP Response,并把原来显示状态码(200、404 …)的部分,改成了其他文字…
由于需要乱发,用了 WinPcap,它提供了一些 API 能够比较方便地跳过系统的协议栈,让网卡发送完全自定义的数据。这里使用了它的一个 Python 绑定 winpcapy。
最后需要发送的是以太网帧,也就是说,要自己从 HTTP、TCP、IP 一路构建下来。为了简化工作使用了 dpkt ,里面提供了丰富的协议支持,能很方便地构建数据包。
一点小细节:Wireshark 会分析 TCP 协议,并把不正常的包用红色标出。为了避免这样,每个包的目的端口都是随机选取的,让 Wireshark 认为每个包都属于不同的 TCP 会话。当然换成随机的 IP 也没问题。
代码贴 Gist 上了 Gist 9721330。
玩法:
net_test.py
得到网卡列表和对应的地址(e.g rpcap://\Device\NPF_{x-x-x-x-x});net_test.py rpcap://\Device\NPF_{x-x-x-x-x}
发送。net_test.py <interface> [repeat_times [message]]
,可选重复次数与消息内容(默认是 5 次、发送随机表情)。注意需要管理员权限,系统如有开 IP Forwarding 功能需关闭。
代码不复杂,想玩更多花样可以自己改..
注意这样不按协议标准发数据的事.. 还是少做得好.. 玩玩就好,切勿滥用..
于是做了不少尝试,比如宿舍内通过 VPN 连接自家路由器,然后将数据包通过自家路由器转发到宿舍路由器上,但这样做怎加了额外的延时,且受家里带宽限制。再比如通过 UDP 打洞 来实现双方直连..
但是如此一来,要用 UDP 来传送 TCP 内容,就需要自己实现 TCP 的各种功能,还要追踪连接,太过复杂大大超出自己能力水平(太弱了。所以就想着用各种办法偷懒。
上学期做的一个尝试是,用 tun 虚拟网卡,直接转发IP包。如此一来,TCP 全由系统实现,避开了这些复杂的事… (参见《OpenWrt 上使用 Python 操作 TAP/TUN》)最后止步于放假回家,和 Windows 下修改路由表的一些问题(很想具体喷一下但是这就扯远太了)。
前段时间忽然想到,OpenVPN 可使用 UDP 建立连接。如此一来,只要自己先(用少量代码)完成 UDP 打洞,而后的操作就可以全权交由(相当完善的) OpenVPN 处理,工作量、复杂度急剧下降。
先简单重复一下 UDP 打洞的具体过程。
实际使用时可能会受到一些限制,宿舍网络不存在此问题不再累述。
首先,在宿舍内架 OpenVPN。我在 Cubieboard 上的 Ubuntu 架的,就按通常设置就行。
UDP 打洞要求在客户端连接前,服务器发送一个 UDP 包到客户端,且必须从 OpenVPN 监听端口上发出。为了在发送时不中断 openvpnd,我用了 pylibnet 跳过系统 socket API 直接发送该包。代码如下:
#!/usr/bin/env python | |
#encoding: utf-8 | |
import libnet | |
from libnet.constants import RAW4, RESOLVE, IPV4_H, UDP_H, IPPROTO_UDP | |
IFACE = 'wlan2' # Sending via the interface. | |
def sendto(sport, address): | |
l = libnet.context(RAW4, IFACE) | |
dest_ip = l.name2addr4(address[0], RESOLVE) | |
l.build_udp(sp=sport, dp=address[1], | |
payload='\x00hehe' | |
l.autobuild_ipv4(len=(IPV4_H + UDP_H), | |
prot=IPPROTO_UDP, dst=dest_ip) | |
l.write() | |
if __name__ == '__main__': | |
sendto(6000, ('vpn.sorz.org', 6001)) |
这段代码(ovpn-snatd.py)用于告知 VPS,OpenVPN 服务器的地址端口。同时接收由 VPS 转发的客户端地址端口信息:
#!/usr/bin/env python | |
import socket | |
import sendudp | |
SNAT_BIND_PORT = 6001 | |
SNAT_SERVER = ('sorz.org', 6002) | |
OPENVPN_BIND_PORT = 1194 | |
def main(): | |
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
sock.bind(('', SNAT_BIND_PORT)) | |
sock.settimeout(20) | |
while True: | |
sock.sendto('\x00', SNAT_SERVER) | |
try: | |
sock.recv(1024) # Ignore ping response | |
data = sock.recv(1024) # Receiving users' connection request. | |
except socket.timeout: | |
continue | |
if data[0] != '\x03': | |
continue | |
print('a new connection from ' + data[1:]) | |
client = data[1:].split(':') | |
sendudp.sendto(OPENVPN_BIND_PORT, (client[0], int(client[1]))) | |
if __name__ == '__main__': | |
main() |
然后,让 VPS 帮助传递端口双方外网端口地址,总共四段代码:
#!/usr/bin/python | |
import socket | |
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
s.bind(('0.0.0.0', 6000)) | |
while True: | |
data, addr = s.recvfrom(1024) | |
s.sendto(str(addr[1]), addr) |
# (...) | |
SNAT_SERVER_PORT = 6002 | |
@app.route('/ovpn/connect') | |
def movpn_openvpn(): | |
server = get_memcache().get('movpn.openvpn.server') | |
if not server: | |
return 'Server is not running.', 404 | |
addr = request.remote_addr | |
if addr.startswith('::ffff:'): | |
addr = addr[7:] | |
port = request.args.get('port', 1194) | |
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
s.sendto('\x02%s:%s' % (addr, port), ('localhost', SNAT_SERVER_PORT)) | |
return server | |
# (...) |
ovpn-snatd.py
,同时返回给用户 OpenVPN 服务器的外网地址端口(为了方便,通过 memcache 读取)。
#!/usr/bin/env python | |
import memcache | |
SNAT_SERVER_PORT = 6002 # local listening | |
OPENVPN_BIND_PORT = 1194 | |
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
s.bind(('0.0.0.0', SNAT_SERVER_PORT)) | |
mc = memcache.Client(['127.0.0.1:11211']) | |
server = mc.get('movpn.openvpn.server') | |
if server: | |
server = (server.split(':')[0], OPENVPN_BIND_PORT) | |
while True: | |
data, addr = s.recvfrom(1024) | |
if data[0] == '\x00': # From openvpn server | |
if addr != server: | |
server = addr | |
mc.set('movpn.openvpn.server', '%s:%s' % (server[0], OPENVPN_BIND_PORT)) | |
s.sendto('\x01', addr) | |
elif data[0] == '\x02': # From local web server (user's conn request) | |
if addr[0] != '127.0.0.1': | |
print('\x02 != localhost') | |
continue | |
print('new connect from ' + data[1:]) | |
s.sendto('\x03%s' % data[1:], server) | |
else: | |
print('unknown') |
用户启动 OpenVPN 前,先随机生成一个 UDP 端口,通过getported.py
取得该端口对应的外网端口。然后通过 HTTP 将端口号传递给view.py
,同时取得服务器地址。最后释放该 UDP 端口,让给 OpenVPN ,由它建立连接。代码如下:
#!/usr/bin/env python | |
import random | |
import logging | |
import subprocess | |
import time | |
import socket | |
import requests | |
NAT_REQUEST_URL = 'https://vpn.sorz.org/ovpn/connect?port=%s' | |
SNAT_SERVER = ('vpn.sorz.org', 6000) | |
def get_default_param(): | |
return ['bin\openvpn.exe', | |
'--client', | |
'--bind', | |
'--local', '0.0.0.0', | |
'--proto', 'udp', | |
'--dev', 'tun', | |
'--resolv-retry', 'infinite', | |
'--persist-key', | |
'--persist-tun', | |
'--ca', 'ca.crt', | |
'--cert', 'testclient.crt', | |
'--key', 'testclient.key', | |
'--ns-cert-type', 'server', | |
'--keepalive', '20', '60', | |
'--comp-lzo', | |
'--verb', '3', | |
'--mute', '20', | |
'--script-security', '2', 'system' | |
] | |
def main(): | |
logging.basicConfig(level=logging.DEBUG, | |
format='%(asctime)s %(levelname)-8s %(message)s', | |
datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') | |
logging.info('Version 0.2a1') | |
port = random.randint(8192, 65535) | |
logging.info('Use random port %s.' % port) | |
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
sock.bind(('0.0.0.0', port)) | |
sock.settimeout(3) | |
nat_port = None | |
for i in range(5): | |
try: | |
sock.sendto('orz', SNAT_SERVER) | |
nat_port = int(str(sock.recv(256))) | |
except socket.timeout: | |
logging.warn('Timeout, retry getting NAT port.') | |
continue | |
except ValueError: | |
logging.warn('Illegal value, retry getting NAT port.') | |
continue | |
sock.shutdown(socket.SHUT_RDWR) | |
sock.close() | |
if nat_port is None: | |
logging.error("Can't get NAT port. Using local bind port.") | |
nat_port = port | |
logging.info('NAT Port is %s.' % nat_port) | |
r = requests.get(NAT_REQUEST_URL % nat_port) | |
if r.status_code == 404: | |
logging.error('Server is offline.') | |
return | |
server = r.text.strip() | |
logging.info('Server address is %s.' % server) | |
logging.info('Waiting 2 seconds...') | |
time.sleep(2) | |
openvpn = get_default_param() | |
openvpn.extend(['--remote'] + server.split(':')) | |
openvpn.extend(('--lport', str(port))) | |
logging.info('Calling openvpn') | |
subprocess.call(openvpn) | |
if __name__ == '__main__': | |
main() |
bin\
。连接前只要安装 Tun/tap 驱动就行了。Linux 下把bin\openvpn.exe
改成openvpn
即可。在 Windows 7 和 Ubuntu 下测试过没问题。
另外,因为只是一次尝试并未正式投入实用,所以通信时并未做认证和加密。为避免安全隐患,代码发布前经过少量修改,但修改后未经实际测试。如有疏漏请留言,谢谢。
当时做得就挺乱的,现在讲得更乱,还请求轻喷。
]]>唔,最早的想法是:学校离家较远所以有两张SIM卡,而且由于资费原因只能分别在两地使用,想通过互联网代理,使得可以在一地同时使用两张卡..
最早想到的是使用闲置低端安卓机,后来发现手头闲置的一张华为数据卡(EC1261)可以利用,遂试着捣鼓出一个这玩意..
本文实现:
虽然还不能接打电话,但至少接收各种验证短信不再麻烦了呢
闲置 华为数据卡 EC1261 一张
装 ubuntu 的 Cubieboard 一块
这张上网卡除了上网,收发短信也是可以的:流量提醒就是用短信实现的(所以总是一条条地接收);带了个 2.5mm 耳机插口,应该是用来打电话的,虽然电信给的软件不带此功能。
其他型号的华为上网卡应该类似
Windows: 插入装好驱动就成,华为上网卡用串口和 PC 通讯,插入后设备管理器中可见串口号(COMXX)。
Linux: 以前在 ubuntu 10.10 试过插上就能用。在刷了官网上下的 ubuntu 镜像的 Cubieboard 上,貌似装了 usb_modeswitch 才正常。若自行编译内核,需确保勾选usb_wwan
option
模块。会看见类似/dev/ttyUSB0
的设备出现。
脚本设计运行于 Linux,依赖 Python 2 和 pySerial,发邮件需装 sendmail 。
apt-get install python-serial sendmail usb-modeswitch
关于如何“操作”上网卡,这个是关键呢,感谢这篇文章给了我线索,搜索关键词 HUAWEI CDMA Datacard Modem AT Command Interface Specification 即可,PDF 里有详细说明。
根据说明写了段程序,完成标题所述功能:hw_smsd.py
(注意下读写串口可能需要 root 权限)
#!/usr/bin/env python | |
#encoding: utf-8 | |
# Copyright (C) 2013 @XiErCh | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in all | |
# copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
# SOFTWARE. | |
import time | |
import serial | |
import smtplib | |
from datetime import datetime | |
LOGFILE_PATH = 'mail.log' | |
TTY_DEV_PATH = '/dev/ttyUSB0' | |
SENDER_EMAIL = 'hw-smsd@somehost' | |
NOTICE_EMAIL = 'your@email' | |
CALL_SMSREPLY = u'抱歉,本号码暂时停用。请拨打 13800138000 联系我,谢谢。' | |
class ATSerial(serial.Serial): | |
'''Send a AT command and return the response or raise a error. | |
''' | |
# Please see HUAWEI CDMA Datacard Modem AT Command Interface Specification | |
# for detial. | |
def at(self, cmd): | |
self.write('AT%s\r\n' % cmd) | |
self.flush() | |
self.readline() | |
rsp = '' | |
while True: | |
l = self.readline().strip() | |
if not l: | |
continue | |
elif l == 'OK': | |
return rsp | |
elif l == 'ERROR': | |
raise IOError('AT: ERROR') | |
elif l == 'COMMAND NOT SUPPORT': | |
raise IOError('AT: COMMAND NOT SUPPORT') | |
elif l == 'TOO MANY PARAMETERS': | |
raise IOError('AT: TOO MANY PARAMETERS') | |
rsp += '%s\n' % l | |
def get_serial(): | |
return ATSerial(TTY_DEV_PATH) | |
def sendmail(msg, subject='',fromaddr=SENDER_EMAIL, toaddr=NOTICE_EMAIL): | |
s = smtplib.SMTP('localhost') | |
msg = u"From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" \ | |
% (fromaddr, toaddr, subject, msg) | |
s.sendmail(fromaddr, toaddr, msg.encode('utf-8')) | |
logfile.write(msg.encode('utf-8') + '\n\n') | |
logfile.flush() | |
s.quit() | |
def sendsms(s, number, text, number_type=0): | |
logfile.write('SMS to %s:\n%s\n\n' % (number, text)) | |
logfile.flush() | |
s.write('AT^HCMGS="%s",%s\r' % (number, number_type)) | |
s.flush() | |
time.sleep(0.1) | |
s.write(text.encode('utf-16-be') + '\x00\x1a') | |
while True: | |
l = s.readline().strip() | |
if l == 'OK': | |
return | |
elif l.startswith('+CMS ERROR:'): | |
raise IOError(l) | |
def e_new_sms(s, opt): | |
# opt[0] callerID | |
# [1:7] yyyy,mm,dd,hh,mm,ss | |
# [7] lang | |
# [8] format | |
# [9] length | |
# [10-13] ... | |
text = s.read(int(opt[9])).decode('utf-16-be') | |
s.read(3) # Ignore ^Z\r\n | |
send_time = "%s-%s-%s %s:%s:%s" % tuple(opt[1:7]) | |
recv_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') | |
mailsubject = u'来自 %s 的短信' % opt[0] | |
mailtext = u'%s\r\n\r\n----\r\n发送号码:%s\r\n发送日期:%s\r\n接收日期:%s' \ | |
% (text, opt[0], send_time, recv_time) | |
sendmail(mailtext, mailsubject) | |
print("SMS => E-mail") | |
s.at('+CNMA') # ack | |
def e_income_call(s, opt): | |
# opt[0] number | |
# [1] type | |
# [5] CLI validity | |
if not opt[0]: | |
if opt[5] == '1': | |
opt[0] = u'隐藏号码' | |
else: | |
opt[0] = u'未知号码' | |
mailsubject = u'来自 %s 的来电' % opt[0] | |
mailtext = u'来电者:%s\r\n来电时间:%s' \ | |
% (opt[0], datetime.now().strftime('%Y-%m-%d %H:%M:%S')) | |
sendmail(mailtext, mailsubject) | |
print("Call => E-mail") | |
s.at('+CHV') # reject | |
if opt[0].startswith('1'): | |
sendsms(s, opt[0], CALL_SMSREPLY, opt[1]) | |
print("Call <= SMS") | |
def loop(s): | |
line = s.readline().strip() | |
if not line: | |
return | |
elif line == 'RING': | |
return | |
try: | |
cmd, rsp = line.split(':', 1) | |
rsp = rsp.strip() | |
opt = rsp.split(',') | |
except ValueError: | |
print('unknow [%s]' % line) | |
return | |
if cmd == '^HCMT': | |
e_new_sms(s, opt) | |
elif cmd == '+CLIP': | |
e_income_call(s, opt) | |
else: | |
print('unknow - %s:' % cmd) | |
print(opt) | |
def main(): | |
s = get_serial() | |
# SMS Parameter Selection: | |
# ^HSMSSS=<ack>,<prt>,<fmt>,<prv> | |
# <fm> 6: Unicode | |
s.at("^HSMSSS=0,0,6,0") | |
# New SMS Notification Setting: | |
# +CNMI=[<mode>[,<mt>[,<bm>[,<ds>[,<bfr>]]]]] | |
s.at("+CNMI=1,2") | |
#sendmail('test', 'sendmail test') | |
while True: | |
loop(s) | |
if __name__ == '__main__': | |
logfile = open(LOGFILE_PATH, 'a') | |
main() |
还比较简陋,但运行了几天没问题,就发上来~怎么说也算留个思路吧。
记得刚才说的,这张上网卡有带一个 2.5mm 插口么,
和 Cubieboard 的 Line-In, 耳机口 相连,再装个 Asterisk 配置一下,一个自己的 SIP 网络电话就架成了~
不过目前来说,我对这功能需求并不迫切呢..
]]>Squid 通过 ACL 为每个访问分类,为每个请求分类,控制行为。与目的网站相关的有:dstdomain
(目标域名)、dstdom_regex
(目标域名,正则表达式) 和dst_as
(目标 AS 号)等,具体用法见 官方文档。
GFWList 使用的是 Adblock 一样的格式,给它全转成正则表达式,然后使用dstdom_regex
匹配就好了。写了个转换脚本(gfwlist2regex.py),运行后自动下载转换生成黑白名单,方便日后更新。
#!/usr/bin/env python | |
#encoding: utf-8 | |
import urllib2 | |
from base64 import b64decode | |
LIST_URL = 'https://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt' | |
BLACK_FILE = 'gfw.url_regex.lst' | |
WHITE_FILE = 'cn.url_regex.lst' | |
def convert_line(line): | |
if line[0] == '/' and line[-1] == '/': | |
return line[1:-1] | |
line = line.replace('*', '.+') | |
line = line.replace('(', r'\(').replace(')', r'\)') | |
if line.startswith('||'): | |
return '^https?:\/\/%s.*' % line[2:] | |
elif line.startswith('|'): | |
return '^%s.*' % line[1:] | |
elif line[-1] == '|': | |
return '.*%s$' % line | |
else: | |
return '.*%s.*' % line | |
def convert(gfwlist): | |
black = open(BLACK_FILE, 'w') | |
white = open(WHITE_FILE, 'w') | |
for l in gfwlist.split('\n'): | |
l = l[:-1] | |
if not l or l[0] == '!' or l[0] == '[': | |
continue | |
if l.startswith('@@'): | |
white.write(convert_line(l[2:]) + '\n') | |
else: | |
black.write(convert_line(l) + '\n') | |
def main(): | |
src = urllib2.urlopen(LIST_URL).read() | |
src = b64decode(src) | |
convert(src) | |
if __name__ == '__main__': | |
main() |
修改 Squid 设置,例如这样:
# ...
acl cn dstdom_regex 'cn.url_regex.lst'
acl gfwed dstdom_regex 'gfw.url_regex.lst'
prefer_direct on
# 默认直连
never_direct allow gfwed
never_direct deny cn
# 白名单直连失败将尝试使用代理;如使用
# always_direct allow cn 将禁止白名单使用代理。
cache_peer localhost parent 1234 0 name=s1 weight=5
cache_peer localhost parent 4321 0 name=s2 weight=10
# ...
黑名单里的必须走上级代理(cache_peer);其余先尝试直接访问,如果被重置,将会转而尝试上级代理。挺智能的。
]]>与 Cubieboard 的连接参照 Wiki,PB18 (TWI1-SCK)、PB19 (TWI1-SDA) 两根接在模块的 SCL、SDI 口上,接好电源(3.3V)和地线,模块的 CS 也接在电源上(以使能 I2C)。官网 ubuntu 镜像默认启用 I2C-1,若未启用则需修改 Fex 文件:twi1_used = 1
可以使用 i2c-tools 确认连接正确。装好后输入i2cdetect -y 1
,如果出现1d
(MMA7455L 的地址),说明连接无误可以通讯。
在 Cubieboard 上使用 Python 读取加速度数值,代码在这:MMA7455L.py,目前实现功能有:
set_range()
);set_offset()
clear_offset()
)。#!/usr/bin/env python | |
import smbus | |
import time | |
class Accel(): | |
def __init__(self, dev=1, g=2): | |
self.bus = smbus.SMBus(dev) | |
self.set_range(g) | |
def write(self, register, data): | |
self.bus.write_byte_data(0x1D, register, data) | |
def read(self, data): | |
return self.bus.read_byte_data(0x1D, data) | |
def set_range(self, g): | |
if g == 8: | |
d = 0b01000001 | |
elif g == 4: | |
d = 0b01001001 | |
elif g == 2: | |
d = 0b01000101 | |
else: | |
raise ValueError("Only 2, 4 or 8.") | |
self.write(0x16, d) | |
self.g = g | |
def _get(self, addr): | |
l = self.read(addr) | |
h = self.read(addr + 1) | |
d = h << 8 | l | |
if d > 511: | |
d -= 1024 | |
return d | |
def get_x(self): | |
return self._get(0x00) | |
def get_y(self): | |
return self._get(0x02) | |
def get_z(self): | |
return self._get(0x04) | |
def clear_offset(self): | |
self.write(0x10, 0) | |
self.write(0x11, 0) | |
self.write(0x12, 0) | |
self.write(0x13, 0) | |
self.write(0x14, 0) | |
self.write(0x15, 0) | |
time.sleep(0.1) | |
def _set_offset(self, addr, zero): | |
d = self._get(addr - 0x10) | |
d = zero - d | |
d = int(d * 2.5) | |
d += 65536 | |
self.write(addr, d & 0xff) # Low | |
self.write(addr + 1, d >> 8 & 0xff) # High | |
def set_offset(self): | |
self.clear_offset() | |
self._set_offset(0x10, 0) | |
self._set_offset(0x12, 0) | |
if self.g == 4: | |
zero = 32 | |
else: | |
zero = 64 | |
self._set_offset(0x14, zero) | |
time.sleep(0.1) | |
def main(): | |
mma = Accel(1) | |
mma.set_offset() | |
while True: | |
x = mma.get_x() | |
y = mma.get_y() | |
z = mma.get_z() | |
print("x =%4d ,y =%4d, z =%4d" % (x, y, z)) | |
time.sleep(0.1) | |
if __name__ == '__main__': | |
main() |
这块IC功能挺多的,先把好玩的给写了,以后有需要再改脚本。
P.s. 写校准时被坑了两次,MMA7455L 的 PDF 里不给计算方法,得下 AN3745 看。里面有两句话:
(…) values calculated in Step 2 must be multiplied by two.
Note that there will still be a bit of offset shift, and you may need to multiply by a bit more than two to exactly subtract the offset.
果断只看到前一句就乘 2 了,实际上反复测试要乘 2.5 才完美…
另外虽然 PDF 里写数据只有 10 位,但负数取补码时得按 16 位取,发 16 位才行…
一句话,参照此 gist,同时将TUNSETIFF = 0x400454ca
改为TUNSETIFF = -2147199798
即可。
那篇文章的代码在 Windows 和 Ubuntu 下都正常运行,但今天在路由器上运行时却报错了:
Traceback (most recent call last): …… File "/root/movpn/tun.py", line 218, in _open_tun fcntl.ioctl(tun, TUNSETIFF, ifr) IOError: [Errno 81] File descriptor in bad state
因为 vtun 运行正常,所以 tun 本身应该是没问题的。又测试了一下,这个错误在 ioctl 的request
不正确时出现。遂铺天盖地地寻找此值,发现不少相同遭遇,如这里,还有这,但没找到解决方案。
先去解了 ioctl,得知第二个参数是由好几个部分组成的,从 if_tun.h 找到了 ioctl.h,最后卡在一点C语言也不会,琢磨半天也没弄明白 TUNSETIFF 取值多少..
无奈,只好试着自己写段代码把 TUNSETIFF 打出来了..
之前只听说过 交叉编译 这个词,现在就要动手了好鸡冻,好在 OpenWrt 的相关资料很丰富:参照 wiki 以及 这篇帖子 外加一晚上时间终于完成了。
找了一个 C Hello Word 改了一下:
#include
#include <linux/if_tun.h>
#include <sys/ioctl.h>
main() {
printf("TUNSETNOCSUM = %d\n", TUNSETNOCSUM);
printf("TUNSETDEBUG = %d\n", TUNSETDEBUG);
printf("TUNSETIFF = %d\n", TUNSETIFF);
printf("TUNSETPERSIST = %d\n", TUNSETPERSIST);
printf("TUNSETOWNER = %d\n", TUNSETOWNER);
printf("TUNSETLINK = %d\n", TUNSETLINK);
printf("TUNSETGROUP = %d\n", TUNSETGROUP);
}
交叉编译:
xx@xx:~/openwrt/staging_dir/toolchain-mips_r2_gcc-4.6-linaro_uClibc-0.9.33.2$ \
bin/mips-openwrt-linux-gcc test.c
上传路由器,执行:
TUNSETNOCSUM = -2147199800 TUNSETDEBUG = -2147199799 TUNSETIFF = -2147199798 TUNSETPERSIST = -2147199797 TUNSETOWNER = -2147199796 TUNSETLINK = -2147199795 TUNSETGROUP = -2147199794
OK,全出来了,话说为啥都是负数 = = 和 CPU 位宽整数长度啥的有关?换一个路由器是否还是这个值?
不知道,管用就行,Python 里TUNSETIFF = -2147199798
就好了。
P.s. 嘛其实回想起来也不复杂,就是编译 OpenWrt 略显蛋疼:经常在各种下载时卡住,挂VPN、断VPN,反复试。 有个 git clone 就是卡在 84% 死活过不去,最后把 git:// 改成 https:// 才好..
]]>SSH -D
替代品。
SOCKS5 代理自己用着是没问题的,但普及以及携带使用的时候,HTTP 代理会更方便些(无 DNS 污染问题)…
代码在这,基于 0.9.2。
先放 pastebin 上了.. git 用不清楚就先不 fork 了.. – –
(虽然改得比较丑,但使用起来没问题啦
这就是之前写的那个asstosrt2.py脚本的GAE移植版。
具体说明、源码请见:《[Python] ass字幕批量转srt – asstosrt2.py》
这个GAE移植版…可以说…相当简陋(丑陋)…
暂不支持批量转换…所以只能供手头没工具时临时使用吧…
需要批量转换可以直接下载 asstosrt2.py 脚本…
#UPDATE111218:
支持上传ZIP压缩包批量转换了!如需离线使用仍可下载 asstosrt2.py 脚本。
#UPDATE120303:
已由GAE迁至VPS的Django上,更灵活些..
电视、手机 都不支持 ASS 字幕 ,之前用 SrtEdit 转。
但后来遇到个字幕组,把 OP&ED 的字幕放在最前面,字幕顺序颠倒了,手机就不认了…
于是写了这个 Python 脚本
为了使用方便,只需要将ass文件全部选中,拖到 asstosrt2.py 上就好了
#UPDATE110903: 如果无法拖拽,可直接运行,将自动转换同目录下所有.ass文件。
再没有其他操作了…
如果没有错误,转换完窗口会直接关闭,在ass文件同目录下会生成srt文件
当然你需要先安装 Python 2.x 才行。
下载 asstosrt2.py:
最新: GitHub | 历史: SkyDrive | Google Docs
下载 Python 2.7:
Python.org
#UPDATA110903: 请确保 atslib.zip 与 asstosrt2.py 在同一目录下。
功能特点:
× 默认输出srt文件编码为 UTF-16 (Unicode / UCS-2 Little Endian)
我电视手机刚好都支持这个编码
× 自动删除 {} 内的特效代码
srt 不支持嘛
× 默认对字幕按时间进行重新排序
我手机只要顺序一乱就罢工了,电视还没试过
× 默认删除带过渡特效的字幕
例如顶部滚动显示“仅供研究,请在24小时内删除……”等,容易覆盖对白字幕
× 繁简体转换(默认禁用)
调用了部分维基百科的翻译规则,使用了部分 pyswim 项目代码
× 只显示第一行字幕(默认禁用)
一些双语字幕将中文放置在第一行,启用后可简单删除 \N 后的内容
由于比较懒 ,如需修改默认设置,请直接编辑代码 48~52 行。
程序很简单,或者说很简陋,本来就是自己方便着用的东西嘛…200行..
Python 2.7, Windows 7 下调试无误,GPLv3
初学 Python 没几天,糟糕的代码,见笑了
P.s 不要吐槽排序部分代码 -_-||
#UPDATE 120306:
(Ver 0.4.6) 支持换行显示
#UPDATE 110923:
(Ver 0.4.5) 解决某些设备(如某些三星电视)完全无法识别字幕的问题。
#UPDATE 110903:
(Ver 0.4.4) 忽略文件编码错误,解决少量字幕转换失败的问题。
(Ver 0.4.3) 非拖拽模式增加转换.ssa文件。
(Ver 0.4.2) 增加繁简体转换功能;
直接运行将转换通目录下所有.ass文件。
#UPDATE 110723:
(Ver 0.3.2) 自动识别ASS文件编码,解决无法转换非 UTF-8 / ASCII 文件的问题;
增加了一点转换失败判断;加了个简短的说明文件并打包。