当portal认证遇上https

参考资料:

  1. http://www.cnblogs.com/lightsong/p/5229411.html
  2. http://serverfault.com/questions/596844/ssl-certificate-errors-in-captive-portals
  3. https://groups.google.com/forum/#!topic/android-platform/ATSxh0kG7cc

背景:

对于公司内部的无线网络来讲,如果只有一个固定的无线密码,是不科学的;公司员工离职之后依然可以使用公司的网络,每有员工离职就修改密码也不现实;常见的解决办法就是使用portal认证。portal认证说来也容易,在网络设备上配置一下就行了,但是,如果要做一个能用的portal认证还是需要了解一些相关知识的,比如,如何实现使用公司员工账号进行认证?这个问题相对好解决,这里不去讨论;下面讨论一个几乎没有完美解决办法的问题。

一般来讲,连接到一个需要portal认证的网络后,系统会自动弹出来一个portal认证页面(哇赛,咋实现的?),如果手欠,没有登录就关掉了,如何再次打开该页面?

首先,如果不小心关掉了portal认证页面也没关系,打开浏览器,输入http://baidu.com 就能看到portal认证页面; 问题是,百度早使用https了,你很可能输入的会是https://baidu.com ,这样的话,你就看不到portal页面,你只好很生气给给网管打电话:….

网管也很郁闷,好不容易做了点儿事儿,还得因为这个经常挨骂,窝心啊…

下面我们就来讨论一下几个问题:

  1. 为什么连网之后会自动弹出portal认证页面
  2. 为什么输入http://baidu.com 就能看到认证页面
  3. 为什么输入https://baidu.com 就看不到认证页面

先说问题2:

  1. 你连网之后,按照管理员的配置,你的网关上配置了portal认证,你想上网的话,网关必然是知道的,而且网关知道你还没有认证过呢;刚好网关发现你发起的是一个http请求,然后就根本不转发你的请求,而是直接把你重定向到了一个早已准备好的portal认证页面,这一切都得益于http是明文的,于是你就看到了portal认证页面

再说问题3:

  1. 通过问题1的分析可知,因为https是密文的,网关就没有能力将你重定向到portal认证页面了

再说问题1:

  1. 通过上述的分析,操作系统的网络管理部件完全可以在连网之后直接发送一个http请求探测一下是否被拦截了,如果被拦截了,则很可能就是一个portal认证页面,然后弹窗显示就行了,真的吗?
  2. 事实上就是这么实现的
    1. mac上默认访问的http地址是: http://captive.apple.com/hotspot-detect.html
    2. android 手机上据说访问的是: http://clients3.google.com/generate_204
    3. windows上访问的是: http://www.msftncsi.com/ncsi.txt

其实上面的做法存在一些问题,网络上为了安全都在启用https,但是上述几个地址却是内置的http请求,如果进行dns投毒,则很容易让用户访问到自己定制的页面的

网络测速方法

一般来讲,如果要测A机器到B机器的网速,很可能会scp传一个大文件,但是这样显得不够专业,而且可能受到硬盘读写速度的限制,正确的测速姿势为:

A机器上:

B机器上:

关于mysql的max_allowed_packet

max_allowed_packet 定义的是所允许的单条sql语句的大小。

引用官方的说法:http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_allowed_packet


You must increase this value if you are using large BLOB columns or long strings. It should be as big as the largest BLOB you want to use. The protocol limit for max_allowed_packet is 1GB. The value should be a multiple of 1024; nonmultiples are rounded down to the nearest multiple.

解释一下:

  1. 该值的类型为integer,允许的最大值为1G (理论上4个字节的表达能力的上限是4G,也或许协议实现上硬编码了吧)
  2. 该变量的默认值为4MB,一般来讲是够的,如果存储大的BLOB列,可能不够,需要修改该配置
  3. 该值总是1KB的整数倍,最小值为1KB;修改的值应该是1024的整数倍,如果不是整数倍,则会按照小于该值的最接近的那个1024的整数倍进行截断处理,并产生1个warning;(注意: 如果设置的值小于1024,则自动调整为1024)如下:

    其实,将max_allowed_packet 设置为比 net_buffer_length 小的意义不大,所以,这里同样会出现一个warning,只是不会强制将max_allowed_packet修改为大于等于net_buffer_length的
    比较专业的设置该值的方法为:
  4. session的max_allowed_packet是不允许修改的,修改了全局配置对当前session也不会生效的,只有重新连接才能看到变化

记录一次sql优化

一大早到公司,加班一宿的同事就跟我抱怨,说,一个表只有8w条数据,需要更新一列,根据目前进度估算,大概需要3个小时,即使不需要更新,查询每条数据也需要400ms左右;

或许确实配置太低,或许确实表的字段太多,我并没有过于吃惊,但是3小时确实太多,便一起查了一下:

  1. iops并不高,说明内存够用
  2. cpu很高,很可能没有合适的索引,说是用了索引了,还是确认一下吧
  3. 果然搞错了,没有用到索引
  4. 8w条数据,添加索引也应该很快,于是添加了一个索引,耗时不到10s
  5. 执行sql语句,还是很慢,explain显示可能能用到我们刚才添加的索引,但是rows上来看还是全表扫描,为什么?
  6. sql语句大致为: update table1 set bb=1 where aa=123
  7. 现在已经基本可以怀疑aa的类型应该不是int的了,查看表结构,果然aa的类型为varchar
  8. 修改sql语句:update table1 set bb=1 where aa=’123′
  9. 重新开始更新8w条数据,共耗时不到10s

分析:

因为aa是varchar类型,如果要和一个数字比较,势必要对aa进行转换,如此就用不到索引了;这种错误太容易出现了

用shell试图使用大量内存的方法

一条命令:

如果要持续占用一段时间呢?

能否把1G的数据保存到shell的变量中呢?

测试是不行的,可能是太大了吧

关于docker-runc

docker命令行会有一系列的操作容器的方法,如: create 、run、start、exec等等,这些都是通过docker daemon的sock文件由docker daemon实现的,那么docker daemon又是如何实现的呢?

docker-runc就是最终执行的程序,docker-runc也提供了一系列类似于docker的操作,如: create、start、run、exec等,当然,也不完全一样;docker-runc  exec的时候可以指定环境变量、进程的能力等,所以,我们可以直接使用docker-runc来在已有的容器内部执行命令,需要注意的是,docker create时定义的环境变量需要通过选项指定,因为docker-runc不知道这些(奇怪的是,预定义的进程的能力似乎是知道的)

关于容器中进程的继承关系

下图是一个容器中的进程:

  1. 每个容器总是有一个1号进程
  2. 1号进程的父进程在容器中显示为0号进程,而在容器外表现为进程 docker-containerd-shim
  3. 容器中的1号进程不同于宿主机的1号进程那么NB,宿主机的1号进程直接或间接地领导所有其它进程;而容器中的1号进程并不一定直接或间接地领导所有进程,取决于这些进程是否1号进程来创建的,这些进程的父进程死掉后会最终归属到容器内所谓的0号进程而非1号进程,不过,容器中所谓的0号进程并非都是同一个进程
  4. docker top 显示的容器中的进程可能不太全,与是否该进程归属于1号进程没有任何关系;与进程是否最终归属于该容器的管理进程docker-containerd-shim也没有关系,如果是nsenter进入容器,则启动的进程在docker top中是看不到的,虽然该进程在容器中显示的ppid也是0,其实同样是0的ppid却可能不是同一个进程,因为,只要父进程在容器外部,则容器内部显示的ppid就统一为0; 为什么docker top可能看到的不全?docker top是如何实现的呢?参看: https://phpor.net/blog/post/4420

关于PHP Fatal error时获取更多信息

PHP 输出错误时的堆栈情况:

如果需要添加更多的信息,只需要在php_error_cb 函数里面添加就行了

关于C语言之字符串拼接(堆与栈)

且看一个demo:

问题: 如何在errstr信息前面添加一个HOSTNAME 环境变量信息?

  1. 考虑到errstr有可能写到标准错误,也有可能写到标准输出,也有可能根本就不输出,所以,提前输出HOSTNAME 信息显然不合适
  2. 考虑到引用errstr的地方确实不少,最好一开始就拼接HOSTNAME到该errstr 上面去,那么如何拼接呢?

如果是在脚本语言或者带有gc的语言中,完全可以直接在errstr变量前面拼接那个动态获取到的环境变量,如:

但是c中却不能这样,为什么呢?字符串拼接的函数不是有的吗,如:strcat;

事实却并非如此简单,为什么呢?

上面的errstr变量是存储在栈上的,不需要考虑free的问题,原作者考虑到后续一大堆复杂的if分支,如果把errstr放在堆上,处处要考虑是不是该把errstr free掉,得死多少脑细胞啊!

对于HOSTNAME是个运行时的信息,显然没法放到栈上,如果非要和errstr拼接起来的话,errstr必须在堆上,即要动态的malloc,由此可见,对于脚本语言来讲,一个非常简单的逻辑,在C中却如此的麻烦

 

解决办法:

  1. 为了既不需要free,又能拼接字符串,可以定义一个字符数组,如: errstr[255],然后,把需要的信息加进去;为了避免信息太多而不小心越界,可以使用snprintf函数,自动丢弃超长的部分

关于php-fpm reload的优雅实现

我们知道,nginx的reload是不会影响服务的,php-fpm也有reload 功能,而且确实不是restart,但是却是影响服务的,下面给出一种改进方式,是的php-fpm的reload不影响服务

修改源代码:

 

重写init脚本:

 

注意:

  1. 该功能要求linux内核版本较高,需要支持socket的SO_REUSEPORT 特性,我的3.10的内核已经支持了
  2. php-fpm 的pid文件是php-fpm进程来维护的,同时启动多个php-fpm不太方便,所以需要玩一些手段;虽然不指定pid文件参数也可以,但是我不知道如何才能方便地拿到php-fpm的pid信息,所以还是指定了pid参数