关于ipv6:大型生产事故现场录播

41次阅读

共计 2972 个字符,预计需要花费 8 分钟才能阅读完成。

事件是酱紫的。
那天上午,轻轻松松实现了一个新性能的开发,本来打算着混到中午吃个饭,美美的午个觉,下午跑跑测试,摸摸鱼,划划水,欢快的一天就过来了。
正当我在为我的完满打算骄傲不已的时候,微信群闪过一条音讯,有人艾特我。
特么的!!!
这个时候!竟然有人艾特我!!!
过后我就不乐意了。指名道姓的艾特我,指定没坏事。

划开手机一看,果然,生产出事变了。而且看起来事件还比拟紧急重大。

我给大家简略形容一下事件的通过。
咱们(咳咳,请把 ” 们 ” 字去掉,说的就是你,别想甩锅)开发了一个能够采集宿主机硬件指标的这么一个性能。其中有一项是获取 IPV6 的地址,而后这个 IPV6 地址获取的时候出问题了,原本不应该获取 fe80 结尾的地址(fe80 结尾的地址相似于 IPV4 的 192.168, 是局域网的地址,这个是应该 pass 掉的),却获取成了一串乌七八糟的数字。

过后的状况是这样的。客户提交了降级单,他们也怕出问题,就先降级了两千台设施,并打算当天早晨降级残余的两万多台。
而后到了快中午的时候,发现这两千台中,有那么一百台的样子,获取的地址不对。
如果嫌我说的啰嗦的话,请我师爷来翻译翻译。

简略来说,就是客户中午的时候发现了程序有问题,然而因为早晨要降级所有生产环境,所以下午必须给他们搞定。不能耽搁他们早晨的降级。此诚危急存亡之秋也。
什么状况啊?
劳资特么一脸懵逼啊!
没方法啊,谁让咱是吃这碗饭的呢?于是乎,简略拾掇一下,连午觉都不顾上了,背上我的蓝色小书包,坐上公交车,间接奔赴客户现场。

过后我心里是一点底都没有,这问题咱也没碰到过啊,于是脑中疾速过了一遍过后的代码逻辑,看看能不能找到能够甩锅的中央。嗯,测试!肯定是测试没测好!带着问题就上线了!
于是乎:

人家很明确的通知我,测试没有发现问题。
这就难堪了。公司环境没有发现问题,pit 环境也没有发现问题(备注:该 pit 环境是客户提供的一套有限靠近生产环境的机器)。也就是说,这个问题仿佛是生产环境独有的。
这特么就扯了。。。
这都什么鬼啊?
没方法了,去现场看吧。

通过了简短而简单的客户园区入场手续后,终于来到了生产机房。客户找出两台有问题的机器,说,你调试吧!而后也不论我,忙他本人的工作去了。
生产的机器,如果玩过的大家都应该理解,客户不让做的事件千万别做,千万别想着删库跑路,而后坐船去新加坡,越南,柬埔寨。
特地是我过后拿到的是 root 权限。客户更是慎之又慎,就连重启程序都得通过他们批准。危险的命令想都不要想。
那还调试个鸡儿啊。

但这个时候我可不能怂啊,老夫一世英名是否保住,就看在此一举了。
首先我给客户强调,这个问题百年难得一遇,必定不是那么好解决的,今天下午就算能找到根因,回去还要批改代码,测试,早晨降级预计有点来不及。
打了这个预防针,客户也晓得问题辣手,就有心里筹备了。说,你先查起因吧,切实难解决的话今晚就暂缓降级,周五之前能搞定就行。
这样我就放心大胆的搞了。

先说一下咱们这个程序获取 IPV6 的逻辑。
咱们这个程序是用 C 语言开发的,为了反对跨平台,咱们引入了一个叫 sigar 的第三方库。这个 sigar 库是专门用来获取机器硬件指标的,而且对不同的平台操作系统都有实现。
Linux 下,获取 IPV6 的逻辑是读取 /proc/net/if_net6 的配置文件。这个文件的第一项就是 IPV6 的地址。
简略来说,获取 ipv6 的地址,就是读取文件中指定字符串的内容的实现。听起来剁么简略的事件。就这破逻辑,还能出啥问题?
sigar库里这段逻辑在 src/os/linux/linux_sigar.c 中的 sigar_net_interface_ipv6_config_get 函数中。当然咱们做了一些调整,最终的样子是这样的:

while (fscanf(fp, "%32s %02x %02x %02x %02x %16s\n",
                  addr, &idx, &prefix, &scope, &flags, ifname) != EOF)
    {if (strEQ(name, ifname) && addr != strstr(addr, "fe80")) {
            status = SIGAR_OK;
            break;
        }
    }

说一下 /proc/net/if_net6 这个文件,这个文件里寄存的是接口和单播地址。其外部格局如下:
|addr(32 位)|

addr(32 位) if_index(个别 2 位十六进制数字) prefix(个别 2 位十六进制数字) scope(个别 2 位十六进制数字) flags(个别 2 位十六进制数字) ifname(16 位)
ipv6 地址 接口 ID 前缀长度 地址适用范围 标记位 网卡名称

这样一看,下面的代码解析,仿佛没有什么问题嘛。
但,等等,这都是什么鬼?

我嗅到了一丝立功的气味,尽管说不上哪里不对,但我感觉这里会有问题。第二位,也就是 if_index,为啥呈现了这么多三位的?
一个三位的字符串,却用 %02x,这是会出问题的呀。
可是,为什么会呈现 3 位的呢?

咱也不晓得啊。没方法,祭出百度吧!
然鹅,一上百度,是这个样子的:

家喻户晓,大天朝的技术文章,百分之八十被号称吃 (C) 屎(S)都 (D) 难(N)的某网站给劫持了,只有一搜寻,千篇一律的进去的都是该网站的链接,而且内容反复率之高,含金量之低,令人咋舌。
既然度娘不助我,我就只能寻求谷嫂的帮忙了。
可是!

完犊子了,这可咋办?
我突然想到我一个高中同学发小是做海内竞价的,本人守业当了老板,咱要不去试试白嫖一波?

嗯。。。弄起来还是挺麻烦的,但好歹是弄到了。
谷歌一游,大多数也只是通知你 if_net6 文件是啥含意的,没有通知我这个 if_index 到底应该有几位。
而后我在 if.h 头文件里发现了这个 if_index 的定义:

struct if_nameindex
  {
    unsigned int if_index;  /* 1, 2, ... */
    char *if_name;      /* null terminated name: "eth0", ... */
  };

int类型,实践上能够有 8 位啊,汗~
得,不论了。为了验证我这一猜测是对的,的确是 if_index 有三位导致的,我让测试的共事在 pit 环境找 if_net6 文件中有三位的测试,很快:

而后就是欢快的批改代码的过程了。咱也不论有的没的,搞什么位数啊,代码一旦 hard code 了,下次有变动还是得出问题,不如一把搞定:

while (fscanf(fp, "%s %s %s %s %s %s\n",
                  addr, idx, prefix, scope, flags, ifname) != EOF)
    {if (strEQ(name, ifname) && addr != strstr(addr, "fe80")) {
            status = SIGAR_OK;
            break;
        }
    }

事实上,fscanf函数解决这种带固定宽度的解析时,如果字符串有三位,但只解析了两位,并不会把第三位抛弃,而是把第三位挤到了下一个域。
举个例子。

上图中第二条记录,if_index321,依照上述代码逻辑解决时,会将32 当做 if_index, 剩下的1 当做 prefix40 当做 scope20flags80ifnameveth1ffa1a8 原本是网卡名,它会将其认为是第二条的 ipv6 地址,导致前面的全副错位。
嗯。。。接下来就是把代码改好,从新公布补丁,让客户更新测试。
尽管过程是波折的,但后果是好的。

至此,故事整齐段落,惋惜了了我的午觉啊。

正文完
 0