基于UnixSocket抽象命名空间的采坑记录

背景

最近接手的某平台的某个服务与业务的通讯交互方式是通过Unix Socket的方式,这种通讯方式相对于已有的TCP来说,效率更高,传输效率大约是TCP的两倍。高效的同时,也是数据可靠的,但是它的缺点是必须本机通讯。由平台的机制,通过这种方式收进行通讯,对性能开销小、效率高的同时也保证了数据传输的可靠性。

Unix Socket显式文件的缺陷

Unix Socket进行通讯时,必须绑定一个文件,也就是需要在服务器上写一个文件,这样就会引发几个问题:

  1. 服务必须具备对应路径的读写权限。
  2. 关闭通讯的时候,文件是不会自动删除的,每次关闭时都要单独增加删除的逻辑
  3. 这个临时文件会被Linux系统或其他程序不经意的删除,会导致一些不可控的问题,并且很难发现。

解决方案和测试难点

UNIX域Socket抽象命名空间是一个很好的解决方案。这个方案采用一个抽象的命名空间,直接在Linux的内存中维护一个虚拟文件系统,也就是说绑定的地址用常规方式是无法看到的,并且在连接断开后,会自动删除。

这种解决方案对于程序来说是一个非常好的方案,但是对于测试来说,却是一个非常难解决的问题,尤其是第一次接触这块内容的时候,会是一个灾难。

问题浮现

通过查看代码可以直接看到Unix Socket绑定的地址是这样的:

//xxx_agent监听地址
const string UnixSocketServerPath = "/tmp/xxx.unix";

如果用Python绑定去发包,会直接报连接被拒绝的错误。

再往下继续跟代码,发现开发在拼地址的时候,做了一个截断的操作:

server_address.sun_family = AF_UNIX;
strncpy(server_address.sun_path+1, address, sizeof(server_address.sun_path) - 1);

整个地址被截了一位。

再试试/tmp/xxx.uni这个地址。依然报错连接拒绝。

找开发沟通后,开发的意思是截断1位之后,会在前面补一个0,但是这个0是一个二进制的0。

与开发确认

于是使用Pythonstruct库做了二进制转换。代码如下:

b_addr = struct.pack("i{a}s".format(a=len(addr)), 0, addr)

依然连接拒绝,通过查看struct库的手册之后,发现使用i做去格式化,会占用4个字节,而在C++的代码中,只截断了1个字节。于是这里需要改用b去做格式化。

b_addr = struct.pack("b{a}s".format(a=len(addr)), 0, addr)

依然报错。同时我还尝试了各种转换字符串等方式,全部都失败了。这个方向已经是一个死循环了。

峰回路转

换一个方向思考,如果从系统的角度来看,这个服务一定是起了某个进程,那么可以去这个进程的proc文件夹中,找到使用的文件描述符.

[root@eb1a5ee8d3d5 /proc/26043/fd]# sudo ls -lrt
total 0
lrwx------ 1 root root 64 Mar  8 14:20 8 -> anon_inode:[eventfd]
lrwx------ 1 root root 64 Mar  8 14:20 7 -> anon_inode:[eventpoll]
lrwx------ 1 root root 64 Mar  8 14:20 6 -> socket:[2628408710]
l-wx------ 1 root root 64 Mar  8 14:20 5 -> /data/server/xxx_agent/log/xxx_agent_20190308_acc.log
l-wx------ 1 root root 64 Mar  8 14:20 4 -> /data/server/xxx_agent/log/xxx_agent_app_20190308_app.log
lrwx------ 1 root root 64 Mar  8 14:20 3 -> /var/tmp/xxx_agent.lock.pid
lrwx------ 1 root root 64 Mar  8 14:20 2 -> /dev/null
lrwx------ 1 root root 64 Mar  8 14:20 1 -> /dev/null
lrwx------ 1 root root 64 Mar  8 14:20 0 -> /dev/null
l-wx------ 1 root root 64 Mar  8 16:37 10 -> /data/server/xxx_agent/log/xxx_agent_stat_2019030815_stat.log

这里有一个socket:[2628408710]引起了我的注意,于是拿着这个去Google。虽然没有搜索到结果,但是这里找到了一个思路,相关信息中有看到可以在/proc/net/tcp位置查看所有进程的tcp的连接信息,那么我们这个协议是unix socket,是否也有对应的连接信息?

[root@eb1a5ee8d3d5 /proc/net]# sudo ls -lrt
total 0
-r--r--r--  1 root root 0 Mar  8 16:40 xfrm_stat
dr-xr-xr-x  2 root root 0 Mar  8 16:40 vlan
-r--r--r--  1 root root 0 Mar  8 16:40 unix
-r--r--r--  1 root root 0 Mar  8 16:40 udplite6
-r--r--r--  1 root root 0 Mar  8 16:40 udplite
-r--r--r--  1 root root 0 Mar  8 16:40 udp6
-r--r--r--  1 root root 0 Mar  8 16:40 udp
-r--r--r--  1 root root 0 Mar  8 16:40 tcp6
-r--r--r--  1 root root 0 Mar  8 16:40 tcp

这里证实了我的猜想,使用cat即可查看unix的信息。

[root@eb1a5ee8d3d5 /proc/net]# cat unix
Num       RefCount Protocol Flags    Type St Inode Path
ffff880058349c00: 00000002 00000000 00000000 0002 01 2629316422 /data/server/xxx_agent/data/xxx_api.sk
ffff880008e83100: 00000002 00000000 00000000 0002 01 2628408710 @/tmp/xxx.uni
ffff8807102aa680: 00000002 00000000 00000000 0001 03 349101185
ffff88001b279880: 00000002 00000000 00000000 0002 01 2629297346
ffff880725603800: 00000002 00000000 00000000 0002 01 2630221924
ffff8800af64de80: 00000002 00000000 00000000 0001 03 349094904
ffff88001b27cd00: 00000002 00000000 00000000 0002 01 2629373949

这里一个很扎眼的@/tmp/xxx.uni。并且地址前面的2628408710与之前在fd里面找到的数字是样的,因此可以确定就是我们要找的Unix Socket的连接信息了。

但是我尝试使用@/tmp/xxx.uni这个地址去连接,依旧是连接失败。

但是有了这个信息,继续去Google,可以找到这个内容的关键名词abstract namespace Unix domain sockets,也就是抽象命名空间。再根据关键字即可找到对应的解决方案。

https://utcc.utoronto.ca/~cks/space/blog/python/AbstractUnixSocketsAndPeercred

这里是最终的解决方案。也就是绑定地址是addr = "\0/tmp/xxx.uni"

这里方法对于Python2Python3都是有效的。

后续思考

addr = "\0/tmp/xxx.uni"这个地址通过print repr(addr)方法打印出来的结果是'\x00/tmp/xxx.uni'。也就是说前面是一个二进制的0。这个方法其实在调试的时候,用struct处理了之后是一个同样的结果。

b_addr = struct.pack("c{a}s".format(a=len(addr)), '\0', addr)

>>>

'\x00/tmp/xxx.uni'

那么为什么在绑定的时候回连接失败呢?

Linux官方说明文档中其实对此是有专门的解释。

abstract: an abstract socket address is distinguished (from a
pathname socket) by the fact that sun_path[0] is a null byte
('\0'). The socket's address in this namespace is given by the
additional bytes in sun_path that are covered by the specified
length of the address structure. (Null bytes in the name have no
special significance.) The name has no connection with filesystem
pathnames. When the address of an abstract socket is returned,
the returned addrlen is greater than sizeof(sa_family_t) (i.e.,
greater than 2), and the name of the socket is contained in the
first (addrlen - sizeof(sa_family_t)) bytes of sun_path.

看官方的定义,似乎这是一个空字节的标识。an abstract socket address is distinguished (from a pathname socket) by the fact that sun_path[0] is a null byte('\0')

而我们用struct转成二进制之后,会有一个字节的长度,虽然打出来的结果在控制台看起来是一样的,但是实际上占了一个字节的长度,这样就会导致绑定的地址不匹配。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容