文章目录
由于 防火墙的默认策略 应该是DROP,对于像 FTP 这种在被动模式下,需要新开端口建立数据通道的协议而言,防火墙不能够主动的开辟相关的端口,也就在默认DROP情况下,FTP的数据端口不能通信,就会出现能登录成功,但获取不到目录的情况。关于 FTP 的介绍,可参考这篇 帖子。
在 Linux 中,Netfilter 自带一个 conntrack 模块,于是就先以 ftp 协议为例研究了一下。其主要原理是,监控相应端口的流量,尝试获取到含有动态端口的数据包,然后,将相应端口的数据包标记成RELATED的数据(猜测是这样),因而,使其实现通信。
实现 ftp 动态端口的模块 文件是 net/netfilter/nf_conntrack_ftp.c,include/linux/netfilter/nf_conntrack_ftp.h,include/uapi/linux/netfilter/nf_conntrack_ftp.h,net/netfilter/nf_nat_ftp.c,还有一个 net/netfilter/ip_vs_ftp.c 文件暂时没看懂干啥的。这些文件均来自于 linux 内核文件。在 Ubuntu 18.04 系统中,以 apt-get install linux-source-5.0.0 的方式获取。
测试系统 Ubuntu 18.04,内核版本:5.0.0-31-generic,iptables 版本:1.6.1
nf_conntrack_ftp 模块加载
首先,了解如何开启linux系统自带的 ftp 协议的动态端口模块。
- 准备ftp客户端和服务器,linux下可使用vsftpd作为ftp服务器,相关安装方法,可参考这篇帖子。windows下的ftp客户端,可使用WinScp这个软件。
- 设置系统对conntrack模块的支持
vim /etc/sysctl.conf
添加一行:
net.netfilter.nf_conntrack_helper=1
然后使用sysctl -p
使其生效。
主要是新版系统需要加这个一句,旧版系统不需要,旧版似乎是默认开启的,此部分可参考这个文章。 - 加载 nf_conntrack_ftp 模块
modprobe nf_conntrack modprobe nf_conntrack_ftp
- iptables 测试规则(此处仅是ipv4的测试规则,ipv6需要用到ip6tables)
此外,还可能需要要一条这样的规则iptables -F # 清空规则 iptables -P INPUT ACCEPT iptables -P OUTPUT ACCEPT iptables -P FORWARD DROP # FORWARD链规则默认DROP iptables -A FORWARD -p tcp -m state --state NEW --dport 21 -j ACCEPT iptables -A FORWARD -p tcp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # 按照对上面那篇文章的理解,应该是用下面这个规则的, # 但是,测试发现,只用下面这个反而通信不了,用上面那句规则,模块是会工作的。有点迷,可能没理解好。 # iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -m helper --helper opc -p tcp -j ACCEPT
iptables -A PREROUTING -t raw -p tcp --dport 21 -j CT --helper ftp
注意:
(1)必须要用-D选项删除上面这条规则后,nf_conntrack_ftp模块才能被卸载,否则卸载时会提示被使用。
(2)应使用 iptables -t raw-nL 查看这条规则的配置。
此处在 FORWARD 链上测试,也可自行改成INPUT和OUTPUT链,Ubuntu 18.04 使用 bridge_utils 配置网桥后,需要额外加载模块,FOWWARD 链规则才生效,相关操作可见这篇帖子。
5. 开启 ftp 服务器后,使用 winscp 客户端对其进行访问测试。
默认情况下,winscp 是用 被动模式通信的,被动模式的确需要动态端口开放这这一策略。
不过首先要明确一点,防火墙上做动态端口开放,显然要求协议是明文的,加密是不行的,加密就没法从数据包里获得地址和动态端口信息了(除非知道秘钥,并且重写模块)。所以,连接设置如下:
如果选用主动模式通信,可点击上面界面中的“高级”,然后进入“连接”,取消“被动模式”的选中即可。主动模式,似乎只需要再开放20端口即可,不用conntrack,当然这和防火墙策略有关,也许不想实时开着20端口呢。
然后,连接就能读取成功,去除模块,或者减少iptables的放行规则,将无法通信。
nf_conntrack_ftp 模块粗读
今天太晚了,先不写了。
include/uapi/linux/netfilter/nf_conntrack_ftp.h
这个文件主要是定义了FTP几种模式:
/* This enum is exposed to userspace */
enum nf_ct_ftp_type {
/* PORT command from client */
NF_CT_FTP_PORT,
/* PASV response from server */
NF_CT_FTP_PASV,
/* EPRT command from client */
NF_CT_FTP_EPRT,
/* EPSV response from server */
NF_CT_FTP_EPSV,
};
上面两个是ipv4下面的主动和被动模式,下面两个则是ipv6下面的主动和被动模式
include/linux/netfilter/nf_conntrack_ftp.h
#define FTP_PORT 21
#define NF_CT_FTP_SEQ_PICKUP (1 << 0)
#define NUM_SEQ_TO_REMEMBER 2
/* This structure exists only once per master */
struct nf_ct_ftp_master {
/* Valid seq positions for cmd matching after newline */
u_int32_t seq_aft_nl[IP_CT_DIR_MAX][NUM_SEQ_TO_REMEMBER];
/* 0 means seq_match_aft_nl not set */
u_int16_t seq_aft_nl_num[IP_CT_DIR_MAX];
/* pickup sequence tracking, useful for conntrackd */
u_int16_t flags[IP_CT_DIR_MAX];
};
没太懂这里的,但是能看出来时用来存储序列号的,就是conntrack时,要校验并更新数据流的序列号。如果序列号对不上,并不会放行这个包。
net/netfilter/nf_conntrack_ftp.c
#define MAX_PORTS 8
static u_int16_t ports[MAX_PORTS];
static unsigned int ports_c;
module_param_array(ports, ushort, &ports_c, 0400);
static bool loose;
module_param(loose, bool, 0600);
首先限制了最大监听的端口数量,不超过8个ftp服务器的端口。
module_param 这个貌似是可以在加载模块是传递参数,0400这个表明权限,但是不太懂这个loose变量到底是什么啥意思,修复的那个bug代表啥含义。
static int try_rfc959(const char *, size_t, struct nf_conntrack_man *,
char, unsigned int *);
static int try_rfc1123(const char *, size_t, struct nf_conntrack_man *,
char, unsigned int *);
static int try_eprt(const char *, size_t, struct nf_conntrack_man *,
char, unsigned int *);
static int try_epsv_response(const char *, size_t, struct nf_conntrack_man *,
char, unsigned int *);
static struct ftp_search {
const char *pattern;
size_t plen;
char skip;
char term;
enum nf_ct_ftp_type ftptype;
int (*getnum)(const char *, size_t, struct nf_conntrack_man *, char, unsigned int *);
} search[IP_CT_DIR_MAX][2];
这四个函数,就是尝试解析 ftp 数据包的,分别以那四种情况,去尝试解析出 动态端口。
然后含有个search结构体数据,里面包含了四种情况对应的解析方法,getnum指针就指向这几个函数。
/* Return 1 for match, 0 for accept, -1 for partial. */
static int find_pattern(const char *data, size_t dlen,
const char *pattern, size_t plen,
char skip, char term,
unsigned int *numoff,
unsigned int *numlen,
struct nf_conntrack_man *cmd,
int (*getnum)(const char *, size_t,
struct nf_conntrack_man *, char,