前几天入手了一台wr1200js路由器,到手后成功刷上了openwrt。但重启后发现路由器的配置都丢失了,连管理密码都丢了,网上好多人刷了openwrt后也遇到这样的问题,有说法是路由器闪存读写次数过多坏了,但这刚买的因该不会是这问题,后来在重新刷了下sysupgrade.bin就没这问题了,可能是openwrt在某些条件下会触发这问题。
路由器相关的一些研究
家中房间里的5G信号不好,每次都得手动切换到2.4G信号,有点麻烦,所以想在房间里弄一台AP,来实现无线无缝漫游,期间做了不少研究,故在此记录一下。
路由器/AP
网上看了一圈AP的价格都不便宜,可能是家用AP需求少且大部分是商用的原因,关键还要上POE。论坛好多推荐ubnt
的,看了下普通版都要700+rmb。
千兆路由器好一点的基本是200左右的价格,但这个价位的厂家路由系统比较挫,还不能刷机。在再加点钱都赶上京东618 ac66u b1 499的价格了。
那有没有又便宜要好的路由呢,V站好多人都推荐这几款:
- 斐讯K2P
- 联想Newifi系列
- 友华wr1200js
斐讯K2P
这个路由器现在都涨到120+了,如果要买的话要买a1版本的。
优点是做工好,有千兆网口,信号貌似是最好的。
缺点是现在比较难买,刷机的话要先上恩山论坛网友开发的breed, breed 就是路由器上的bootloader,breed一开始是基于u-boot的,现在好像是作者自主开发的,但这东西不开源,存在安全风险。固件可以刷padavan,padavan最初是由这个固件https://bitbucket.org/padavan/rt-n56u修改过来的,比较有名的是荒野无灯的,但这个也没开源。github上也有基于padavan开源的,不放心的可以拿来自己编译。
联想Newifi
联想这边就看了newifi3,这款路由器配置比k2p还要好,价格比k2p还要便宜,某宝的价格才80+,但这款的bootloader被官方锁了,如果要刷第三方固件的话要先越狱,恩山有越狱文件。刷机也是breed+恩山上一大堆网友上传的固件,风险自负,还有一个缺点就是这款信号比较差。
友华wr1200js
友华wr1200js配置和k2p差不多,但做工、信号没有k2p好,最终选择了这款,某宝138入手。这款路由器最大的优点是bootloader没有锁,可以直接上传第三方固件!!还有一个是openwrt完美支持,固件下载地址。
Openwrt 和 NAT
喜欢路由器刷机的都知道openwrt,路由器刷了openwrt基本上就告别了硬件nat(貌似MT7621是支持的),由路由器CPU来实现nat,由于路由器CPU的性能都比较弱如果再加上一些附带的功能,路由器CPU就有点力不从心了,这是就推荐openwrt x86了。
MESH网络
研究了下mesh网络,研究下来一些mesh设备是通过在开一个无线频道来实现组网的,貌似无线组网的效果都不怎么好,要走有线回程才靠谱。
一些实现方案:
无线漫游
比较省钱的无线漫游方式就是路由器设置成AP模式,要相同SSID,要隔开几个频道。
专业的AP都带管理功能的。
参考
路由器透明代理
作为程序员的都知道,现在比较流行的科学上网方式是$$。$$搭建步骤简单,速度快,相比其他的科学上网方式比较轻量。但这种方式也有一些缺点,比如需要在本机安装客户端,维护服务端信息等缺点,路由器透明代理就是为了解决这些问题而诞生的 。
传统科学上网实现方式
传统的科学上网方式是在本机安装$$客户端,不同平台的客户端如下:
- IOS
- ShadowRocket(国区已下架)
- Surge(国区已下架)
- Mac OS
- Shadowsocks NG
- Surge
- ss-local
- Linux
- ss-local
- Windows
- Shadowsocks
代理模式
$$一般有两种代理模式,
自动切换模式
- 黑名单模式,只代理被墙的网站,对于名单中匹配的网站进行代理,如果不匹配则直接连接,比较著名的 有GFW List
- 白名单模式,默认代理,对于名单中匹配的网站进行直连。白名单模式在不同平台上的效果不同,在IOS可以实现白名单模式,在Mac OS 上只支持黑名单模式,如果要实现需要做一些hack操作,如:ShadowsocksX-NG实现白名单模式
全局模式
所有流量都走代理,这个在不同平台上表现也不同,在Mac os上只能代理部分流量,像CLI的流量需要额外配置。
传统代理模式的缺点
传统的代理模式的缺点也很明显:
- 需要安装客户端,且需要在所有客户端配置服务器信息
- 无法代理系统级流量或需要额外设置
- 无法实现大陆白名单模式(除了大陆以外的网站都走代理)
- 被屏蔽的网站越来越多,所有客户端都需要更新名单
路由器透明代理
路由器透明代理,说直白点就是在路由器实现科学上网功能。路由器透明代理的优点很明显,无需在本机安装客户端,只需在路由器维护服务器信息及黑白名单,且能代理系统级流量。
数据流逻辑图
如下图所示, 路由器透明代理主要是两个流程,DNS解析流程与流量代理流程。
实现原理
路由器透明代理主要依赖如下工具:
iptables
ipset
ss-redir
ss-tunnel
dnsmasq
dnsmasq
+ss-tunnel
dnsmasq
和ss-tunnel
主要是用来解决域名污染的问题, dnsmasq会将除了国内以外的DNS解析请求通过dnsmasq
会将国外域名dns解析请求通过ss-tunnel
转发到shadowsocks服务器ss-tunnel
转发到shadowsocks服务器
dnsmasq配置文件:
https://cokebar.github.io/gfwlist2dnsmasq/dnsmasq_gfwlist.conf
1 | server=/google.com/127.0.0.1#5353 |
如上面的代码所示,遇到如需要解析google.com
的域名时,dnsmasq会将解析请求转发到ss-tunnel
监听的5353端口,ss-tunnel
会将解析请求转发到远程服务器,通过远程服务器来解析域名。
不建议用上面的方法了,DNS污染太多太频繁,直接上DNS白名单模式:
1 | server=/baidu.cc/114.114.114.114 |
dnsmasq.conf
将ss-tunnel
设置为上游服务器
1 | no-resolv |
iptables+ipset
iptables
和ipset
主要用来区分国内流量和转发代理流量。
ipset
ipset.conf
1 | 1.0.1.0/24 |
shell:
1 | ipset create chnroute hash:net |
上面代码创建了一个名为chnroute
的IP集合。
iptables sample:
1 | iptables -t nat -N SHADOWSOCKS |
上面代码的主要含义是:
- 先判断是不是请求远程$$服务流量,如果是则直接放行
- 如果是请求内网的流量也会直接放行
- 如果是请求大陆网站的流量,则直接放行
- 如果上面的条件都不匹配,则会将流量转发到本机
ss-local
监听的1080端口
ss-redir
ss-redir
会监听端口,并将代理请求转发到$$服务器
路由器透明代理实现方式
一般高端一点的路由器都可以通过刷机来实现,以华硕路由器为例:
梅林固件
梅林固件华硕路由器第三方固件,基于华硕官方固件。这种方式你需要自己搭建整套工具,虽然网上也有一些一键安装的脚本,但与我们上面提到的有一些出入。
官网: https://asuswrt.lostrealm.ca/
优点:开源,基于华硕官方固件,比较稳定。
缺点:
- 需要熟悉linux
- 需要自己搭建且坑比较多
- 定制差
- 除华硕路由器外,只支持部分非华硕路由器
梅林小宝
梅林小宝是基于梅林的固件,这个实现方式比较傻瓜式,刷机完成后可通过在软件中心安装shadowsocks,配置一下服务器信息就可以实现路由器透明代理。
官网:http://koolshare.cn/forum-96-1.html
优点:
- 无需自己搭建
- 有图形化管理界面
缺点:
- 闭源,存在风险
- 除华硕路由器外,只支持部分非华硕路由器
OpenWrt
OpenWrt是开源的路由器固件,他能提供一整套linux操作环境,插件众多且大多数插件都提供图形管理界面,大大降低而搭建难度。
当然在刷机之前你需要在Table of Hardware
里查一下你的路由器是否支持openwrt.
优点:
- 开源,维护者众多
- 插件众多,很多定制功能:广告过滤,文件共享等。
- 图形化管理界面
- OpenWrt x86 支持X86平台,可实现软路由。
缺点:
- 需要一点linux知识
路由器透明代理的不足
当然路由器透明代理也有其不足,路由器一般都是低功耗平台。以ac66u为例,大陆白名单模式下只能跑到30M的带宽,CPU占用已经是100%。
软路由
软路由顾名思义,就是在软件层面实现路由功能,基于x86平台,性能强大,解决硬路由性能不足的问题,这里就不多说了。
参考
Groovy JSON处理的一些细节
最近遇到一项目,需要在手机端存储用户数据,实现离线访问。其中用户数据处理的逻辑如下图:
服务端从亚马逊S3上下载用户JSON文本数据库
反序列化用户数据
更新用户数据
将用户数据序列化为JSON文本
保存到亚马逊S3上
由于项目设计缺陷,用户所有的数据都存储一个Map对象里,导致Map对象过大,在项目运行过程中出现了内存不足的异常。为了解决内存不足问题,服务端采用了JacksonStreamingApi优化了JSON序列及反序列化步骤,避免将整个用户文件载入到内存中,至此内存不足的异常就再也没有发生。
其实这里有个问题,如果是用户数据过大,内存不足异常会在步骤3结束后就会发生,为什么偏偏在步骤4序列化为JSON时抛出呢?这里就要说到 Groovy的LazyMap了。
LazyMap代码:
1 | public class LazyMap extends AbstractMap<String, Object> { |
从代码中可以看出:对于未进行过读操作(get,containsKey等)的LazyMap对象,keys和values分别存在了两个数组中,一旦调用了读取方法,LazyMap会将数组转化成Map对象,就是这一步操作引起了内存占用变化。
拿大小约为35MB的用户文件测试,反序列化后,内存中LazyMap对象为73MB(line 1),一旦对对象进行toJson
操作(line 2),内存占用上升到了559MB(line 3)。
1 | public static void main(String[] args) { |
由于用户数据文件较大且嵌套了多层Map,加之JsonOutput.toJson
方法会遍历LazyMap对象所有节点,相当于对所有节点进行了读操作,导致节点中的数组转换成Map对象,最终引起内存不足异常。
ASUS 66u 搭建透明代理
安装软件
1 | /usr/sbin/entware-setup.sh |
配置ss-redir服务
修改配置文件
vim /opt/etc/init.d/S22shadowsocks
- 将
ss-local
改为ss-redir
- 添加启动参数
-b 0.0.0.0
- 将
ENABLED
值改为yes
,变成开机启动
修改shadowsocks.json
修改服务器IP,端口,加密码方式。
配置dnsmasq
创建配置文件
vim /jffs/configs/dnsmasq.conf.add
- 添加
conf-dir=/opt/etc/dnsmasq.d
创建目录
mkdir /opt/etc/dnsmasq.d/
- 下载规则文件
1 | cd /opt/etc/dnsmasq.d/ |
配置ss-tunnel服务
ss-tunnel主要用于解决dns污染的问题,dnsmasq会将解析请求转发到ss-tunnel
创建开机启动脚本
- 转到目录
/jffs/scripts/
- 创建名为比如
start-ss-tunnel.sh
文件,内容如下:
1 | !/bin/sh |
创建service-start
文件
1 | chmod +x service-start |
将/jffs/scripts/start-ss-tunnel.sh
加到service-start
中
至此ss-tunnel
就能开机启动了
配置iptables
下载chnroute.txt
1 | wget -O- 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | awk -F\| '/CN\|ipv4/ { printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > /jffs/configs/chnroute.txt |
生成ipset集合
1 | #!/bin/sh |
因为chnroute.txt
文件比较大,生成会比较慢,故将生成的ipset保存到ipset.conf
中,每次启动时从ipset.conf
中导入IP.
启动脚本
SS启动脚本ss-up.sh
1 | #!/bin/sh |
将其中的SS-SERVER-IP
修改成你的服务器地址
创建post-mount
文件
1 | chmod +x post-mount |
将ss-up.sh
加入
1 | !/bin/sh |
经测试只有将启动脚本加入到post-mount
中对能开机启动,可能的原因是ss-up.sh
中设及到挂载分区的访问。
停止脚本
1 | #!/bin/sh |
参考
- shadowsocks-libev ss-nat
- 另一个ss-nat
- U盘挂载
- gfwlist2dnsmasq
- Iptables 指南 1.1.19
- ss/ssr/v2ray/socks5 透明代理
- https://www.shintaku.cc/posts/gfwlist/
- https://zzz.buzz/zh/gfw/2016/02/16/deploy-shadowsocks-on-routers
- luci-app-shadowsocks
- https://blog.bluerain.io/p/SS-Redir-For-Router.html
- transparent proxy base on ss, ipset, iptables, chinadns on asuswrt merlin
- 利用shadowsocks打造局域网翻墙透明网关
- 在 ArchLinux 上配置 shadowsocks + iptables + ipset 实现自动分流
- asuswrt-merlin/wiki/User-scripts
ShadowsocksX-NG实现白名单模式
ShadowsocksX-NG Proxy Auto Configure Mode
使用的是黑名单模式,默认直连,符合规则的走代理。要实现白名单模式,默认代理,符合规则的直连,有一种hack方法:
找到程序文件夹下的abp.js文件,路径一般为
/Applications/ShadowsocksX-NG.app/Contents/Resources/abp.js
修改
FindProxyForURL
方法,将direct,proxy
对调,改成下面代码中的样子1
2
3
4
5
6function FindProxyForURL(url, host) {
if (defaultMatcher.matchesAny(url, host) instanceof BlockingFilter) {
return direct;
}
return proxy;
}重启程序
修改程序
preferences
里的GFW List url
,改成白名单规则然后点击
Update PAC from GFW List
更新pac文件即可
Groovy+Grails迁移到Kotlin+Spring Boot的一些研究
Kotlin是静态语言,可以运行在JVM上且与java百分百可互操作。自从Google将Kotlin正式做为Android开发的语言后,Kotlin的一下子就火了起来。Kotlin不仅可以做为Android的首选开发语言,而且它也非常适合服务端的开发。
为什么选择Kotlin
与Java百分百可互操作groovy-grails-to-kotlin-springbootgroovy-grails-to-kotlin-springboot
Kotlin可以使用JVM平台上的任何库与框架,Kotlin编写的代码也可以供Java调用
简洁
Kotlin的代码非常简洁:
变量
变量声明:
1 | val a: Int = 1 // immediate assignment |
可变变量:
1 | var x = 5 // `Int` type is inferred |
可空值与不可空值:
1 | val s: String? = null //May be null |
方法
1 | fun sum(a: Int, b: Int): Int { |
扩展函数
1 | fun String.lastChar(): Char = this.get(this.length - 1) |
infix函数
1 | infix fun Any.to(other: Any) = Pair(this, other) |
类
1 | class Greeter(val name: String) { |
数据类(data class)
自带equals hashcode tostring copy等方法
1 | data class User(val name: String, val age: Int) |
DSL(Domain-specific language)
1 | open class Tag(val name: String) { |
空安全
空安全(null safe)可以说是kotlin杀手级的特性了,它能排除一切空指针的可能,让你写出更安全的代码。
如下面这段代码会直接编译不通过:
1 | var output: String |
1 | val name: String? = null // Nullable type |
自动类型转换:
1 | fun calculateTotal(obj: Any) { |
静态类型(Statically Typed)
动态语言是一把双刃剑,groovy的代码非常简洁,弥补了java语言太啰嗦的缺点,同时groovy的闭包也非常好,正因为如此我们选择了groovy。但时做为动态语言,许多行为是在代码运行时才会确定,编译错误在写代码时无法检测到,除非加上@TypeChecked
注解。
Kotlin的代码非常简洁,更重要的是它是静态类型语言,能检测到编译错误,减少了代码中潜在的问题。
为什么选择Spring Boot
- 从2015年初开始,Sring Boot的热度就超过了Grails,可参考:Grails && Spring Boot Stack Overflow Trends
- Grails 2.x版本有上千个插件,但升级到Grails 3.x之后,这些插件就不再被支持,更糟糕的是没有人将这些插件迁移到Grails 3.x,Grails 3.x现在只有244个插件。
- 尽管Grails 3是基于Spring Boot的,但其自身也带来了些奇怪的bug, 有时候解决框架bug的时间甚至超过了使用框架节省的时间。
Kotlin+Spring Boot的一些不足
没有Gorm查询
Grails框架的最大亮点是Gorm:动态方法和DSL查询:
1 | //will be translated to hql automatically |
在Spring Boot中你只能自己写查询语句或者使用类似spring data jpa query dsl插件:
1 | internal interface TodoRepository : Repository<Todo, Long> { |
数据库迁移(Database Migration)
Grails 的database migration在数据库表结构和数据迁移两方面都非常强,它可以对比项目中的实体和数据库表来自动生成表结构修改语句,同时你可以写sql来完成数据迁移。
1 | changeSet(author: "xxx (generated)", id: "1521527665533-1") { |
而在Spring Boot方面,数据库迁移插件有flyway和liquibase,两者各有千秋,flyway在数据迁移上比较强,但缺少自动生成表结构修改语句功能。liquibase正好相反,它有自动生成表结构修改语句功能,但在写数据迁移sql方面比较弱。
表单校验
在自定义校验方法上Grails非常方便,只需要一个闭句就可以了:
1 | class User { |
而在Spring Boot方面比如用hiberate validator你需要定义类,定义注解,写很多代码来实现自定义校验。
总结
在项目开发速度上,Groovy+Grails上占优,如果时间比较紧的话,Groovy+Grails会是一个不错的选择,它能在较短时间内做出项目。如果你需要一个bug少,更稳定的项目或者你的项目用不到Gorm的话,你可以选择Kotlin+Spring Boot.
简而言之,100分做为满分的话,Groovy+Grails能在最短的时间内达到80分,而要达到90甚至95分的话,选择Kotlin+Spring Boot无疑。
Amazon API Gateway 踩坑记
最近项目中用到了Amazon API Gateway,当中遇到了不少的坑。本文主要罗列了在使用amazon api gateway中遇的问题,希望能帮助到遇到相同问题的开发者。
不支持parmeters array
1 | GET /user/1/addresses?addressId=1&addressId=2 |
服务端接收到的只有一个值,解决办法passing-array-query-parameters-with-api-gateway-to-lambda
POST/PUT 返回 406
当使用json body发送put/post请求时,api gateway有一定机率返回406,查了半天也找出什么原国,后来在stackoverflow上找到了答案,需要在Integration Request中添加Accept头,并且设置其值为空字符串。具体什么原因估计只有AWS api gateway的开发者才知道了。
aws-api-gateway-returns-http-406
URL Query String Parameter、header需要显示设定
如果不在Method Request里显示声名URL Query String Parameter的话,请求参数是不会转发到后台的,request header同理。
Grails参数绑定不支持application/x-amz-json-1.0
/application/x-amz-json-1.0
content type.
请求转发到后台时,API gateway会将application/json
转换成application/x-amz-json-1.0
/application/x-amz-json-1.0
,需要修改代码,添加对这两个header的支持,不然会无法绑定参数。
Java 浮点数存储方式
Java浮点数使用的是IEEE二进制浮点数算术标准。
浮点数分为符号,指数,分数三部分。以32位浮点数为例,分为1符号位+8指数位+23分数位。
1(bit) | 8(bit) | 23(bit) |
---|---|---|
符号 | 指数 | 分数 |
以173.7为例:
173.7为正数,故符号位为0
0 00000000 00000000000000000000000
将其转换成1.??? * 2^?形式
1 | 173.7/2=86.85 2^1 |
最终格式为1.35703125*2^7 ,指数位的值为7,由于指数部分是由Bias偏移量来表示的,float的偏移量为Bias=2^k-1 -1=2^8-1 -1=127,做指数部分的值为127+7=134,134的二进制值为 1000 0110,则符号位+指数为存储格式如下:
0 1000 0110 00000000000000000000000
计算尾数部分,由于浮点数最终都是以1.??? * 2^?的形式保存的,小数点左边永远是1,所以我们只需关心小数点后面部分
Number | X2 | 1/0 |
---|---|---|
0.35703125 | 0.7140625 | 0 |
0.7140625 | 1.428125 | 1 |
0.428125 | 0.85625 | 0 |
0.85625 | 1.7125 | 1 |
0.7125 | 1.425 | 1 |
0.425 | 0.85 | 0 |
0.85 | 1.7 | 1 |
0.7 | 1.4 | 1 |
0.4 | 0.8 | 0 |
0.8 | 1.6 | 1 |
0.6 | 1.2 | 1 |
0.2 | 0.4 | 0 |
0.4 | 0.8 | 0 |
由于到了0.4这一步这边是会个循环,0110 0110 …
加上尾数部分,最终存储格式如下:
0 1000 0110 01011011011001100110011
我们可以通过Float.floatToIntBits(173.7)
来验证结果:
1 | Float.floatToIntBits(173.7) = 1127068467 |