2012年3月30日星期五

Linux撞鬼集4之IFS

前阵子自建CDN,因为有攻击导致某地点某IP不可用。在屏蔽攻击后重新需要给用户重新分配IP地址,但是分配之后一直都没有成功。通过查看配置文件,我们确实配置成新的IP地址了,并且确实已经“成功”的重新启动bind服务。但是用户持续报告不可访问,我们直接找ip地址发现实际上仍然是旧的。

一开始我们怀疑可能是不是TTL错误,或者什么别的原因导致用户访问的DNS服务器没有刷新新的解析记录。经过一阵紧张的nslookup和dig之后,还是没有能成功找出问题所在。最后修改named.conf当中的日志级别为debug,重新启动之后发现实际上有一个区域的解析记录并没有成功加载。日志大概意思如下:

unknown type "xxx.xxx.xxx" for record NS (大概这意思吧)

打开配置仔细一看,原来该区域下面应该有一个NS解析记录,原本应该是:

            NS       xxx.xxx.xxx

结果变成了:

NS xxx.xxx.xxx

这一条解析的意思是,当前要解析的域下面的域名服务器是xxx.xxx.xxx。但是由于前面的空格去掉了,结果按照空格划分,第一个表示二级域名的字段从空变成了NS,而记录类型则从NS变成了"xxx.xxx.xxx",而第三个表示解析结果的字段则从"xxx.xxx.xxx"变成了空。于是这一个域名解析记录加载失败,这也导致了后面更改了的解析记录实际上从未被正确加载。之所以域名服务器仍然返回旧的IP,是因为过期时间比较长,在得不到我们的正确解析时,其它的DNS服务器将沿用原来已经有的域名解析结果。

那么,这个空格为什么消失了呢?如果是某个人手动修改的,其手动意图又是啥?经过调查之后发现,原来有人写了一个修改解析地址的脚本,正是这个脚本造成的问题。而之前就已经发现有此问题,解决的办法是通过运行脚本之后手动添加空格来解决,而这一次却忘了。

具体造成问题的脚本命令是read,这个命令十分神奇,会自动根据IFS来分割字段,然后将字段重新组合成一个字符串。用C#来描述,就是:
return string.Join(IFS[0], Console.ReadLine().Split(IFS, StringSplitOptions.RemoveEmptyEntries));

解决的办法很简单,就是将脚本改写成:

while IFS=""; read LINE
do
#  something
done < afile

关于IFS在while上面赋值,我想了一下,原因应该是这样的:
如果在独立一行修改,则导致从此之后的运行环境发生改变,会影响并改变后续所有命令的默认行为。而如果在while上面赋值,则因为是folk一个子进程,并且在子进程中运行修改的缘故,并不会导致外部(当前脚本)环境的变化,也不会改变后续命令的默认行为。

这种环境变量影响程序行为的问题,在Windows算比较难以注意到的。一般也就是PATH之类的变量会导致能否找到可执行程序,但很少会影响程序本身正常行为的。对于read来说,默认按照IFS分割并重组输入,确实让初学者难以注意到。

没有评论:

发表评论