惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V2EX - 技术
V2EX - 技术
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
S
Securelist
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
量子位
博客园 - Franky
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
T
Troy Hunt's Blog
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
小众软件
小众软件
P
Palo Alto Networks Blog
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
WordPress大学
WordPress大学
L
Lohrmann on Cybersecurity
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
P
Privacy International News Feed
Last Week in AI
Last Week in AI
Microsoft Security Blog
Microsoft Security Blog
T
Tailwind CSS Blog
博客园_首页
云风的 BLOG
云风的 BLOG
V
Vulnerabilities – Threatpost
D
DataBreaches.Net
Recent Announcements
Recent Announcements
酷 壳 – CoolShell
酷 壳 – CoolShell
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
罗磊的独立博客
Engineering at Meta
Engineering at Meta
Forbes - Security
Forbes - Security
T
Tenable Blog

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
你的Linux上有一个超酷的 TCP 代理!
laixintao · 2019-11-07 · via 卡瓦邦噶!

TL;DR 本文只用 Linux 自带的工具实现了一个可以将 TCP 流量打印出来的中间人代理程序(透明的)。脚本来源于这里。本文是对其工作原理的解释。

一般我们想知道一个 TCP 连接收发的内容时,会用 wireshark/tcpdump 这种工具,来把包抓下来分析查看。我开发 IRedis 的时候,就经常有这种需求,需要看看我的客户端对 server 到底发送了什么和接收了什么。用抓包工具不太方便,信息比较杂乱,操作也比较繁琐。在服务器上部署抓包工具的时候,还要给这些工具特殊权限。

其实这种需求可以不抓包的。因为 Redis 这种程序(RESP3协议)是基于 TCP 的,我们可以做一个代理,将发往 redis-server 的流量打印出来(输出到 stdout),然后再发给 redis-server。将 redis-server 的 Response 打印出来,再返回给客户端。相当于是一个中间人。

实现正向代理和反向代理都可以,反向代理不需要客户端支持,只是开一个端口,将所有发往这个端口的流量转发到 redis-server,所以我们这里用反向代理来实现。

在这篇文章中,我们只用 Linux 自带的工具,nc 来实现这个代理。还要用到 named pipe(因为 Linux 的 pipe 只支持单项传递,这里我们需要一个双向的代理),sed(用来对输出稍作格式化),trap (负责在退出的时候清理 named pipe)。

工作原理

我可以用一个最小化的版本解释这个中间人代理的工作原理,然后我们再来处理格式化输出。

这个中间人代理只需要两个 nc ,一个负责接收客户端那边的输出,将这个输入传给另一个 nc;另一个 nc 接收前一个 nc 的输入,然后传给 Server(比如 redis-server)。Server 传回来的信息也一样,先进入后一个 nc (后面称呼它为 nc2 好了),然后传给前一个 nc (后面叫做 nc1)。在 nc1 -> nc2 和 nc2 -> nc1 的过程中,就可以把传递的东西给打印出来了。

我们先写一个简单的版本:

ZSH

1

$ nc -l 8800 | nc kawabangga.com 80

这一行是监听 8800 端口,把收到的内容通过管道发给 nc2,然后 nc2 将内容发给 kawabangga.com 的 80 端口。其实就是一个 HTTP 代理啦。

用 curl 来试一下:

ZSH

1

2

3

4

5

6

7

8

9

10

$ curl --proxy 127.0.0.1:8800  kawabangga.com -v

*   Trying 127.0.0.1:8800...

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 8800 (#0)

> GET http://kawabangga.com/ HTTP/1.1

> Host: kawabangga.com

> User-Agent: curl/7.66.0

> Accept: */*

> Proxy-Connection: Keep-Alive

>

注意这个 curl 是卡住了,并没有信息返回。

而两个 nc 那一行的输出是:

ZSH

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

$ tmp nc -l 8800 | nc kawabangga.com 80

HTTP/1.1 301 Moved Permanently

Server: wts/1.6.0

Date: Thu, 07 Nov 2019 12:09:52 GMT

Content-Type: text/html; charset=iso-8859-1

Content-Length: 299

Connection: keep-alive

Location: https://www.kawabangga.com/

X-Cache:  from WTS

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">

<html><head>

<title>301 Moved Permanently</title>

</head><body>

<h1>Moved Permanently</h1>

<p>The document has moved <a href="https://www.kawabangga.com/">here</a>.</p>

<hr>

<address>Apache Server at kawabangga.com Port 80</address>

</body></html>

可以看到,第二个 nc 把服务器的返回直接输出到 stdout 了,而并没有返回给 curl。这样的代理是不合格的,因为我们不想夺走本该属于 curl 的 response。

一个优秀的代理应该完整的将服务器的回复还给客户端(curl),像下面这样。至于 stdout 的问题,我们先不管。

Linux 的 pipe 只支持从一边传给另一边,是单向的。那怎么做到后面的进程往前面的进程传呢?答案就是 namedpipe。Named pipe,嗯……顾名思义,就是有名字的管道。在本文中,理解成有了这个名字,我就可以控制重定向的方向,就好了。

我们用这一行,就完全可以实现图2那种传输。即 nc2 通过 named pipe 传给 nc1 ,nc 默认会将自己的 stdin 的内容传回给连接到 nc 的 TCP 另一头。

ZSH

1

$ nc -l 8800 < fifo | nc kawabangga.com 80 > fifo

用 curl 再来试一下:

ZSH

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

curl --proxy 127.0.0.1:8800  kawabangga.com -v

*   Trying 127.0.0.1:8800...

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 8800 (#0)

> GET http://kawabangga.com/ HTTP/1.1

> Host: kawabangga.com

> User-Agent: curl/7.66.0

> Accept: */*

> Proxy-Connection: Keep-Alive

>

* Mark bundle as not supporting multiuse

< HTTP/1.1 301 Moved Permanently

< Server: wts/1.6.0

< Date: Thu, 07 Nov 2019 12:27:56 GMT

< Content-Type: text/html; charset=iso-8859-1

< Content-Length: 299

< Connection: keep-alive

< Location: https://www.kawabangga.com/

< X-Cache:  from WTS

<

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">

<html><head>

<title>301 Moved Permanently</title>

</head><body>

<h1>Moved Permanently</h1>

<p>The document has moved <a href="https://www.kawabangga.com/">here</a>.</p>

<hr>

<address>Apache Server at kawabangga.com Port 80</address>

</body></html>

* Connection #0 to host 127.0.0.1 left intact

而 nc 那个命令行没有任何输出。

OK,代理已经工作了,接下来要做的是将 TCP 内容打印出来。这里要做的是 nc 在将内容输出到 stdout (即管道)的同时,还要打印出来。

……当然是要用 tee 啦!

ZSH

1

$ nc -l 8800 < fifo | tee in.txt | nc kawabangga.com 80 | tee out.txt > fifo

这样可以用 tee 将 TCP 流量分别保存到 in.txt 和 out.txt 里面。可以用上面用过的 curl 测试一下。

因为 stdout 会被管道重定向,所以我们这里无法直接输出到屏幕了。那咋办呢?一不做二不休,再用 named pipe 将 tee 写入到 named pipe 中。

ZSH

1

2

$ mkfifo logging

$ nc -l 8800 < fifo | tee logging | nc kawabangga.com 80 | tee logging > fifo

curl 之后,用 cat logging 就能看到 TCP 的内容啦。(这里如果不使用 cat 读出来 pipe 的内容,curl 会被 block 住)。

最终的脚本

最后,加点细节。写个脚本自动生成 named pipe,使用 trap 在退出的时候把创建的 named pipe 删掉。用 sed 格式化一下输出,分别加上 => 和 <=

代码如下(BSD 的 nc,适用于 OS X):

ZSH

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#!/bin/sh -e

if [ $# != 3 ]

then

    echo "usage: $0 <src-port> <dst-host> <dst-port>"

    exit 0

fi

TMP=`mktemp -d`

BACK=$TMP/pipe.back

SENT=$TMP/pipe.sent

RCVD=$TMP/pipe.rcvd

trap 'rm -rf "$TMP"' EXIT

mkfifo -m 0600 "$BACK" "$SENT" "$RCVD"

sed 's/^/ => /' <"$SENT" &

sed 's/^/<=  /' <"$RCVD" &

nc -l "$1" <"$BACK" | tee "$SENT" | nc "$2" "$3" | tee "$RCVD" >"$BACK"

nc 的 BSD 版本和 GNU 版本不一样。如果你用的不是 Mac,要改下最后一行,加一个 -p. GNU 版本的代码如下:

ZSH

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#!/bin/sh -e

if [ $# != 3 ]

then

    echo "usage: $0 <src-port> <dst-host> <dst-port>"

    exit 0

fi

TMP=`mktemp -d`

BACK=$TMP/pipe.back

SENT=$TMP/pipe.sent

RCVD=$TMP/pipe.rcvd

trap 'rm -rf "$TMP"' EXIT

mkfifo -m 0600 "$BACK" "$SENT" "$RCVD"

sed 's/^/ => /' <"$SENT" &

sed 's/^/<=  /' <"$RCVD" &

nc -l -p "$1" <"$BACK" | tee "$SENT" | nc "$2" "$3" | tee "$RCVD" >"$BACK"

使用效果如下(还是用 curl 测试):

ZSH

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

  tmp ./tcp_proxy.sh 8800 kawabangga.com 80

=> GET http://kawabangga.com/ HTTP/1.1

=> Host: kawabangga.com

=> User-Agent: curl/7.66.0

=> Accept: */*

=> Proxy-Connection: Keep-Alive

=>

<=  HTTP/1.1 301 Moved Permanently

<=  Server: wts/1.6.0

<=  Date: Thu, 07 Nov 2019 12:50:38 GMT

<=  Content-Type: text/html; charset=iso-8859-1

<=  Content-Length: 299

<=  Connection: keep-alive

<=  Location: https://www.kawabangga.com/

<=  X-Cache:  from WTS

<=

<=  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">

<=  <html><head>

<=  <title>301 Moved Permanently</title>

<=  </head><body>

<=  <h1>Moved Permanently</h1>

<=  <p>The document has moved <a href="https://www.kawabangga.com/">here</a>.</p>

<=  <hr>

<=  <address>Apache Server at kawabangga.com Port 80</address>

<=  </body></html>

作为 redis-server 的代理效果:

ZSH

1

2

3

4

5

6

7

8

9

10

11

12

13

14

  tmp ./tcp_proxy.sh 8800 127.0.0.1 6379

=> *2

=> $3

=> get

=> $3

=> foo

<=  $3

<=  bar

=> *2

=> $4

=> llen

=> $7

=> animals

<=  :0

Pretty cool uh? Now I am going to drink some tea!