User Space Tcp Rust Implement

prepare

tcp

rfc793

Transmission Control Protocol [Page ii]

rfc7414

A Roadmap for Transmission Control Protocol (TCP)

rfc2398

Some Testing Tools for TCP Implementors

rfc2525

2 . Known implementation problems

ip

3.1 . Internet Header Format

pnet

https://docs.rs/pnet/latest/pnet

tun/tap

https://docs.rs/tun-tap/0.1.2/tun_tap

tun/tap

Universal TUN/TAP device driver ¶

Let’s do it

part 1 make it work

  1. 新建一个rust项目

cargo new nb_trust –bin

添加依赖

[dependencies]

tun-tap = “0.1.2”

代码

use std::io;
fn main() -> io::Result<()> {
    let nic = tun_tap::Iface::new("tun0", tun_tap::Mode::Tun)?;
    let mut buf = [0u8; 1504];
    let nbytes = nic.recv(&mut buf[..])?;
    eprintln!("read {} bytes: {:x?}", nbytes, &buf[..nbytes]);
    Ok(())
}

编译rust

cargo b --release
sudo setcap cap_net_admin=eip ./target/release/nb_trust

设置环境

sudo ip addr add 192.168.0.1/24 dev tun0
sudo ip link set up dev tun0

测试

nb_trust git:(master) ✗ sudo ./target/release/nb_trust

read 52 bytes: [0, 0, 86, dd, 60, 0, 0, 0, 0, 8, 3a, ff, fe, 80, 0, 0, 0, 0, 0, 0, 47, 29, a4, 4e, 5c, b7, 70, db, ff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 85, 0, c4, 2c, 0, 0, 0, 0]

so it is a little annoying here, we would like to write a bash.

#!/bin/bash
cargo b --release
sudo setcap cap_net_admin=eip ./target/release/nb_trust
./target/release/nb_trust &
pid=$!
sudo ip addr add 192.168.0.1/24 dev tun0
sudo ip link set up dev tun0
wait $pid

part 2 recognizes ip protocol and filter

在part 1 我们已经可以收到包了,因为我们想要focus on tcp,所以ip相关的一些东西只需要识别出来就好。

让我们改造下我们的代码, 我们主要做以下几件事

  • 持续地收包并打印出来
  • 现在我们要处理的对象的认知要从一个1504的buf转为一个packet,我们都知道一个正常的packet的mtu是1500,这多出来的4字节实际上是一个packet header.
  • 识别一些ip level的信息。

ping test

ping -I tun0 192.168.0.2

所以当我们可以连续不断地收到数据包之后, 我们不希望只是无序地打印出来,我们希望能够识别这些协议分别是什么?它带有怎样的数据。

由于4字节的头部信息由如下组成:

If flag IFF_NO_PI is not set each frame format is:

Flags [2 bytes]

Proto [2 bytes]

Raw protocol(IP, IPv6, etc) frame.

Universal TUN/TAP device driver ¶

所以我们只需要解析这4字节即可知道flag和proto

以及,我们需要改造一下我们的run.sh, 做一些clean up的工作,以便我们进行重复测试。

trap "kill $pid" INT TERM

添加这条命令方便我们在脚本被中断时,优雅地终止其启动的子进程。关键在于正确管理 $pid 的生命周期,确保信号触发时能准确杀死目标进程。

测试:

read 48 bytes, flags: 0, proto: 86dd: [60, 0, 0, 0, 0, 8, 3a, ff, fe, 80, 0, 0, 0, 0, 0, 0, c0, ed, 51, f1, 2a, 79, 54, 90, ff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 85, 0, eb, 4e, 0, 0, 0, 0]

以下网址包含protocol number对应的协议:

https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml

但是86dd是什么?

测试

试一下ping呢?

read 84 bytes, flags: 0, proto: 800: [45, 0, 0, 54, fa, fd, 40, 0, 40, 1, be, 57, c0, a8, 0, 1, c0, a8, 0, 2, 8, 0, d, dd, 1d, be, 0, 5, 4a, 49, b3, 67, 0, 0, 0, 0, 3, dc, c, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1a, 1b, 1c, 1d, 1e, 1f, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 2a, 2b, 2c, 2d, 2e, 2f, 30, 31, 32, 33, 34, 35, 36, 37]

protocol 800

OH~这显然是一个ip包,所以我们现在识别出了ipv4包,并且可以先只处理ipv4包,这是一个好消息!

part 3 parse ip and tcp header

我们在part2已经成功识别到ip proto并过滤了其他类型的pkt了。所以现在可以识别一些ip proto的字段,由于我们的重点不在这里,我们可以借助一些前人已经写好的轮子。

在 crates.io 中 etherparse 看起来不错。将它加入配置中

etherparse = "0.8.0"

现在我们可以通过Ipv4HeaderSlice解析字段

match etherparse::Ipv4HeaderSlice::from_slice(&mutbuf[4..nbytes]) {
     /* --snipped-- */
 }

那么我们关注ip的哪些字段呢?我们可以看一下ip头部都带有哪些信息:

RFC 791:

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1. 元组信息,在ip头部中肯定是原地址和目的地址
  2. payload 的长度

So, now our code turns to this

match etherparse::Ipv4HeaderSlice::from_slice(&mut buf[4..nbytes]) {
    Ok(p) => {
        let src = p.source_addr();
        let dst = p.destination_addr();
        let proto = p.protocol();
        let payload_len = p.payload_len();
        eprintln!(
            "{} -> {} {}b of protocol {}",
            src, dst, payload_len, proto,
        );
    }
    Err(e) => {
        eprintln!("ignoreing weired packet {:?}", e);
        continue;
    }
}

测试

  1. ping: ping -I tun0 192.168.0.2

192.168.0.1 -> 192.168.0.2 64b of protocol 1

  1. nc tcp: nc 192.168.0.2 80

192.168.0.1 -> 192.168.0.2 40b of protocol 6

我们以及知道proto 1代表icmp, 那proto 6代表tcp么?

是的,以及udp的proto number为17.

所以这实际上是我们这个项目的开始。

我们不会专注于解释tcp header,而是实现protocol。

所以我们可以增加对tcpHeader的解释,直接调库即可

match etherparse::TcpHeaderSlice::from_slice(&mut buf[ETH_HEADER_BYTES + p.slice().len()..nbytes]) {
    Ok(p) => {
        eprintln!(
            "{} -> {} {}b of tcp from port {} to {}",
            src,
            dst,
            payload_len,
            p.source_port(),
            p.destination_port(),
        );
    }
    Err(e) => {
        eprintln!("ignoreing weired packet {:?}", e);
        continue;
    }
}

测试:

192.168.0.1 -> 192.168.0.2 40b of tcp from port 17664 to 60

继续解析tcp heder

如何标识一个tcp会话(session)?一个明显的特征是四元组,即

src_ip, src_port, dst_ip, dst_port

所以我们可以以四元组构造一个Quad结构,后面会用到

#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
struct Quad {
    src: (Ipv4Addr, u16),
    dst: (Ipv4Addr, u16),
}

将tcp解析相关的代码组成一个mod

mod tcp

pubstruct State {}

impl Default for State {
    fn default() -> Self {
        Self {}
    }
}

impl State {
    pubfnon_packet<'a>(
        &mutself,
        iph: etherparse::Ipv4HeaderSlice<'a>,
        tcph: etherparse::TcpHeaderSlice<'a>,
        data: &'a [u8],
    ) {
        eprintln!(
            "{}:{} -> {}:{} {}b of tcp",
            iph.source_addr(),
            tcph.source_port(),
            iph.destination_addr(),
            tcph.destination_port(),
            data.len(),
        );
    }
}

在解析ipheader后我们就可以解析tcpheader了

match etherparse::Ipv4HeaderSlice::from_slice(&buf[ETH_HEADER_BYTES..nbytes]) {
            Ok(iph) => {
                let src = iph.source_addr();
                let dst = iph.destination_addr();
                let proto = iph.protocol();
                if proto != PROTO_TCP {
                    /* only handle tcp now */
                    eprintln!("ignoreing packet proto {:?}", proto);
                    continue;
                }

                let ip_hdr_size = iph.slice().len();
                match etherparse::TcpHeaderSlice::from_slice(
                    &buf[ETH_HEADER_BYTES + iph.slice().len()..nbytes],
                ) {
                    Ok(tcph) => {
                        let datai = ETH_HEADER_BYTES + ip_hdr_size + tcph.slice().len();
                        connections
                            .entry(Quad {
                                src: (src, tcph.source_port()),
                                dst: (dst, tcph.destination_port()),
                            })
                            .or_default()
                            .on_packet(iph, tcph, &buf[datai..nbytes]);
                    }
                    Err(e) => {
                        eprintln!("ignoreing weired packet {:?}", e);
                        continue;
                    }
                }
            }
            Err(e) => {
                eprintln!("ignoreing weired packet {:?}", e);
                continue;
            }
        }

测试

nc 192.168.0.2 443

192.168.0.1:48814 -> 192.168.0.2:443 0b of tcp

修改一下我们的run脚本,使得只有编译成功的时候才配置相关环境

#!/bin/bash
cargo b --release
ext=$?
if [[ $ext -ne 0 ]]; then
        exit $ext
fi

sudo setcap cap_net_admin=eip ./target/release/nb_trust
./target/release/nb_trust &
pid=$!

sudo ip addr add 192.168.0.1/24 dev tun0
sudo ip link set up dev tun0
trap "kill $pid" INT TERM
wait $pid

part 4 tcp protocol startup

对于tcp来说最重要的是其控制协议

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇