Skip to content

Instantly share code, notes, and snippets.

@keegoo
Last active August 27, 2022 16:10
Show Gist options
  • Save keegoo/25ad3e01e416bb597725c0b4c9dfef68 to your computer and use it in GitHub Desktop.
Save keegoo/25ad3e01e416bb597725c0b4c9dfef68 to your computer and use it in GitHub Desktop.
记录如何写一个http proxy in Rust的学习笔记

Briefing

在Mac和Linux下感觉没有好用的http capture的工具。

其实需求很简单:可以monitor某个ip,监控其中发生的http的traffice,并实时的在Web UI中显示出来。

用过Windows平台的Fiddler,感觉非常易用。

想用Rust来写,不过之前对于Rust和网络编程都没有什么经验。

用此gist来一步一步的记录整个学习过程。

2022年6月20号,新加坡。

@keegoo
Copy link
Author

keegoo commented Jun 20, 2022

想在github上找一个类似的项目看下源代码。发现Github Explore很好用。
github_explore

在Explore下的Topics中,可以搜索感兴趣的关键字,比如http capture。你会得到在Github中所有类似的项目。并且可以按照语言(e.g. Python)进行过滤。
topic_http_in_pythohn

初略的看了几个项目,最终都是依赖于kamene(或scapy)。感觉可以从kamene看起,先了解下http编程在Python中的样子。

@keegoo
Copy link
Author

keegoo commented Jun 20, 2022

在kamene项目中,使用了tcpdump来进行数据包的抓取。

root/kamene/arch/linux.py中,程序检查了系统中是否有tcpdump

with os.popen("tcpdump -V 2> /dev/null") as _f:
    if _f.close() >> 8 == 0x7f:
        log_loading.warning("Failed to execute tcpdump. Check it is installed and in the PATH")
        TCPDUMP=0
    else:
        TCPDUMP=1
del(_f)

tcpdump用于抓取通过网络端口的数据包。

例如,我用sudo tcpdump -i en0来抓取通过en0(在Mac中en0是wifi的网卡端口)的数据包,同时使用ping www.baidu.com,tcpdump就会抓取到ping的数据包。如下:

tcpdump_capture

ping_tcp_package

@keegoo
Copy link
Author

keegoo commented Jun 21, 2022

继续看了下root/kamene/arch/linux.py中的内容,下面的代码(特别是get_working_if())用来获取正在工作中的网口。

import struct
import socket
from fcntl import ioctl

LOOPBACK_NAME = "lo"

# From bits/ioctls.h
SIOCGIFHWADDR = 0x8927  # Get hardware address
SIOCGIFADDR = 0x8915  # get PA address
SIOCGIFNETMASK = 0x891B  # get network PA mask
SIOCGIFNAME = 0x8910  # get iface name
SIOCSIFLINK = 0x8911  # set iface channel
SIOCGIFCONF = 0x8912  # get iface list
SIOCGIFFLAGS = 0x8913  # get flags
SIOCSIFFLAGS = 0x8914  # set flags
SIOCGIFINDEX = 0x8933  # name -> if_index mapping
SIOCGIFCOUNT = 0x8938  # get number of devices
SIOCGSTAMP = 0x8906  # get packet timestamp (as a timeval)

# From if.h
IFF_UP = 0x1  # Interface is up.
IFF_BROADCAST = 0x2  # Broadcast address valid.
IFF_DEBUG = 0x4  # Turn on debugging.
IFF_LOOPBACK = 0x8  # Is a loopback net.
IFF_POINTOPOINT = 0x10  # Interface is point-to-point link.
IFF_NOTRAILERS = 0x20  # Avoid use of trailers.
IFF_RUNNING = 0x40  # Resources allocated.
IFF_NOARP = 0x80  # No address resolution protocol.
IFF_PROMISC = 0x100  # Receive all packets.


def get_if(iff, cmd):
    s = socket.socket()
    ifreq = ioctl(s, cmd, struct.pack("16s16x", bytes(iff, "utf-8")))
    s.close()
    return ifreq


def get_if_list():
    try:
        f = open("/proc/net/dev", "r")
    except IOError:
        print("warning: Can't open /proc/net/dev !")
        return []
    lst = []
    f.readline()
    f.readline()
    for l in f:
        lst.append(l.split(":")[0].strip())
    f.close()
    return lst


def get_working_if():
    for i in get_if_list():
        if i == LOOPBACK_NAME:
            continue
        ifflags = struct.unpack("16xH14x", get_if(i, SIOCGIFFLAGS))[0]
        if ifflags & IFF_UP:
            return i
    return LOOPBACK_NAME


x = get_working_if()
print(x)

其中:

  • 在Linux下,从/proc/net/dev中可以获取网络端口统计信息。可以参考what is /proc/<pid>/net/devLinux下查看网络流量信息两篇文章。
  • from fcntl import ioctl中,fcntl模块是基于文件描述符来进行文件控制和I/O控制。它是Unix系统调用fcntl()和ioctl()的接口。可以参见Python官方文档
  • 代码开头定义了大量的全局变量,注释中说是来源bits/ioctls.hif.h,查了下应该是Linux代码中的头文件,定义了具体的数据格式。
  • 在Mac下运行了此代码,显示没有/proc/net/dev文件。所以此代码应该是仅试用Linux。因为Scapy是跨平台的,所以要找一下Mac下的平台代码在哪里。猜测是在root/kamene/arch/unix.py中 。

@keegoo
Copy link
Author

keegoo commented Jun 22, 2022

早期的时候kamenescapy的一个面向Python3的版本;但后面scapy也加入了对Python3的支持,所以kamene渐渐失去维护了。

感觉scapy的代码更规整一点,所以转去读scapy的代码。

对Mac的支持显示在root/scapy/arch/__init__.py代码中,如下:

from scapy.consts import LINUX, SOLARIS, WINDOWS, BSD
from scapy.config import conf


if LINUX:
    from scapy.arch.linux import *
elif BSD:
    from scapy.arch.unix import read_routes, read_routes6, in6_getifaddr
    from scapy.arch.bpf.core import *
    if not conf.use_pcap:
        # Native
        from scapy.arch.bpf.supersocket import *
        conf.use_bpf = True
elif SOLARIS:
    from scapy.arch.solaris import *
elif WINDOWS:
    from scapy.arch.windows import *
    from scapy.arch.windows.native import *

其中,root/scapy/consts.py

LINUX = platform.startswith("linux")
OPENBSD = platform.startswith("openbsd")
FREEBSD = "freebsd" in platform
NETBSD = platform.startswith("netbsd")
DARWIN = platform.startswith("darwin")
SOLARIS = platform.startswith("sunos")
WINDOWS = platform.startswith("win32")
WINDOWS_XP = platform_lib.release() == "XP"
BSD = DARWIN or FREEBSD or OPENBSD or NETBSD

所以BSD,即root/scapy/arch/unixroot/scapy/arch/bpf/core中存在的才是支持Mac平台的代码。

@keegoo
Copy link
Author

keegoo commented Jun 22, 2022

libpcaptcpdump用来抓取数据包的函数库。你可以在tcpdump的官方网站中找到对libpcap的详尽介绍。这里面推荐Programming with pcap by Tim Carstens.

这里有一段对libpcap的介绍

The libpcap library was written as part of a larger program called TCPDump. The libpcap library allowed developers to write code to receive link-layer packets(Layer 2 in the OSI model) on different flavors of UNIX operating systems without having to worry about the idiosyncrasy of different operating system's network cards and drivers. Essentially, the libpcap library grabs packets directly from the network cards, which allowed developers to write programs to decode, display, or log the packets.

@keegoo
Copy link
Author

keegoo commented Jun 30, 2022

才知道:

  • ifconfig早晚有一天要被ip替换掉
  • netstat已经被ss替换掉了

还是要对socket底层的东西有所了解,不然每次碰到address family,protocol,socket都不知道具体是什么关系。摘自The Open Group Base Specifications Issue 6.

(可能翻译的不准确)

所有的网络协议(protocol)都会关联到一个具体的address family。每个address family都会提供一些基础的services来支持协议(protocol)在特定网络设备中的具体实现。这些services包括packet fragmentation and reassembly, routing, addressing, and basic transport. 所以一个address family会包含多个protocols,每个protocol会对应到一个socket type。。。

...所有的network address都有一个通用的格式,叫sockaddr,被定义在sys/socket.h的文件中。然而每个address family都会在此基础上引入各自不同的结构。。。The field sa_family in the sockaddr structure contains the address family identifier, specifying the format of the sa_data area.

例如下面的例子,来自socket.h:

struct sockaddr {
	sa_family_t	sa_family;	/* address family, AF_xxx	*/
	char		sa_data[14];	/* 14 bytes of protocol address	*/
};

Socket有四种类型:SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM, and SOCK_RAW。

其中SOCK_STREAMSOCK_SEQPACKEt功能相似,提供reliable, sequenced, full-duplex octet streams between the socket and a peer to which the socket is connected. 这里应该就是指TCP那种面向连接的。SOCK_DGRAMSOCK_RAW比较相似,supports connectionless data transfer which is not necessarily acknowledged or reliable. 这里应该是指UDP那种没有链接的。

关于socket()函数。

int socket(int domain, int type, int protocol);

// domain
//     Specifies the address family used in the communications domain.
//     e.g.
//         AF_INET - Internet IP Protocol.
//         AF_INET6 - IP version 6.
//         AF_UNIX - Unix domain sockets.
//         大概还有100多个AF。。。
// type
//     Specifies the type of socket to be created.
// protocol
//     Specifies a particular protocol to be used with the socket.
//     e.g.
//         TCP
//         UDP
//         IIP
//         Internet Control Message Protocol (ICMP)

@keegoo
Copy link
Author

keegoo commented Aug 24, 2022

程序是从run_scapy开始的。

$ sudo ./run_scapy -H
# Welcome to Scapy (2.4.4.dev221)

run_scapy -> SCAPY/__init__.py -> SCAPY/main.py,可以看到所有传给run_scapy的arguments最终都是在main.py中的interact()函数处理的。 所以从interact()看起。

@keegoo
Copy link
Author

keegoo commented Aug 24, 2022

interact()做了一下几件事:

  • parse and check arguments with getopt.
  • init_session()。将libs/all.py中的所有modules都读取到SESSION中。同时加入config的信息,SESSION = {"conf": conf}。其中SESSION是一个dictionary。
  • 千方百计的要在终端上打出一个logo,logo的大小会根据终端的column来决定。
  • 打开一个可以交互的console。使用code.interact(banner=banner_text, local=SESSION),因为SESSION中存了scapy中所有的modules,所以可以直接在这个console中使用这些模块了。

@keegoo
Copy link
Author

keegoo commented Aug 27, 2022

上面提到./run_scapy会启动一个可交互的console,下面是一个如何在console中操作数据包的例子:

send(IP(dst="1.2.3.4")/TCP(dport=502, options=[("MSS", 0)]))

其中

  • IP和TCP都是class,定义在scapy/layers/inet.py中。
  • send应该是定义在scapy/sendrecv.py中。

(to be continue ...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment