0%

使用strace抓取uds数据包

目前很多公司的服务架构都采用了mesh作为服务治理,那么就会引入一个问题,就需要所有的流量都多经历一跳,大部分为了优化性能都会采用UDS作为通信,那么如何抓取uds数据包呢?tcpdump没办法使用,专业点就是使用 bcc ,但是依赖太重了,所以采用 strace 抓取系统调用, 进行实现抓包,就可以完美解决了!

uds 服务端代码

uds 是 unix domain socket,其实就是不实用tcp/ip进行传输,降低一些性能开销,是比较广泛的进程间通信的解决方案!

  1. 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"flag"
"fmt"
"net"
"net/http"
"os"
)

func main() {
socket := flag.String("socket", "", "unix domain socket")
flag.Parse()
if socket == nil || *socket == "" {
panic("not found socket")
}
_ = os.Remove(*socket) // 防止文件存在,启动失败
fmt.Println("pid: ", os.Getpid())
fmt.Println("listen addr: ", *socket)
listen, err := net.Listen("unix", *socket)
if err != nil {
panic(err)
}
if err := http.Serve(listen, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if _, err := writer.Write([]byte(`hello world`)); err != nil {
panic(err)
}
})); err != nil {
panic(err)
}
}
  1. 启动服务
1
go run example/uds/server.go -socket /tmp/tmp.9dd2RLqRnc/uds.socket

curl 测试请求

  • --unix-socket <socket> 用于指定socket地址
  • http:/xx.xx.xxx/api/v1 格式 <scheme>:/<host><path> , 例如这里 scheme是http, host是xx.xx.xx , path是/api/v1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~ curl --unix-socket /tmp/tmp.9dd2RLqRnc/uds.socket -X GET http:/xx.xx.xxx/api/v1 -v
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying /tmp/tmp.9dd2RLqRnc/uds.socket...
* Connected to xx.xx.xxx (/tmp/tmp.9dd2RLqRnc/uds.socket) port 80 (#0)
> GET /api/v1 HTTP/1.1
> Host: xx.xx.xxx
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 18 Apr 2024 05:28:58 GMT
< Content-Length: 11
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host xx.xx.xxx left intact
hello world

使用 strace 抓包

  1. 抓取系统调用 sudo strace -p 3604685 -tt -f -y -s 65535
  • -f 参数主要是适用于多线程/多进程
  • -tt 会附加时间
  • -s <size> 是打印字符串的最大长度,默认很短貌似16个?,这里我设置的是64k
  • -p <pid> 标识抓取的进程ID
  • -y 表示打印出相关文件的路径,对于 open, connect 等函数,它将显示打开或连接的文件或 socket 的路径
  • -o <output> 输出到文件
  • -x: print non-ascii strings in hex
  • -xx:print all strings in hex,比较适用于抓取二进制数据包,后续做处理!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
strace: Process 3604685 attached with 6 threads
[pid 3608084] 14:09:45.676154 epoll_pwait(5<anon_inode:[eventpoll]>, <unfinished ...>
[pid 3604689] 14:09:45.676221 futex(0xc000080148, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid 3604687] 14:09:45.676266 futex(0xc000050548, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid 3604686] 14:09:45.676286 restart_syscall(<... resuming interrupted futex ...> <unfinished ...>
[pid 3604685] 14:09:45.676320 futex(0x85c368, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid 3604688] 14:09:45.676340 futex(0xc000050948, FUTEX_WAIT_PRIVATE, 0, NULL
<unfinished ...>
[pid 3608084] 14:09:47.595066 <... epoll_pwait resumed> [{EPOLLIN, {u32=1924628344, u64=139897599393656}}], 128, -1, NULL, 0) = 1
[pid 3608084] 14:09:47.595186 futex(0x85c720, FUTEX_WAKE_PRIVATE, 1) = 1
[pid 3604686] 14:09:47.595242 <... restart_syscall resumed> ) = 0
[pid 3608084] 14:09:47.595255 accept4(3<socket:[1187928144]>, <unfinished ...>
[pid 3604686] 14:09:47.595330 epoll_pwait(5<anon_inode:[eventpoll]>, <unfinished ...>
[pid 3608084] 14:09:47.595371 <... accept4 resumed> {sa_family=AF_UNIX}, [112->2], SOCK_CLOEXEC|SOCK_NONBLOCK) = 4<socket:[1188331398]>
[pid 3604686] 14:09:47.595399 <... epoll_pwait resumed> [], 128, 0, NULL, 0) = 0
[pid 3608084] 14:09:47.595462 epoll_ctl(5<anon_inode:[eventpoll]>, EPOLL_CTL_ADD, 4<socket:[1188331398]>, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=1924628104, u64=139897599393416}} <unfinished ...>
[pid 3604686] 14:09:47.595597 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3608084] 14:09:47.595621 <... epoll_ctl resumed> ) = 0
[pid 3608084] 14:09:47.595655 getsockname(4<socket:[1188331398]>, {sa_family=AF_UNIX, sun_path="/tmp/tmp.9dd2RLqRnc/uds.socket"}, [112->33]) = 0
[pid 3604686] 14:09:47.595700 <... nanosleep resumed> NULL) = 0
[pid 3604686] 14:09:47.595721 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3608084] 14:09:47.595773 futex(0x85c368, FUTEX_WAKE_PRIVATE, 1) = 1
[pid 3604685] 14:09:47.595863 <... futex resumed> ) = 0
[pid 3608084] 14:09:47.595876 accept4(3<socket:[1187928144]>, <unfinished ...>
[pid 3604686] 14:09:47.595901 <... nanosleep resumed> NULL) = 0
[pid 3604685] 14:09:47.595914 epoll_pwait(5<anon_inode:[eventpoll]>, <unfinished ...>
[pid 3608084] 14:09:47.595939 <... accept4 resumed> 0xc000104b28, [112], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
[pid 3604686] 14:09:47.596022 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3604685] 14:09:47.596041 <... epoll_pwait resumed> [{EPOLLIN|EPOLLOUT, {u32=1924628104, u64=139897599393416}}], 128, 0, NULL, 0) = 1
[pid 3604685] 14:09:47.596071 epoll_pwait(5<anon_inode:[eventpoll]>, <unfinished ...>
[pid 3608084] 14:09:47.596109 read(4<socket:[1188331398]>, <unfinished ...>
[pid 3604686] 14:09:47.596135 <... nanosleep resumed> NULL) = 0
[pid 3608084] 14:09:47.596155 <... read resumed> "GET /api/v1 HTTP/1.1\r\nHost: xx.xx.xxx\r\nUser-Agent: curl/7.64.0\r\nAccept: */*\r\n\r\n", 4096) = 79
[pid 3604686] 14:09:47.596179 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3608084] 14:09:47.596258 futex(0xc000080148, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 3604686] 14:09:47.596284 <... nanosleep resumed> NULL) = 0
[pid 3608084] 14:09:47.596297 <... futex resumed> ) = 1
[pid 3604689] 14:09:47.596309 <... futex resumed> ) = 0
[pid 3604686] 14:09:47.596320 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3608084] 14:09:47.596406 write(4<socket:[1188331398]>, "HTTP/1.1 200 OK\r\nDate: Thu, 18 Apr 2024 06:09:47 GMT\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nhello world", 128 <unfinished ...>
[pid 3604689] 14:09:47.596487 nanosleep({tv_sec=0, tv_nsec=3000}, <unfinished ...>
[pid 3608084] 14:09:47.596567 <... write resumed> ) = 128
[pid 3604686] 14:09:47.596582 <... nanosleep resumed> NULL) = 0
[pid 3604685] 14:09:47.596600 <... epoll_pwait resumed> [{EPOLLOUT, {u32=1924628104, u64=139897599393416}}], 128, -1, NULL, 0) = 1
[pid 3608084] 14:09:47.596620 read(4<socket:[1188331398]>, <unfinished ...>
[pid 3604689] 14:09:47.596634 <... nanosleep resumed> NULL) = 0
[pid 3604686] 14:09:47.596647 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3608084] 14:09:47.596659 <... read resumed> 0xc0001b0000, 4096) = -1 EAGAIN (Resource temporarily unavailable)
[pid 3604689] 14:09:47.596678 epoll_pwait(5<anon_inode:[eventpoll]>, <unfinished ...>
[pid 3608084] 14:09:47.596740 futex(0xc000080548, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid 3604689] 14:09:47.596774 <... epoll_pwait resumed> [{EPOLLIN|EPOLLOUT|EPOLLHUP|EPOLLRDHUP, {u32=1924628104, u64=139897599393416}}], 128, -1, NULL, 0) = 1
[pid 3604686] 14:09:47.596807 <... nanosleep resumed> NULL) = 0
[pid 3604685] 14:09:47.596816 epoll_pwait(5<anon_inode:[eventpoll]>, <unfinished ...>
[pid 3604689] 14:09:47.596907 read(4<socket:[1188331398]>, <unfinished ...>
[pid 3604686] 14:09:47.596975 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3604689] 14:09:47.597008 <... read resumed> "", 4096) = 0
[pid 3604685] 14:09:47.597022 <... epoll_pwait resumed> [], 128, 0, NULL, 0) = 0
[pid 3604689] 14:09:47.597063 epoll_ctl(5<anon_inode:[eventpoll]>, EPOLL_CTL_DEL, 4<socket:[1188331398]>, 0xc00010595c <unfinished ...>
[pid 3604686] 14:09:47.597126 <... nanosleep resumed> NULL) = 0
[pid 3604689] 14:09:47.597152 <... epoll_ctl resumed> ) = 0
[pid 3604686] 14:09:47.597171 nanosleep({tv_sec=0, tv_nsec=20000}, <unfinished ...>
[pid 3604689] 14:09:47.597190 close(4<socket:[1188331398]> <unfinished ...>
[pid 3604685] 14:09:47.597211 epoll_pwait(5<anon_inode:[eventpoll]>, <unfinished ...>
[pid 3604689] 14:09:47.597245 <... close resumed> ) = 0
[pid 3604689] 14:09:47.597284 futex(0xc000080148, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid 3604686] 14:09:47.597309 <... nanosleep resumed> NULL) = 0
[pid 3604686] 14:09:47.597335 futex(0x85c720, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0}

这里我们就可以看到请求/响应的内容了!

  1. 内容太杂了,我们可能仅关注我们需要关注的系统调用,比如 readwriteclose 等系统调用,可以通过参数

-e read,write,close:这个选项告诉 strace 只跟踪指定的系统调用。在这个例子中,只有 readwriteclose 这三个系统调用被跟踪。

我们使用 sudo strace -p 3604685 -tt -f -y -s 65535 -e read,write,close再抓取一次包呢?

1
2
3
4
5
strace: Process 3604685 attached with 6 threads
[pid 3604685] 14:16:15.263803 read(4<socket:[1188372268]>, "GET /api/v1 HTTP/1.1\r\nHost: xx.xx.xxx\r\nUser-Agent: curl/7.64.0\r\nAccept: */*\r\n\r\n", 4096) = 79
[pid 3604685] 14:16:15.264236 write(4<socket:[1188372268]>, "HTTP/1.1 200 OK\r\nDate: Thu, 18 Apr 2024 06:16:15 GMT\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nhello world", 128) = 128
[pid 3608084] 14:16:15.264523 read(4<socket:[1188372268]>, "", 4096) = 0
[pid 3608084] 14:16:15.264702 close(4<socket:[1188372268]>) = 0

这么就清晰许多了!解释一下含义

1
2
3
4
5
6
7
8
9
10
# 内容
read(4<socket:[1188372268]>, "GET /api/v1 HTTP/1.1\r\nHost: xx.xx.xxx\r\nUser-Agent: curl/7.64.0\r\nAccept: */*\r\n\r\n", 4096) = 79

# read 系统调用,头文件 #include <unistd.h>
# extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;

# 4<socket:[1188372268]> 表示 fd=4
# "GET..." 表示__buf内容
# 4096 表示 __nbytes
# 79 表示读取了多少字节

备注

对于 hex 的解析,这里有个简单例子,大家可以看一下!逻辑很简单!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"encoding/hex"
"fmt"
"regexp"
"strings"
)

var input = `[pid 3604687] 13:44:54.848366 write(4<\x73\x6f\x63\x6b\x65\x74\x3a\x5b\x31\x31\x38\x38\x30\x38\x37\x30\x33\x38\x5d>, 4<\x6f\x6f\x63\x6b\x65\x74\x3a\x5b\x31\x31\x38\x38\x30\x38\x37\x30\x33\x38\x5d>, "\x48\x54\x54\x50\x2f\x31\x2e\x31\x20\x32\x30\x30\x20\x4f\x4b\x0d\x0a\x44\x61\x74\x65\x3a\x20\x54\x68\x75\x2c\x20\x31\x38\x20\x41\x70\x72\x20\x32\x30\x32\x34\x20\x30\x35\x3a\x34\x34\x3a\x35\x34\x20\x47\x4d\x54\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x4c\x65\x6e\x67\x74\x68\x3a\x20\x31\x31\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70\x65\x3a\x20\x74\x65\x78\x74\x2f\x70\x6c\x61\x69\x6e\x3b\x20\x63\x68\x61\x72\x73\x65\x74\x3d\x75\x74\x66\x2d\x38\x0d\x0a\x0d\x0a\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64", 128 <unfinished ...>`

func hexToString(input string) (string, error) {
input = strings.ReplaceAll(input, "\\x", "")
decodeString, err := hex.DecodeString(input)
if err != nil {
return "", err
}
return string(decodeString), nil
}

func handleSubMatch(input string, subs [][]string) string {
for _, sub := range subs {
if len(sub) != 2 {
continue
}
originStr := sub[1]
if toStr, err := hexToString(originStr); err == nil {
input = strings.ReplaceAll(input, originStr, toStr)
}
}
return input
}

func main() {
match1 := regexp.MustCompile(`"([\\x0-9a-f]+)"`)
match2 := regexp.MustCompile(`\d<([\\x0-9a-f]+)>`)
input = handleSubMatch(input, match1.FindAllStringSubmatch(input, -1))
input = handleSubMatch(input, match2.FindAllStringSubmatch(input, -1))
fmt.Println(input)
}
本人坚持原创技术分享,如果你觉得文章对您有用,请随意打赏! 如果有需要咨询的请发送到我的邮箱!