若水斋 若水斋:Werner的个人博客,关注信息安全 https://blog.werner.wiki zh-cn me@werner.wiki 2018-08-05 使用dns2tcp搭建DNS隧道 https://blog.werner.wiki/building-a-dns-tunnel-with-dns2tcp/ https://blog.werner.wiki/building-a-dns-tunnel-with-dns2tcp/ Werner 2018-08-05 0x00 问题场景

假设有如下图所示的问题场景。

问题场景

办公电脑通过防火墙与互联网相连,防火墙被配置为仅允许DNS数据通过。在此场景下,办公电脑显然无法直接访问互联网。现在我们想做的是通过使用dns2tcp建立DNS隧道,使得办公电脑可以访问互联网。

许多大学的校园网在连接后,打开浏览器会自动跳转到身份认证页面。在认证前,是无法访问互联网的,但一般来说,其DNS解析是可用的。这便是上述问题场景中的一个实例。另一个实例是有些公司的网络出口处有防火墙阻止了普通员工的办公电脑访问互联网的,但有时防火墙的配置会允许DNS数据通过。

0x01 预备知识

我们假设读者对DNS有最基本的了解,若不了解可先阅读《百度百科:DNS》。这里要介绍的是NS记录。以下内容摘自《阿里云-用户指南-记录类型》。

NS 记录

(1)什么情况下会用到NS记录?

如果需要把子域名交给其他DNS服务商解析,就需要添加NS记录。

(2)NS记录的添加方式

记录类型为NS。

主机记录处填子域名(比如需要将opo.abc.com的解析授权给其他DNS服务器,只需要在主机记录处填写opo即可,NS记录的主机记录(RR值)不能为空,主机记录@不能作为NS记录使用,且NS记录不支持泛解析【将所有子域名解析到同一地址】,授权出去的子域名不会影响其他子域名的正常解析)。

线路类型(默认为必填项,否则会导致部分用户无法解析)。

NS记录值,NS向下授权,请填写DNS域名记录值为要授权的DNS服务器域名,为了保证服务可靠性,建议您添加至少2组DNS服务(例如:ns1.alidns.com, ns2.alidns.com)。

TTL不需要填写,添加时系统会自动生成,默认为600秒(TTL为缓存时间,数值越小,修改记录各地生效时间越快)。

NS记录不是在设置某个域名的DNS服务器,而是在设置某个子域名的DNS服务器。理解这一点对于成功搭建DNS隧道至关重要。

例如我有域名“werner.wiki”,添加NS记录类型的解析,主机记录值为“test”,记录值为“dns.my.com”。则此记录生效后,所有关于“test.werner.wiki”及其子域名的DNS查询,都会找DNS服务器“dns.my.com”。

0x02 原材料

为了成功搭建DNS隧道,我们需要一台位于互联网中的(而不是在防火墙里的)服务器,我们将要搭建的隧道的一头是办公电脑,另一头便是这台服务器。在实践中,我们常常会买一台云服务器或vps来搭建DNS隧道。由于DNS的敏感性,这台服务器最好是在中国大陆。为了进行演示,我买了一台腾讯云的云服务器(刚好有快要过期的代金券~)。

我们还需要一个可以设置解析的域名。这个域名是什么无所谓,只要能设置解析就行。所以可以买一个很便宜的域名,第一年的价格在几块钱。在这篇文章中,我直接使用了自己博客的域名werner.wiki。实际上,域名是非必须的,后文中会解释这一点。

0x03 开始搭建

1 设置域名解析

首先添加一条A类解析,主机记录为“dns”,记录值为“118.xx.xx.120”,这个IP地址就是腾讯云服务器的公网IP地址。

然后添加一条NS解析,主机记录为“dns2tcp”,记录值为“dns.werner.wiki”。

这两条域名解析如下图所示。

问域名解析设置

这两条域名解析做了什么?

第一条A类解析是在告诉域名系统,“dns.werner.wiki”的IP地址是“118.xx.xx.120”。

第二条NS解析是在告诉域名系统,想要知道“dns2tcp.werner.wiki”的IP地址,就去问“dns.werner.wiki”。

如何验证域名解析设置是否成功?

在随便一台电脑上ping域名“dns.werner.wiki”,若能ping通,且显示的IP地址是“118.xx.xx.120”,说明第一条A类解析设置成功并已生效。

在腾讯云服务器(118.xx.xx.120)上抓包,抓目的端口是53的udp包,命令是:

tcpdump -n -i eth0 udp dst port 53

53端口是DNS协议使用的端口。注意上述命令中的参数“eth0”,含义是网卡接口名,在不同的服务器中可能不同。

然后在随便一台电脑上运行命令:

nslookup dns2tcp.werner.wiki

这条命令是在对域名“dns2tcp.werner.wiki”进行查询。如前所述,若我们设置的域名解析生效,则对此域名的DNS查询,会最终被转发到“dns.werner.wiki”,而“dns.werner.wiki”的IP地址是“118.xx.xx.120”,也就是我们的腾讯云服务器上的的公网IP地址。

查看腾讯云服务器上的抓包情况,若抓到对域名“dns2tcp.werner.wiki”进行查询的DNS请求数据包,则说明第二条NS解析设置成功并已生效。

抓到的数据包如下图所示。图中的目的IP地址是腾讯云服务器的内网IP地址,所以我没有打码。

抓到的DNS查询数据包

2 服务器端软件配置

我买云服务器时选择了Ubuntu16.04系统。以下操作均只针对此系统。其他操作系统原理相同,细节可能有所出入。

安装配置dns2tcp

在腾讯云服务器上,我们首先需要安装dns2tcp,命令如下:

sudo apt-get install dns2tcp

我安装的版本是v0.5.2。安装完成后打开配置文件“/etc/dns2tcpd.conf”,将其中内容修改为:

listen = 0.0.0.0
port = 53
user = nobody
chroot = /tmp
domain = dns2tcp.werner.wiki
resources = socks:118.xx.xx.120:7777

在修改配置文件前可以先备份原有配置文件。关于此配置,需要说明的第一点是监听IP地址应该为“0.0.0.0”,而不是“127.0.0.1”,否则就只能监听到本地连接;第二点是域名(domain)是NS解析对应的域名;第三点是dns2tcp中的资源(resources)不是dns2tcp自带的,需要自己动手实现。在我给出的配置文件中只写了一个名为“socks”的资源,其IP地址是云服务器公网IP,端口号是我随手敲的。这个资源是一个socks代理,我想要实现的是办公电脑通过DNS隧道后直接连接到一个socks代理上,使用此代理访问互联网。

安装配置dante-server

现在让我们来搭建一个socks代理。我选择使用dante-server来搭建。首先安装它,命令如下:

apt-get install dante-server

我安装的版本是v1.4.1。安装完成后打开配置文件“/etc/danted.conf”,将其中内容修改为:

logoutput: /var/log/sockd.log
internal: eth0 port = 7777
external: eth0
socksmethod: username none
user.privileged: proxy
user.unprivileged: nobody
client pass {
    from: 0.0.0.0/0 port 1-65535 to: 0.0.0.0/0
}
socks pass {
    from: 0.0.0.0/0 to: 0.0.0.0/0
    protocol: tcp udp
}

在修改配置文件前可以先备份原有配置文件。原有配置文件对各配置项的含义与作用有充分的说明,此处不再赘述。我们现在看到的配置文件配置了一个监听端口7777的socks代理,无身份认证,允许任何客户端连接。

3 客户端软件配置

办公电脑便是这里所说的客户端。若办公电脑是Ubuntu系统,直接运行如下命令即可获得dns2tcp的客户端程序dns2tcpc:

    sudo apt-get install dns2tcp

若办公电脑是Windows系统,可以点此下载dns2tcp客户端程序dns2tcpc的Windows版。

0x04 搭建隧道

现在终于可以搭建隧道了。

首先在服务器上运行如下命令来启动socks代理:

/etc/init.d/danted start

然后在服务器上运行如下命令来启动dns2tcp的服务器端:

dns2tcpd -f /etc/dns2tcpd.conf -F -d 2

其中参数“-f /etc/dns2tcpd.conf”指定了配置文件,“-F”要求程序在前台运行,“-d 2”指明了输出调试信息,级别为2。作为首次运行,我们加上参数“-F”和“-d 2”。

最后,在办公电脑(客户端)上运行如下命令以建立DNS隧道:

dns2tcpc -r socks -z dns2tcp.werner.wiki -l 8888 -d 2

其中参数“-r socks”指明了要连接的资源,“-z dns2tcp.werner.wiki”指明了建立DNS隧道使用的域名,“-l 8888”指明了隧道的这头监听的端口,“-d 2”的作用和服务器端相同。

至此,隧道搭建完成。

0x05 测试

在办公电脑(客户端)里打开火狐浏览器,设置socksv5代理,IP地址是“127.0.0.1”,端口是8888,如下图所示。

设置火狐浏览器代理

然后用百度搜索“IP”,看到显示当前办公电脑IP地址为“118.xx.xx.120”,如下图所示。

用百度搜索“IP”

这说明DNS隧道搭建成功,实现了办公电脑访问互联网的目标。

0x06 其他

若是前面各步均正确无误,最后出现了错误:

No response from DNS xx.xx.xx.xx

那么可能的原因是中途某个DNS服务器丢弃了TXT类型的奇怪DNS查询请求。如何解决这一问题呢?其实只要我们能绕过中途那台DNS服务器就好了。但问题是我们不知道问题出在哪里,中途有哪些DNS服务器我们也是不知道的。所以一个简单的办法就是绕过中途所有的DNS服务器,把办公电脑的DNS直接设置为“118.xx.xx.120”。这固然是可行的,但还有更简单的办法,我们可以在dns2tcpc的参数中指明要使用的DNS服务器,命令如下:

dns2tcpc -r socks -z dns2tcp.werner.wiki dns.werner.wiki -l 8888

直接指明以“dns.werner.wiki”为DNS服务器,办公电脑发出的DNS查询直接指向“dns.werner.wiki”,不再经过原有DNS服务器转发。这样我们实际上完全抛开了现有的域名系统,也就没有了注册域名设置解析的必要。但若防火墙规则较为严格,只允许目的IP是特定DNS服务器的数据包通过,此方法便行不通了。

0x07 总结

我是在上周五开始尝试用dns2tcp搭建DNS隧道,到这周六才成功。中途几经波折,差点就放弃了。看来做事还是得有锲而不舍的精神,不能轻易放弃。

刚开始只是照着网上的教程搭建,以为自己是明白其中的原理的。若是没有遇到什么问题,顺顺利利地就搭建成功了,便也就失去了学习了机会。直到遇到了难以解决的问题,在网上找不到解决方法时,才只好自己去研究,尝试弄清楚事物的运作原理。只有知道了运作原理,才能定位问题、解决问题。记得《如何思考》一书中也讲到,弄清楚事物的运作原理是进行聪明思考的前提。

其实我在几年前就看到过DNS隧道的文章,当时觉得原理很简单,以为自己全都明白,要搭一个也就是分分钟的事。但现在真正动手去做,才发现并不简单。正应了古人所说“纸上得来终觉浅,绝知此事要躬行”。

]]>
网络安全
总结与计划-大学毕业 https://blog.werner.wiki/86556446/ https://blog.werner.wiki/86556446/ Werner 2018-06-21 0x00 前言

今天上午,我参加了学校的本科生毕业典礼。全程都没什么意思,除了校友代表讲话,讲得倒还有点东西,其他的全是“拜年的话”。学士服也只是借给我们穿了一会儿,之后就回收了。

下午我没有参加班级的毕业照拍摄活动,领取了毕业证等证书后就到图书馆自习,继续看《自卑与超越》。但心很乱,看不进去。吃过晚饭后开始写这篇文章。

0x01 生活

0x01-1 情感

让我来细数一下我大学四年来喜欢过的女生。

第一个是女生是王舒然。大一军训的时候她带着帽子,觉得和潘艺有点像,加了她QQ好友,也很聊得来,就很喜欢她。和她一起爬过一次喻家山。后来告诉了她我喜欢她是因为她长得像我以前喜欢的女生,她就不理我了。感觉有些荒唐,不过早就对她没感觉了。

第二个是焦丽。大一军训时作文比赛一等奖的共同得主。颁奖仪式上认识的。长得很漂亮,至少在我看来真的很漂亮,性格也很好,我真的很喜欢。和她一起爬过一次喻家山,还一起出去玩过,去了黄鹤楼,坐了长江的渡轮。这次游玩是我大学里少有的开心的时候。之后再约她去爬山,她就不再愿意了。过了一年多,到大二了,忽然又约她爬山,她同意了。我原本很开心的,但那天早上她却睡过头了,迟到了近两个小时。由此我知道了她一点也不在乎我。再加上又有一个男生给她打电话约一起自习,我便知道她大概喜欢那个男生。此后我就再没有联系她了。后来在路上遇到过好几次她和一个男生牵着手。现在还是很喜欢她,不过早就放弃了。

第三个是李怡笑。在信息安全导论课上认识的她,留着披肩的卷发,真的很漂亮。在计算机基础课上她展示了自我介绍的ppt,将到了自己的经历。她早年在美国生活,十几岁才回到中国。特长有羽毛球、钢琴等。听完这个介绍,在离开教室时我知道我喜欢上了这个姑娘。之后一直都没有机会认识她。直到有一次她忽然邀请我去听她演出的音乐会。我们的关系这才从陌生的同学转变成熟悉的同学。我有邀请过她和我一起爬喻家山,被她以马上要乐器考级所以需要训练为理由拒绝了。不过我是在QQ上邀请的,她却是在下课后当面给我解释不去的理由的,让我觉得很受尊重,没有被拒绝的不悦。再之后,大概过了半年还是一年,我有邀请过她一起吃饭,这次她回都没回我。这让我很受伤,在很长的一段时间里没有和她联系过。真正和李怡笑成为朋友是在大三和大四。我们第一次一起自习是在2017年的四月,那时我刚刚和华苗闹翻,很不开心。李怡笑问我有做什么项目吗,我回答在做“以诗之名”,并邀请她参与,她同意了。于是便一起自习了一次讨论了要怎么写。这让我很开心。两个月后,端午小长假,因为计算机网络安全实验,李怡校第二次和我一起自习。在这个小小的假期里我和她一起自习了很多次,我真的特别开心,特别感谢计算机网络安全实验。而在嵌入式系统课程里,我和李怡笑一组,合作做实验。那段时间真的很开心。原本我因为华苗的事情差点自杀,只是因为李怡笑如上天派来的天使一般,才拯救了我。到大四时,我和李怡笑已经是相当熟悉的朋友了,我也一直默默的喜欢着她,或者更准确的说是欣赏着她。在网络安全课设期间,我几乎每天从早到晚都和李怡笑一起写代码,这大概是我大学四年最开心的一段时光了。再之后,就不怎么和她联系了。她偶尔会问我一些编程的问题,我也会一一解答。我们还互相推荐过书籍。为了她,我才使用自己不喜欢的app微信读书的。在学院毕业晚会的那天中午,李怡笑特意提醒我晚上有晚会。我想她希望我去看,于是我就去了,原本不打算去的。我去的很早,坐在第二排。若不是第一排是留给老师的,我就坐在第一排了。李怡笑的第一个节目是现代舞。别的女生跳舞需要露腿露腰,李怡笑什么都不露,穿着长裤,就足以艺压全场了。第二个节目是唱歌《岁月神偷》,唱的真的很好听。这大概就是我和李怡笑的全部故事了。我一直都默默地喜欢她,小心翼翼地维护着这份难得的友谊,甚至没敢问她是否有男朋友。我想她也是把我当作朋友的,虽然,更可能的,我只是她发展的人脉。

第四个是董晓娟。我一直都不确定自己是否喜欢董晓娟。但和她在一起时我感到很快乐。就这样吧,做朋友也好。

第五个是李宜蔓。我和她是在公选课“合理用药一点通”上认识的。我坐在第一排,她坐在我后面。我偷偷看了她的学号,社工到了她的QQ。加了好友,之后约自习,晚上我送她会宿舍。在一个暑假里甚至每天发送早安、晚安,就如同男女朋友一般,但可惜快开学时她不怎么理我了。我又认识了华苗,就也没再怎么联系她。现在想来,我若是没有移情别恋,而是锲而不舍的追求她,说不定早就有女朋友了。

第六个是华苗,这也是最后一个。和华苗的故事几天几夜都写不完。简单的说就是在硬件课设上我们相识,之后有一两个月一起自习。然后有一天她不愿再和我一起自习,我感到很伤心。再之后我通过她的室友告诉她我喜欢她,问她是否喜欢我。答案当然是不喜欢。就此我陷入到了深深的痛苦中,越陷越深。我还记得她曾说我是她的天使,每每想起这句话我都很伤心。我有很多次都很想死。一打开百度就搜索自杀,一天到晚想着死啊死的。我烧毁了日记,去了广东韶山朝拜了六祖慧能。后来我给华苗写了一封长长的信,并和她好好地聊了一次。我终于缓了过来,没有死去。当然我能坚持这么久在很大程度上也离不开李怡笑,我很感谢她。

这些都是喜欢过的女生。现在没有喜欢的人了。若说有,大概就是李怡笑了,但我对她更多的是欣赏。而且我知道我和她是不可能的。种种迹象表明她是有男朋友的,而且她要去留学,远在地球的另一边,我能怎么样。我唯一能做的就是让自己变得更加强大。

0x01-2 朋友

我打算给每个朋友认真的写一段话作为告别。

给宋楚瑜:

毕业快乐!祝:工作顺利,家庭幸福。

给董晓娟:

根据不完全统计,你应该是大学里和我聊天最多的人了,在此对你表示感谢。原本应该说一些告别的话的,但我们是去同一个城市的同一个区工作,好像也就没有必要告别了。另,提醒一下,记得给我介绍女朋友[笑哭]

给华苗:

根据不完全统计,你应该是大学四年里和我一起自习最多的人了,在此对你表示感谢。

我因你产生了最大的快乐和最大的痛苦,时过境迁,一切都尘埃落定之时,回想起来,这些体验倒是让我原本无聊至极的大学生活多了一些曲折与波澜。

无论如何,总归是毕业了。祝:毕业快乐!

给李俊:

昨天参加了毕业典礼,今天晚上我就走了。这样就算是正式的结束了大学生活。原本想在走之前的某个周末去找你玩,但没有想到会有这么多事情,一直耽搁未能如愿。以后有机会去深圳一定要记得来找我玩。按照初步的计划,我应该至少会在深圳呆两年。

给李建豪:

毕业快乐!祝:建功立业,豪气干云,早日拿到图灵奖。

给李宜蔓:

毕业快乐!祝愿你一路上高歌勇进,披荆斩棘,粉碎一切艰难险阻,闯出自己的一番天地。以后若是去深圳,一定要记得来找我玩。

给李怡笑:

现在想来,依旧觉得能和你成为朋友是一件很神奇的事情。我这么个生性孤僻、不善交际的人,想有个朋友,尤其是异性朋友,真的很难很难。

感谢这份友谊,让我原本无聊的大学生活多了很多乐趣。第一次听音乐会、第一次吃麦当劳,都是你带我的。和你一起自习、帮你调试代码让我觉得很有趣。在大学里,我只和你合作写过代码,其余的全都是我独立完成的。我想,和漂亮妹子合作写代码的经历一定不是每个程序员都能有的[笑哭]

希望毕业不会导致我们友谊的终结,以后能常联系。若去深圳一定要记得来找我玩。按照初步的计划,我应该至少会在深圳呆两年。

[图:一直觉的,有你做我的朋友,超酷的!]

图:一直觉的,有你做我的朋友,超酷的!

给刘舰阳:

因为一次实习而认识了你,并成为了朋友。根据不完全统计,你应该是大学里和我一起吃饭次数最多的人了,在此对你表示感谢。

在我在华科认识的所有朋友中,你是最像一个正常朋友的。我的其他朋友大概都敬我为“大神”,对我比较客气,这让我很有距离感。只有你老跟我抬杠,甚至还吵过架。但这些经历回想起来让我觉得很开心。

原本应该说一些告别的话的,但我们是去同一个城市的同一个区工作的,好像也就没有必要告别了。

给刘宸硕:

毕业快乐!祝刘书记一路高歌猛进,踏破所有魑魅魍魉,粉碎一切艰难困苦,闯出自己的一番天地。

0x02 学习

这是很重要的一块,会在另一篇公开的文章中单独总结。

0x03 计划

0x03-1 生活

一定要整租,不和任何人合租!!!只有当我独处时,才能最好的提高自己。

学会做饭,并作为特别培养的特长。

坚持锻炼,保护眼睛。

0x03-2 工作

刻意练习告诉我,我可以成为世界一流的黑客,无关天赋,只关努力。我计划用五年的时间,将自己培养为世界一流的黑客。一方面是人生追求,难得的高峰体验一定会让我不枉此生;另一方面是经济考量。我即将要去工作的地方,深圳市南山区,房价是一平米七万,我若是一个普通人,工作一辈子连房都买不起。不过若我是世界一流的黑客,房价就不再是问题了。

现在的问题是,如何运用刻意练习的原则,把我自己培养成世界一流的黑客呢?

在TP-LINK我大概是难以成为世界一流的黑客的,所以初步计划是在TP-LINK干两年,打下基础,再跳槽到一个更高更大的平台。新的平台不要局限在国内,这样的平台可能是在美国的,所以我需要学好英语。

0x03-3 外貌

外貌改进。我是一个追求完美的人,丑陋的外貌让我自惭形愧,我也因此常常感到自卑。而我相信,人的外貌,尤其是男性的外貌,在很大程度上是可以通过自己的努力改变的。

0x04 后记

在华中科技大学主校区图书馆B区二楼2332座位,我写下这些文字。

今天一定是我此生最后一次来这里自习。这个世界总是在不停地发生变化,这个社会总是在不断地制造离别。没有什么能一直陪我走下去,就连组成我身体的细胞也无时无刻不在凋亡与新生,构成我思想的念头也时时刻刻都在生灭与幻化。

但我们依旧会继续前行,因为时间从来不会暂停。无论是勇敢还是怯懦,是接受还是抗拒,该来的将准时到来。

]]>
记录
天生我材 https://blog.werner.wiki/deliberate-practice-book-report/ https://blog.werner.wiki/deliberate-practice-book-report/ Werner 2018-06-14

本文是我阅读《刻意练习:如何从新手到大师》(安德斯·艾利克森,罗伯特·普尔 著,王正林 译,机械工业出版社2016年版)后写的读后感。

一、改变大脑

大学时常常要进行体质测试。我最喜欢的项目是1000米,因为我比周围的同学更擅长跑步,往往可以第一个冲过终点,获得不错的成绩。而我最讨厌的项目是引体向上,因为我身体羸弱,力气很小,无法正握横杆将自己拉起。我甚至连一个标准的引体向上都做不了!而我所在班级的体育委员,同时也是我的室友,却可以在体测时轻松地做二三十个引体向上,拿到满分。我常常想,这一定是因为他天生就身体强壮,具有很好的运动细胞,而我一生下来就体弱多病,没法像他那样潇洒地完成标准引体向上。

为何我无法完成引体向上而体育委员却可以呢?原因很简单,因为他的体重比我轻,又有着比我更加发达的肌肉。也就是说,虽然我们两个是年龄相差无几的成年雄性智人,但身体却是不同的。这样的不同,甚至不用借助任何仪器,仅靠人类眼睛就可以观察到。

是什么造成了这种身体上的不同呢?真的是天生的吗?当然在出生时,体育委员确实可能就比我要强壮一些。但二十几年的成长,使得出生时的微弱优势完全可以忽略不计。既然不是天生的,那么到底是什么使两个年龄相差无几的成年雄性智人在身体上有所不同?我想任何一个人都明白其中的道理:因为他锻炼的比我要多。多加锻炼,就可以让肌肉变得发达,使自己具有更大的力量。

不仅仅是肌肉。我比周围的同学更擅长跑步,是因为我常常跑步,这锻炼了我的呼吸系统和循环系统,使得我的身体更擅长跑步。也就是说,我的身体和其他的不那么擅长跑步的同学的身体是不同的。这样的不同可能难以用肉眼察觉,但若是有一个医生将我和那个不那么擅长跑步的同学的身体解剖,仔细地测量,一定会发现不同之处。

从事体育锻炼就可以改变自己的身体,这是显而易见且举世公认的。如果我从现在开始就认真地进行引体向上的训练,那么一段时间后我的身体会发生变化,和现在的身体有所不同,使得我可以做出现在的身体无法做出的动作————完成标准引体向上。

身体通过锻炼可以发生改变,那么我们身体中最重要的器官:大脑,情况如何呢?我们是否可以通过锻炼来改变自己的大脑,让自己的大脑能够完成原本无法完成的事情?如果回答是肯定的,便意味着一个原本不擅长数学的人,通过正确的训练,便可能改造自己的大脑,使自己变的擅长数学。

幸运的是,一系列的科学研究表明,我们智人的大脑是可以通过训练改变的,无论是成年人的大脑,还是未成年人的大脑。这就给了我们每个人无限的可能性。

二、反馈的重要性

我一直觉得,编程可能是这个世界上最容易自学的事了。因为当我在学习编程时,只要一出现错误,编译器(或解释器)就会立马报错,告诉我第几行有错误,甚至还可以哈告诉我是什么类型的错误。更妙的是,当我的程序写的可能有错时,编译器也会警告我,某某处可能存在怎样的潜在风险。因此,我学习C语言、JavaScript语言或是Python语言时比我学习英语时要顺利的多。因为在学习前者时,只要一出错我就能马上知晓并纠正,而且编译器也不会介意我反复尝试;而在学习英语时,我常常不知道自己是对是错,再加上我性格内向,总是在尽可能地避免一切不必要的与别人的互动,难以去请教老师或其他人。

当我在思考我为何能轻松学会好几门编程语言而学不会一门外语时,我想反馈在其中发挥着极大的作用。这也解释了为何入门级的程序员很多,而更高级的程序员与入门级程序员庞大的基数相比少得可怜,因为一旦能够顺利地编写程序,达到入门的水平,编译器便没有能力再提供有效的反馈了。一个想要有所提高的入门级程序员想要找一个高级程序员来对自己提供有效的反馈,一定比我找一个英语老师对我提供有效的反馈难多了。

我想除了少数幸运儿,大多数入门级程序员都是找不到的。一遍又一遍写着入门级的程序,根本不可能带来任何提高。那么入门级程序员要如何进阶呢?没有人提供反馈,我们可以自己给自己提供反馈。方法之一便是阅读高质量的开源代码,最好是这颗星球上最杰出的程序员写的代码,与自己写的代码相对比,寻找差异。模仿最杰出的程序员写的代码,以提升自己的代码编写水平。我听说学习绘画的学生会临摹著名画家的作品,大概是出于同样的道理。

三、如何坚持

当有两个人做同样的事情,一个人做到中途就放弃了,另一个人坚持完成了这件事时,我们说前者的意志力不够强大,后者的意志力强大。用意志力强大与否便可以解释为何一个人中途放弃,另一个人却能坚持到底。

长久以来,我一直认为自己是一个毫无意志力的人。比如在读大学时,每逢期末考试,我的室友们总是以令我惊讶的意志力坚持复习,有时甚至可以一整晚都坐在那里认真的看书。而我却总是看一小会就觉得无聊,很少有哪次可以认真地复习。

但现在我有了新的想法,可以从另一个角度来解释这些现象。首先,我们假设意志力根本就不存在,人们坚持做某事是出于动机。一个人中途放弃,另一个人却能坚持到底,是因为后者有比前者更强的做完某事的动机。而我的室友们能在期末考试前的复习中投入让我惊讶的努力,是因为他们不认真复习就无法通过考试,具有强烈的复习动机。而我有自信完全不复习也能通过考试,我对分数又没有追求,所以缺乏复习的动机,当然没法好好复习。小孩子常常一会儿要学画画,一会儿又想学乐器,总是难以坚持。家长会说这孩子做事总是三分钟的热情,没有一颗恒心。但实际上,我们也可以说,小孩子无论是想学画画还是想学乐器,都是一时兴起,没有足够的动机,当在真正的学习中遇到困难时,自然容易放弃。

从动机而不是意志力的角度考虑,为了坚持做某事,我们应该增强自己的动机。因此我们应该认真地思考,什么是我们真正想做的。这样的思考与决定,可以使我们做某事的动机在一开始就很强大。此外,坚信自己能够成功的信念,获得身边人的支持,加入有共同目标的小团体是常见的保持和增强动机的方法。

但是,对我这样不想让别人失望的人而言,坚持下去最简单的方法便是:

先给大家造成“我是大神”的假象,然后为了维持这个假象……

这一方法来自“知乎:你是如何强迫自己不断学习提升的?羡辙的回答”。

关于意志力是否真的存在,我根据自己以往的经验得出的结论是意志力并不存在。否则它便是一个过于善变的东西,时强时弱,强弱相易,有时便在一个念头之间。但在网上又能找到许多提升意志力的锻炼方法,甚至有以意志力为名的书。这说明至少还是有许多人相信意志力是存在的。

牛顿力学中的力是存在的吗?在今天大概没有人会否认力的存在。但力确实是一个相当抽象的东西,抽象到受过教育的人中,也只有一部分能很好地理解它。但我们相信力是存在的,因为牛顿力学可以很好地解释这个世界。同样,人们相信意志力,是因为意志力可以解释一些事情。而当动机可以更好地解释意志力可以解释的所有事情时,意志力便没有存在的必要了。

四、人生的悲哀与追求

我是一个很喜欢爬山的人。我喜欢那种向着高处进发,最后攀登到最高处的感觉。

在我读高三时,高二重点班有一个女生跳楼自杀。学校消息封锁工作做得很好,以至于我到现在也不知道那个女生为什么自杀。不过我记得第二天全校停课,班主任给我们讲,不要太在意结果,高三努力拼搏的经历本身便是一种宝贵的财富。但我当时并不十分理解这是什么意思。四年多过去了,有了一些见闻,我似乎明白了这句话的意思,它是说,在一个人的一生之中,很少再能有像高三时的那样努力拼搏了。

在《刻意练习》一书的最后一章写到:

大部分人,甚至是成年人,从来没有在任何领域中达到足够的技能水平,这使得他们无法像杰出人物那样感受到心理表征的真正力量,来规划、执行和评估他们自己的表现。因此,他们从来没有真正理解达到这种水平需要做些什么,不仅仅是花时间,还需要进行高质量的练习。

读到这里,我感到很悲哀。我想这个星球上绝大多数成年智人都活得很辛苦,终日劳累。从事的活动以极低的效率创造财富,自己也只能获得自己创造出的财富中的一小部分。在一生中从来都没有努力地提高自己在任何领域中的技能到足够的水平,以更加高效地创造财富。更让我感到悲哀的是,我也是这样的智人。

但好在,我们的身体,甚至大脑,通过正确的训练都可以改变,几乎有着无穷的潜力。虽然问鼎巅峰并不容易,但比今天的自己更好却近在眼前。我们可以把自己的未来掌握在自己手中,并不断通过自身的努力来提高、完善和改进自己。

]]>
阅读
编写一个简单的MariaDB认证插件 https://blog.werner.wiki/write-a-simple-mariadb-auth-plugin/ https://blog.werner.wiki/write-a-simple-mariadb-auth-plugin/ Werner 2018-05-06 概述

不知从哪天起,大家都不用Mysql转而使用MariaDB了。

众所周知(其实可能很多人不知道)MariaDB支持插件认证。在MariaDB中新建用户,常见的语句是:

CREATE USER 'username'@'host' IDENTIFIED BY 'password';

这样创建的用户,登录时的认证方式是密码。其实创建用户的语句还可以是:

CREATE USER 'username'@'host' IDENTIFIED VIA 'pluginname' USING 'authstring';

这样创建的用户,登录时的认证方式由插件决定。

本文展示了编写一个简单的MariaDB认证插件的全过程。实现的认证机制是用户输入正确的姓名学号即可登录。显然这一认证机制毫无安全性可言,本文重点在于展示插件编写过程。

本文内容基于MariaDB-10.1.8,操作系统是Ubuntu12.04。假设已经安装好了数据库。

基本原理

一个认证插件分为两部分,服务器侧和客户端侧,两者配合,才能完成整个认证过程。最常见的认证情景是服务器侧提问,客户端侧回答。

MariaDB提供了一个通用的客户端侧“dialog”,该客户端侧的功能是接收服务器侧的问题,将问题显示在终端上,并在终端上读取待登录用户的回答,之后将回答发送给服务器侧。它支持不限个数的问答,还支持普通问题和密码问题两种问题,普通问题在待登录用户输入回答时是有回显的,密码问题在待登录用户输入回答时是没有回显的。由于最后一个问题需要特殊处理,所以实际上有四种类型的问题。问题字符串的第一个字节是问题类型,宏定义如下:

    /* mysql/auth_dialog_client.h */
    #define ORDINARY_QUESTION       "\2"
    #define LAST_QUESTION           "\3"
    #define PASSWORD_QUESTION       "\4"
    #define LAST_PASSWORD           "\5"

由于我们想要编写一个简单的认证插件,所以简单起见,客户端侧就使用“dialog”,完全满足要求。这样,我们便只用编写服务器侧部分。

服务器侧部分要做的事情便是与客户端侧的“dialog”通讯,读取输入的姓名学号进行验证。具体实现见下节。

编写代码

套路部分

认证插件的套路如下:

    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <mysql/plugin_auth.h>
    #include <mysql/auth_dialog_client.h>

    static int school_number_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
    {
        /* 该函数是实际上进行认证的地方,
           认证通过返回CR_OK,
           认证失败返回CR_ERROR; */
    }

    static struct st_mysql_auth my_auth_plugin=
    {
        MYSQL_AUTHENTICATION_INTERFACE_VERSION, // 插件的接口版本号
        "dialog", // 客户端侧处理函数,我们直接使用了“dialog”,也可以自定义
        school_number_auth // 服务器侧处理函数
    };

    mysql_declare_plugin(dialog)
    {
        MYSQL_AUTHENTICATION_PLUGIN, // 插件类型
        &my_auth_plugin, // 插件结构体指针
        "school_number", // 插件名
        "Werner", // 作者
        "A simple MariaDB auth plugin", // 描述
        PLUGIN_LICENSE_GPL, // 许可证书
        NULL,
        NULL,
        0x0100,
        NULL,
        NULL,
        NULL,
        0,
    }
    mysql_declare_plugin_end;

mysql_declare_plugin声明了一个插件,其中写明了插件名、插件类型、作者、描述和许可证书等信息, 最重要的是插件结构体指针“&my_auth_plugin”。

插件结构体指针“&my_auth_plugin”指向插件结构体“my_auth_plugin”,该结构体中写明了客户端侧处理函数和服务器侧处理函数。在我们编写的插件中,客户端侧处理函数直接写字符串"dialog",表示使用MariaDB提供的通用客户端侧“dialog”,服务器侧处理函数school_number_auth是实际上进行认证的地方,认证通过返回CR_OK,认证失败返回CR_ERROR。CR_OK和CR_ERROR宏定义如下:

    /* mysql/plugin_auth_common.h */
    #define CR_ERROR 0
    #define CR_OK -1

我们只需要完善函数school_number_auth即可。

认证部分

在这一小节中,我们将完善函数school_number_auth。

首先看该函数的两个参数“MYSQL_PLUGIN_VIO *vio”和“MYSQL_SERVER_AUTH_INFO *info”。

“MYSQL_PLUGIN_VIO”中的“VIO”的含义是虚拟输入输出,它的定义如下所示:

    /* mysql/plugin_auth.h.pp */
    typedef struct st_plugin_vio
    {
      int (*read_packet)(struct st_plugin_vio *vio,
                         unsigned char **buf);
      int (*write_packet)(struct st_plugin_vio *vio,
                          const unsigned char *packet,
                          int packet_len);
      void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info);
    } MYSQL_PLUGIN_VIO;

可以看到它是一个结构体,成员都是函数指针。

顾名思义,函数*read_packet是虚拟的读,从vio中读取以“\0”结尾的字符串,返回读取到的字符串长度。这个读操作是阻塞读。

*write_packet是虚拟的写,向vio中写入一个字符串,需要指定写入长度。同样,写操作是阻塞写。

“MYSQL_SERVER_AUTH_INFO”的定义如下:

    /* mysql/plugin_auth.h.pp */
    typedef struct st_mysql_server_auth_info
    {
      char *user_name; // 客户端发送的用户名
      unsigned int user_name_length; // 客户端发送的用户名长度
      const char *auth_string; // 在mysql.user表中记录的相应账户的authentication_string
      unsigned long auth_string_length; // authentication_string长度
      char authenticated_as[512 +1]; // 代理用户名,传入时为user_name,可设置
      char external_user[512 +1]; // 系统变量external_user显示的值,待设置
      int password_used; // 是否使用密码,待设置
      const char *host_or_ip; // 主机或IP
      unsigned int host_or_ip_length; // 主机或IP的长度
    } MYSQL_SERVER_AUTH_INFO;

由上述定义可知在“MYSQL_SERVER_AUTH_INFO”中可以取到“user_name”和“auth_string”这样的关键字符串。

“password_used”的含义是“是否使用密码”,当认证出错时,报错信息的后面有“Password used: Yes/No”,显示“Yes”还是“No”就由“password_used”决定。默认为“No”,若想保存信息中显示“Yes”,可在school_number_auth函数中设置“password_used”,代码片段如下:

info->password_used= PASSWORD_USED_YES;

明白传入参数的含义后很容易就可以写出school_number_auth函数,其内容如下:

    static int school_number_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
    {
        int pkt_len;
        unsigned char *pkt;

        if (vio->write_packet(vio, (const unsigned char *) ORDINARY_QUESTION "Please enter your name: ", 26))
            return CR_ERROR;

        if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
            return CR_ERROR;

        if (strcmp((const char *) pkt, info->user_name))
            return CR_ERROR;

        if (vio->write_packet(vio, (const unsigned char *) LAST_QUESTION "Please enter your school number: ", 35))
            return CR_ERROR;

        if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
            return CR_ERROR;

        if (strcmp((const char *) pkt, info->auth_string))
            return CR_ERROR;

        return CR_OK;
    }

至此,我们就完成了认证插件的代码编写,将其保存到文件my_auth_plugin.c中,然后进入到下一节。

编译安装

编译

插件的代码写好后按如下命令编译:

gcc $(mysql_config --cflags) -shared -fPIC -DMYSQL_DYNAMIC_PLUGIN -o my_auth_plugin.so my_auth_plugin.c

参数“-DMYSQL_DYNAMIC_PLUGIN”是必不可少的,否则编译的时候不会报错,但在MariaDB中执行“INSTALL PLUGIN”时会报如下错误:

ERROR 1127 (HY000): Can't find symbol '_mysql_plugin_interface_version_' in library

另外一种常见的错误是找不到头文件:

#include <mysql/plugin_auth.h>
#include <mysql/auth_dialog_client.h>

解决方法是安装相关开发包引入需要的头文件,命令是:

sudo rpm -ivh MariaDB-devel-5.2.9-102.el5.x86_64.rpm

sudo apt-get install libmariadbclient-dev

其实不执行上述命令,将MariaDB安装路径下的inculde目录加入到gcc的头文件搜索路径中也可以解决头文件缺失问题。

编译成功后得到my_auth_plugin.so。

复制

编译得到.so文件后需要将.so文件复制到MariaDB的插件目录中。进入MariaDB,用如下语句查询插件目录:

MariaDB [(none)]> SHOW VARIABLES LIKE 'plugin_dir';
+---------------+------------------------------+
| Variable_name | Value                        |
+---------------+------------------------------+
| plugin_dir    | /usr/local/mysql/lib/plugin/ |
+---------------+------------------------------+
1 row in set (0.00 sec)

将my_auth_plugin.so复制到MariaDB的插件目录中:

sudo cp my_auth_plugin.so /usr/local/mysql/lib/plugin/

复制完成后最好修改my_auth_plugin.so的所有者为运行MariaDB的用户,该用户名一般是mysql,命令如下:

sudo chown mysql /usr/local/mysql/lib/plugin/my_auth_plugin.so

安装

只是将.so文件复制到MariaDB的插件目录中还不够,还需要在MariaDB中安装插件,语句如下:

MariaDB [(none)]> INSTALL PLUGIN school_number SONAME 'my_auth_plugin.so';
Query OK, 0 rows affected (0.00 sec)

“school_number”是插件名,定义在mysql_declare_plugin中,my_auth_plugin.so是.so文件名,不要混淆。

有安装就有卸载,如何卸载呢?语句如下:

MariaDB [(none)]> UNINSTALL PLUGIN school_number;
Query OK, 0 rows affected (0.00 sec)

先不要执行卸载语句,或是卸载后重新安装,后面还要用到这个插件。

使用插件

先创建一个使用该插件认证登录的用户,语句如下:

MariaDB [(none)]> CREATE USER 'werner'@'localhost' IDENTIFIED VIA 'school_number' USING 'M201434212';
Query OK, 0 rows affected (0.00 sec)

退出MariaDB后以werner用户登录,可以看到确实使用了插件认证方式,具体过程如下图所示。

插件认证方式登录过程截图

源码下载

可以在这里下载到源码。(其实文中已经出现了全部源码。)

参考文献

  1. MySQL 5.5 Reference Manual
  2. Writing a MariaDB PAM Authentication Plugin
  3. MySQLPlugin之如何编写Auth Plugin
]]>
编程
[译]SVM核函数RBF的参数 https://blog.werner.wiki/rbf-svm-parameters/ https://blog.werner.wiki/rbf-svm-parameters/ Werner 2018-04-17

本文翻译自《RBF SVM parameters》

本例将阐明径向基函数(RBF)做SVM的核函数时参数gamma和C的影响。

直观地,参数gamma定义了单个训练样本的影响大小,值越小影响越大,值越大影响越小。参数gamma可以看作被模型选中作为支持向量的样本的影响半径的倒数。

参数C在误分类样本和分界面简单性之间进行权衡。低的C值使分界面平滑,而高的C值通过增加模型自由度以选择更多支持向量来确保所有样本都被正确分类。

图1是只有两个输入特征和两个可能目标分类(二分类)的简单分类问题在取不同参数值时的决策函数的可视化。注意当有更多特征和目标分类时这种图便画不出来了。

图1

图2是分类器交叉验证的正确率作为C和gamma的函数绘制出的热力图。在这个例子中出于演示目的,我们探索了一个相对较大的参数范围。在实践中,10-3到103的对数范围一般来说是足够的。如果最佳参数位于范围的边界,则可以向该方向扩展范围做进一步的搜索。

图2

注意到热力图中有一个特殊的彩条,它的中间点的值接近于模型表现最好的得分,这是一眼就可以看到的。

模型的行为对于参数gamma十分敏感。如果参数gamma过大,支持向量的影响半径将小到只能影响到它自己,这时再怎么调整参数C也不能避免过拟合。

当参数gamma非常小时,模型会过于拘束不能捕捉到数据的复杂性或“形状”。任何选中的支持向量的影响区域将包含整个训练集。模型的结果将表现地像是用一组超平面分割两类或多类的高密度中心的线性模型。

至于中间值,我们在图2中可以看到,参数gamma和C的对角线上可以找到好的模型。平滑的模型(更小的gamma值)可以通过选择大量的支持向量(更大的C值)来获得更高的复杂度,于是好的模型便出现在了对角线上。

最后我们也观察到对一些gamma的中间值,当C取非常大的值时依旧可以得到表现良好的模型:没有必要通过限制支持向量的数量来实现正则化。RBF核的半径本身就是一个很好的结构调整器。在实践中仍可能会对通过一个较小的C值来限定支持向量的数目感兴趣,这样就可以使模型使用更少的内存,更快地做出预测。

我们还应该指出随机分割的交叉验证会导致结果得分有细微的不同。通过以计算时间为代价增加CV迭代次数n_splits,可以平滑这种细微的不同。在热力图中增加参数C和gamma的取值步长会降低热力图的分辨率。

]]>
杂项