0%

NAT实验报告

实验目的

给定网络拓扑以及节点的网络地址配置(包含私网地址),实现NAT地址转换功能,使得左侧的私网H1节点能够连接H2节点并传输数据

实验拓扑

实验内容

  1. 运行给定网络拓扑(nat_topo.py)
1
sudo python2 topo/nat_topo.py
  1. 在n1中启动NAT服务
1
2
3
4
5
6
./scripts/disable_arp.sh
./scripts/disable_icmp.sh
./scripts/disable_ip_forward.sh # Archlinux中需要将-p ip改成-p ipv4
./scripts/disable_tcp_rst.sh
export LD_LIBRARY_PATH=.; # 设置lib路径
./nat # 启动NAT服务
  1. 在h2节点启动简易HTTP服务器
1
2
./scripts/disable_offloading.sh # 防止协议栈生成错误的checksum
python2 -m SimpleHTTPServer
  1. 在h1节点启动wget请求h2
1
2
./scripts/disable_offloading.sh # 防止协议栈生成错误的checksum
wget http://159.226.39.123:8000

主要数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct nat_connection {
u8 internal_fin; // indicates whether received fin packet from inner node
u8 external_fin; // indicates whether received fin packet from outer node

u32 internal_seq_end; // the highest sequence number transmitted by inner node
u32 external_seq_end; // the highest sequence number transmitted by outer node

u32 internal_ack; // the highest sequence number acked by inner node
u32 external_ack; // the highest sequence number acked by outer node
};

// the mapping entry used for address translation
struct nat_mapping {
struct list_head list;

u32 internal_ip; // ip address seen in private network
u32 external_ip; // ip address seen in public network (the ip address of external interface)
u16 internal_port; // port seen in private network
u16 external_port; // port seen in public network (assigned by nat)

time_t update_time; // when receiving the latest packet
struct nat_connection conn; // statistics of the tcp connection
};

实验过程

本实验老师已经给了框架,只需要将几个函数的功能实现就可以了。所以这里只介绍每个函数的功能,具体实现见源代码。

代码地址:https://gitee.com/873314461/nat_stack

is_flow_finished

这个函数主要是判断TCP流有没有结束,如果TCP两端发送了FIN报文且seq_end与ack相等,则证明TCP流已经结束了。

同时满足以下4个条件时,TCP流已经结束。

1
2
3
4
internal_fin == true
external_fin == true
internal_seq_end == external_ack
external_seq_end == internal_ack

nat_lookup_external

这个函数是通过external_port在mapping_list中查找对应的nat_mapping对象。

只需要遍历整个链表,找到external_port相等的节点返回即可。

nat_lookup_internal

与上个函数作用类似,只是通过internal_ip和internal_port在mapping_list中查找对应的nat_mapping对象。

assign_external_port

分配一个未使用的external_port。

只需要遍历端口池(nat.assigned_ports),若nat.assigned_ports[port]为0,则将其置为1,则返回port。

free_port

释放一个端口。

将nat.assigned_ports[port]置为0。

nat_insert_mapping

创建一个nat_mapping对象,并将其插入到hash表中。

需要:

  1. 申请内存。
  2. 初始化相应信息。
  3. 插入到list中。
  4. 并将nat_mapping指针返回。

get_packet_direction

这个函数主要是通过packet的内容,判断这个报文的传送方向是DIR_IN还是DIR_OUT。

判断方法是,用packet的目的IP查找路由表,得到相应的iface。若iface与internal_iface相同,则方向为DIR_IN;若iface与external_iface相同,则方向为DIR_OUT。

具体方法如下:

  1. 将packet解析。
  2. 根据目的IP查找路由表。
  3. 比较iface,并返回结果。

nat_update_tcp_connection

这个函数用来更新nat_mapping对象中的tcp信息。包括:最后连接时间(update_time)和nat_connection对象。

  1. 解析packet。
  2. 计算TCP报文的seq_end。
  3. 更新update_time。
  4. 更新nat_connection对象。

PS: 注意网络序主机序的转换!

nat_get_mapping_from_packet

通过packet在nat_mapping_list中查找对应的nat_mapping对象。

若报文方向为DIR_IN,则通过external查找。

若报文方向为DIR_OUT,则通过internal查找。

do_translation

这个函数是NAT实验的核心函数。实现真正的包转发。

  1. 根据(server_ip, server_port)计算hash
  2. 查找packet所对应的nat_mapping对象。
  3. 若未找到nat_mapping对象,且传输方向为DIR_OUT,则插入到nat_mapping_list中。
  4. 修改packet的源、目的IP地址和端口。
  5. 重新计算TCP和IP的checksum。
  6. 更新nat_mapping对象中的TCP信息。
  7. 将报文发送出去。

nat_timeout

这是一个单独运行的线程,用来关闭并回收已经结束的或者超时的TCP流的nat_mapping对象。

每隔一定时间,遍历整个nat_mapping_list,将超时的或者已经结束的nat_mapping对象移除,并释放资源。

PS:遍历list时,一定要使用list_for_each_entry_safe这个循环,只有在这个循环中,才可以对list进行插入或者删除节点的操作。

实验感悟

  • 了解了NAT的工作原理。