制作自签名的证书

当搭建一个内部网站系统时,我们可能会使用自签名(self-signed)的证书,它的主要目的不是防伪,而是使用户和系统之间能够进行SSL通信,保证密码等个人信息传输时的安全。

一般情况下,制作证书要经过几个步骤,如上图所示。首先,用openssl genrsa生成一个私钥;然后,用openssl req生成一个签署请求;最后,把请求交给CA,CA签署后就成为该CA认证的证书了。

如果在第二步请求时加上-x509参数,那么就直接生成一个self-signed的证书,即自己充当CA认证自己。

除了这种方式外,在Debian或者Ubuntu系统中有更加简便的方法制作self-signed证书——使用make-ssl-cert(1)命令。该命令在ssl-cert的包里,一般会伴随着Apache的安装而安装,可能单独安装也可以。

make-ssl-cert generate-default-snakeoil命令生成一对万金油式的公钥密钥,可以直接使用。其中,公钥(证书)在/etc/ssl/certs/ssl-cert-snakeoil.pem,密钥在/etc/ssl/private/ssl-cert-snakeoil.key。另外make-ssl-cert还可以根据模板生成公钥密钥,并保存在其他位置,这样我们可以在默认的模板文件的基础上添加一些信息,如Organization等,默认的模板文件在/usr/share/ssl-cert/ssleay.cnf。例子:

# make-ssl-cert /usr/share/ssl-cert/sslsvn.cnf /home/svn/sslsvn.pem

我利用模板文件生成了一个公钥私钥文件,它们的内容放在了一起,一般问题不大,如果实在需要分开,手工分开即可。还有一点值得注意,我发现Debian里,make-ssl-cert是只能由root执行的命令。

使用这个方法,我们就可以快速方便地生成一个或多个公钥密钥对。

设定Ubuntu启动服务

当我们安装了一些Ubuntu的服务后,有时希望配置它为自动启动或不要自动启动。比如我最近安装了tor和privoxy,我希望设定它们不要自动启动。

怎样做呢?Ubuntu虽然是使用upstart进行启动,但是Ubuntu还是模拟了System V的运行级别体系。首先,服务脚本要出现在/etc/init.d/下面。然后/etc/rc0.d/到/etc/rc6.d/分别代表0到6的运行级别,每个级别对应的目录下面,有着刚才init.d目录里脚本的软链接,但是名字和原来不完全一样,Ubuntu启动的时候就是根据rcX.d/目录里软链的名字做出相应的动作。

比如我的Ubuntu 8.10默认是进入运行级别2的,如果rc2.d/目录下有S20tor软链接,表示启动的时候自动运行tor服务。软链接名字的第一个字符或者是S,或者是K,S表示start,K表示stop。接下来数字NN表示优先级,20是默认值,启动的时候先执行K开头的服务,在其中按NN从小到大执行,接下来执行S开头的服务,也是按NN从小到大执行。比如目录里有K50,K60,S10,S20,那么启动到该运行级别时按K50->K60->S10->S20的顺序执行。优先级的目的是提供服务的依赖性,比如A服务要依赖于B服务的运行,可以设置B为S20,A为S21,然后stop的时候要相反顺序。为什么先执行K呢?那是因为切换运行级别的时候,先关闭服务再开启的顺序不容易乱。

说了这么久,相信读者都明白配置Ubuntu启动服务是否自动运行可以直接编辑软连接的名字了。要自动运行,就是要在相应运行级别下加入S开头的软链接。如果不要自动运行,或者把链接删掉,或者设为K开头的名字。需要指出的是,把软链接删掉是不正确的方法,因为当软件包升级的时候,升级脚本运行update-rc.d命令,就会重新加入那个软链接,因此正确的方法是把S改为K,这样不会受update-rc.d命令影响。进一步要指出的是,我们最好也使用update-rc.d命令去进行软链接的操作。

典型用法一,把服务加到2,3,4,5级别自动运行,加到0,1,6自动停止。

sudo update-rc.d foobar defaults

或者

sudo update-rc.d foobar start 2 3 4 5 . stop 0 1 6 .

典型用法二,把服务设定为启动时自动停止。

sudo update-rc.d -f foobar remove
sudo update-rc.d foobar stop 0 1 2 3 4 5 6 .

值得注意的是,如果任意一个rcX.d/目录已经有某脚本的链接,必须像刚才的命令第一行一样先删除原来的链接,再进行添加。

HTML5乒乓球

Solidot介绍,pong是一个用HTML5和javascript编写的乒乓球游戏,我试了一下,Ubuntu 9.10下的Firefox 3.5及8.10下的3.0都能玩,玩的时候要允许网站弹出窗口。

pong

正如游戏的提示,按下p后,游戏就开始。首先会弹出三个窗口,左右两个表示player,中间那个表示球。用户只要按上下键控制player移动就可以了。比赛是11分制的,pong可以适当打发时间,如果刚好有两人,也可以对战。

Happy New Year

昨晚在北京路划算地买到了喜欢的鞋,然后看着东方卫视播林俊杰,郭富城和黎明,倒数2010的新年!

uname2_6_27_16

新的一年,我的Ubuntu 8.10系统也换了两个新的引擎,内核升级为2.6.27-16,nvidia显卡驱程升级到195,现在用mplayer看高清,可以直接用显卡硬解了。

最后,在新的一年里祝愿大家心想事成,新年快乐!

比左边和上边大的矩阵

昨天遇到一道矩阵的题目。这个矩阵有一个特性,对于任意元素,所有它左边和上边的元素都比它小,或者用形式化的语言来说,任意(x,y),凡是i小于等于x和j小于等于y,且(i,j)不是(x,y),那么(i,j)小于(x,y)。

 1  4  5  8  9
 2  6  7 11 13
 3 10 18 19 20
12 15 21 24 28
14 16 22 25 29

上面给出了一个符合要求的例子,题目问如何查找一个元素是否在矩阵中。

我第一个想法是从左上角开始,先在对角线上找。当要找的值介于两个对角元素之间时,再横着找或竖着找。比如要找7,先是1,往对角线6,然后18,这时6小于7,7小于18,再竖着找。但是其实这种方法是错误的,比如我们要找15,首先也是找到6和18之间,但是之后无论横着找还是竖着找,都找不到15,但15是在矩阵里的。这种方法不行是因为虽然题目说任意元素左边和上边元素比它小,但是并不是比它小的元素都在它左边和上边。

我第二个想法是逐行搜索(或者逐列搜索),因为每一行本身是有序的,所以可以用二分法去搜。这种方法是行得通的,于是我就没有再想下去了。后来请教同学,发现有一种更简单的方法,我估计那就是题目想要的正解。

方法是这样的,从右上角开始。如果元素的值比目标值大,那么说明元素所在的列都比目标值大,所以整列都不用考虑,因此往左移动一格;同样道理,如果元素的值比目标值小,那么元素所在的行都比目标值小,这时应该往下移动一行。这样,算法最坏的复杂度不超过O(m+n),m和n是矩阵的宽度和高度。下面我用C实现了一下。

// val - the value searched
// matrix - the matrix we want to find val
// row - number of rows in matrix
// col - number of columns in matrix
// return 1 if found
// return 0 if not found
int matrixFind(const int val, const int *matrix, const int row, const int col)
{
	int i = 0;
	int j = col - 1;

	while (i < row && j >= 0) {
		int cur = matrix[row * i + j];
		if (cur == val)
			return 1;
		else if (cur > val)
			j--;
		else
			i++;
	}
	return 0;
}

我觉得最后一种解法的巧妙之处在于突破了惯性思维,因为一般情况我们都从左上角想起,但是这个算法从右上角开始,当然,从左下角开始也是可以的。

Tracert与Traceroute

traceroute是一个检查网络路径的工具,最初由Van Jacobson实现。它现在已经成为Linux、Cisco IOS以及其他很多操作系统的基本网络工具之一,Windows下也有一个类似的工具tracert。下图是Linux执行traceroute的结果,它能显示每一跳的IP地址及域名(如果反向查询成功),以及对应的往返时间。

traceroute3

traceroute与tracert的原理是很类似的,它们通过设置IP报文里的TTL字段,TTL在每个路由器里都会减1,因此到了指定的跳数之后,TTL刚好减到0。如果这时候还没到达目的地,那么路由器会返回一个类型11的ICMP报文,表示time exceeded(TTL为0)。从而,原主机根据这个ICMP报文就能知道每一跳的IP地址。

但是,其实traceroute具体的实现方法与tracert是不同的,我昨晚在Packet Tracer做了下面的实现。首先看看实验的拓扑图,我做了两次实验,第一次从laptop2a使用tracert,目的地是dhcp(IP地址是8.8.8.2);第二次从core2使用traceroute,目的地同样是dhcp。我的目的是观察到tracert与traceroute的不同。

sample-topology

tracert1

从上图可以看到,tracert使用的ICMP类型8报文,即ICMP Echo Request,与ping程序一样。可以看到,第一次TTL设为了1,可以预见这个报文将会在core2丢弃,并返回ICMP Time Exceeded报文。下图是返回的报文,可以见到ICMP报文里还会附上原来报文的内容。根据《TCP/IP详解》的说明,ICMP的差错报文需要附上原来报文的IP报头,以及后面紧跟着的8个字节,不过现在的实现一般都把更多的字节附上。

tracert2

为什么必须多8个字节呢?因为这样做可以把ICMP Echo Request报文里的标识符字段和序列号字段包含进来,那么如果原来客户端同时开了两个tracert程序,客户端可以根据标识符字段区分交给哪个进程处理。实际上,TCP/IP协议栈就是故意这样设计的,IP报头后面的ICMP报头,或者UDP报头,或者TCP报头,它们的前8个字节就已经包含了端口号、序列号等所有能区分进程的信息,从而ICMP差错报文会把这些信息附上。

对于tracert来说,如果TTL大到可以到达目的地,那么目的地会返回一个ICMP Echo Reply(ICMP报文类型0),就像ping一样。这时,tracert就可以结束了。

traceroute则不一样,它不是使用ICMP Echo Request报文,从下图可以看出,它是使用UDP报文。UDP的源端口用于标识主机上不同的traceroute程序,原理刚才已经分析过了。目的端口从33434开始,每一个报文会把目的端口增加1,直到33534,目的是为了区分返回的报文对应哪个发出的报文。

traceroute1

traceroute2

如果TTL不足以去到目的地,就和tracert的情况一样,路由器返回一个ICMP Time Exceeded报文。那么如果到达目的地会发生什么事呢?上图里有了答案,目的地返回一个类型3,代码也是3的ICMP报文,表示端口不可达。所以,我们还可以知道多一个常识,即主机的33434到33534端口一般不能作为服务用途的,因为traceroute希望目的地返回端口不可达。traceroute收到这个报文之后,就知道已经到达目的地,可以结束程序了。

总结一下,tracert是使用ICMP Echo Request报文去实现,traceroute是使用UDP报文去实现,不过基本原理都是利用IP报文的TTL字段。现在的互联网复杂很多,有些路由器屏蔽了其中一种检查路径的方式(甚至两种都屏蔽),所以同时使用tracert和traceroute,再把结果合并,有时会更好。因此,traceroute实际上更有优势,因为现代版的traceroute只要加-I选项就相当于tracert的功能。据说现在还有一种使用-P选项的方式使用TCP报文的,但是我还没试过。

Ubuntu下使用Packet Tracer

我目前还是使用Ubuntu 8.10系统,Packet Tracer使用的是最新的5.2.1版本。

最新版的PT和以往一样,对Windows,Linux和Mac OS X都提供支持,估计主要原因是使用了Qt4编写的GUI。问题是PT针对Ubuntu的版本,字体显示很是难看(有锯齿),而新版甚至出现输入框输入IP地址时,显示不出来或者只显示部分的问题。

上网查找了一番,原来原因在于PT是用自带Qt库,而这个Qt库可能版本较旧或者存在缺陷。因此解决方法就是不使用PT自带的Qt库,转而使用系统自身的Qt库。对于Ubuntu,首先找到路径/usr/local/PacketTracer5,用编辑器打开packettracer脚本,把其中的指定库路径的LD_LIBRARY_PATH注释掉即可。第二步,系统需要安装部分Qt4的运行库,具体要安装哪些,可以参考里面的lib目录,运行库在Ubuntu里的package名字都是以libqt4开头的,具体名字用apt-cache搜索一下,比如Qt4的XML支持在Ubuntu下的安装,

sudo apt-get install libqt4-xml

其他Qt4的运行库也可以用类似的方法安装。

PT

这样就大功告成了,运行的时候字体和系统的一致了,而且估计Ubuntu下的System菜单配置中全局的Qt4配置也会对PT起作用。完成了之后,就可以愉悦地在Ubuntu上学习CCNA或者其他思科技术了。