带你手把手解读rejail沙盒源码(0.9.72版本) (七) fnetfilter

发布时间:2023年12月20日
├── fnetfilter
│   ├── Makefile
│   └── main.c

main.c


#include "../include/common.h"

#define MAXBUF 4098
#define MAXARGS 16
static char *args[MAXARGS] = {0};
static int argcnt = 0;
int arg_quiet = 0;


static char *default_filter =
"*filter\n"
":INPUT DROP [0:0]\n"
":FORWARD DROP [0:0]\n"
":OUTPUT ACCEPT [0:0]\n"
"-A INPUT -i lo -j ACCEPT\n"
"-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
"# echo replay is handled by -m state RELATED/ESTABLISHED above\n"
"#-A INPUT -p icmp --icmp-type echo-reply -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type echo-request -j ACCEPT \n"
"# disable STUN\n"
"-A OUTPUT -p udp --dport 3478 -j DROP\n"
"-A OUTPUT -p udp --dport 3479 -j DROP\n"
"-A OUTPUT -p tcp --dport 3478 -j DROP\n"
"-A OUTPUT -p tcp --dport 3479 -j DROP\n"
"COMMIT\n";

static void usage(void) {
	printf("Usage:\n");
	printf("\tfnetfilter netfilter-command destination-file\n");
}

static void err_exit_cannot_open_file(const char *fname) {
	fprintf(stderr, "Error fnetfilter: cannot open %s\n", fname);
	exit(1);
}


static void copy(const char *src, const char *dest) {
	FILE *fp1 = fopen(src, "r");
	if (!fp1)
		err_exit_cannot_open_file(src);

	FILE *fp2 = fopen(dest, "w");
	if (!fp2)
		err_exit_cannot_open_file(dest);

	char buf[MAXBUF];
	while (fgets(buf, MAXBUF, fp1))
		fprintf(fp2, "%s", buf);

	fclose(fp1);
	fclose(fp2);
}

static void process_template(char *src, const char *dest) {
	char *arg_start = strchr(src, ',');
	assert(arg_start);
	*arg_start = '\0';
	arg_start++;
	if (*arg_start == '\0') {
		fprintf(stderr, "Error fnetfilter: you need to provide at least one argument\n");
		exit(1);
	}

	// extract the arguments from command line
	char *token = strtok(arg_start, ",");
	while (token) {
		if (argcnt == MAXARGS) {
			fprintf(stderr, "Error fnetfilter: only up to %u arguments are supported\n", (unsigned) MAXARGS);
			exit(1);
		}
		// look for abnormal things
		int len = strlen(token);
		if (strcspn(token, "\\&!?\"'<>%^(){};,*[]") != (size_t)len) {
			fprintf(stderr, "Error fnetfilter: invalid argument in netfilter command\n");
			exit(1);
		}
		args[argcnt] = token;
		argcnt++;
		token = strtok(NULL, ",");
	}
#if 0
{
printf("argcnt %d\n", argcnt);
int i;
for (i = 0; i < argcnt; i++)
	printf("%s\n", args[i]);
}
#endif

	// open the files
	FILE *fp1 = fopen(src, "r");
	if (!fp1)
		err_exit_cannot_open_file(src);

	FILE *fp2 = fopen(dest, "w");
	if (!fp2)
		err_exit_cannot_open_file(dest);

	int line = 0;
	char buf[MAXBUF];
	while (fgets(buf, MAXBUF, fp1)) {
		line++;
		char *ptr = buf;
		while (*ptr != '\0') {
			if (*ptr != '$')
				fputc(*ptr, fp2);
			else {
				// parsing
				int index = 0;
				int rv = sscanf(ptr, "$ARG%d", &index) ;
				if (rv != 1) {
					fprintf(stderr, "Error fnetfilter: invalid template argument on line %d\n", line);
					exit(1);
				}

				// print argument
				if (index < 1 || index > argcnt) {
					fprintf(stderr, "Error fnetfilter: $ARG%d on line %d was not defined\n", index, line);
					exit(1);
				}
				fprintf(fp2, "%s", args[index - 1]);

				// march to the end of argument
				ptr += 4;
				while (isdigit(*ptr))
					ptr++;
				ptr--;
			}
			ptr++;
		}
	}

	fclose(fp1);
	fclose(fp2);
}

int main(int argc, char **argv) {
#if 0
{
system("cat /proc/self/status");
int i;
for (i = 0; i < argc; i++)
	printf("*%s* ", argv[i]);
printf("\n");
}
#endif

	char *quiet = getenv("FIREJAIL_QUIET");
	if (quiet && strcmp(quiet, "yes") == 0)
		arg_quiet = 1;

	if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0)) {
		usage();
		return 0;
	}

	if (argc != 2 && argc != 3) {
		usage();
		return 1;
	}

	warn_dumpable();

	char *destfile = (argc == 3)? argv[2]: argv[1];
	char *command = (argc == 3)? argv[1]: NULL;
//printf("command %s\n", command);
//printf("destfile %s\n", destfile);

	// destfile is a real filename
	reject_meta_chars(destfile, 0);

	// handle default config (command = NULL, destfile)
	if (command == NULL) {
		// create a default filter file
		FILE *fp = fopen(destfile, "w");
		if (!fp)
			err_exit_cannot_open_file(destfile);
		fprintf(fp, "%s\n", default_filter);
		fclose(fp);
	}
	else {
		if (strrchr(command, ','))
			process_template(command, destfile);
		else
			copy(command, destfile);
	}

	return 0;
}

这是一个iptables的防火墙规则脚本,下面是每一行的解释:

  • *filter:这是定义过滤表的开始,iptables有多个表,filter是默认表,主要用于过滤数据包。
  • :INPUT DROP [0:0]:设置默认策略,所有进入(INPUT)的数据包默认丢弃(DROP),除非有规则明确允许。[0:0]是计数器,表示匹配到这条规则的数据包和字节数,初始为0。
  • :FORWARD DROP [0:0]:所有转发(FORWARD)的数据包默认丢弃。
  • :OUTPUT ACCEPT [0:0]:所有出去(OUTPUT)的数据包默认接受(ACCEPT)。
  • -A INPUT -i lo -j ACCEPT:允许来自本地回环(lo)接口的数据包。
  • -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT:允许所有已经建立连接或者相关的数据包。
  • -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT:允许ICMP目的地不可达的数据包。
  • -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT:允许ICMP超时的数据包。
  • -A INPUT -p icmp --icmp-type echo-request -j ACCEPT:允许ICMP回显请求的数据包,即允许ping请求。
  • -A OUTPUT -p udp --dport 3478 -j DROP:禁止向外发送目的端口为3478的UDP数据包。
  • -A OUTPUT -p udp --dport 3479 -j DROP:禁止向外发送目的端口为3479的UDP数据包。
  • -A OUTPUT -p tcp --dport 3478 -j DROP:禁止向外发送目的端口为3478的TCP数据包。
  • -A OUTPUT -p tcp --dport 3479 -j DROP:禁止向外发送目的端口为3479的TCP数据包。
  • COMMIT:提交以上规则,使其生效。

这个脚本的主要作用是默认拒绝所有进入和转发的数据包,但允许所有出去的数据包和已经建立的连接。同时,它还允许ICMP和本地回环接口的数据包,但禁止了向外发送到某些特定端口的数据包。这些特定端口通常用于STUN协议,用于NAT穿透。所以这个脚本可能是为了禁止STUN协议而编写的。

usage

  1. static void usage(void) {:声明一个名为usage的静态函数,它没有参数也没有返回值。
  2. printf("Usage:\n");:在标准输出(通常是终端)上打印字符串"Usage:"和换行符。
  3. printf("\tfnetfilter netfilter-command destination-file\n");:在标准输出上打印制表符、字符串"fnetfilter netfilter-command destination-file"和换行符。这行代码用于显示命令的使用方式。
  4. }:结束usage函数。

err_exit_cannot_open_file

  1. static void err_exit_cannot_open_file(const char *fname) {:声明一个名为err_exit_cannot_open_file的静态函数,它接受一个指向字符常量的指针作为参数,并且没有返回值。
  2. fprintf(stderr, "Error fnetfilter: cannot open %s\n", fname);:使用fprintf函数将错误信息(包含文件名fname)写入标准错误输出(通常是终端)。
  3. exit(1);:调用exit函数,使程序立即终止,返回值为1,通常表示出现了错误。
  4. }:结束err_exit_cannot_open_file函数。

copy

  1. static void copy(const char *src, const char *dest) {:声明一个名为copy的静态函数,它接受两个指向字符常量的指针作为参数,并且没有返回值。
  2. FILE *fp1 = fopen(src, "r");:使用fopen函数打开名为src的文件以读取模式。如果文件无法打开,则返回NULL。
  3. if (!fp1) err_exit_cannot_open_file(src);:检查fp1是否为NULL。如果是,则调用err_exit_cannot_open_file函数并传入src作为参数,该函数会打印错误消息并退出程序。
  4. FILE *fp2 = fopen(dest, "w");:使用fopen函数打开名为dest的文件以写入模式。如果文件无法打开,则返回NULL。
  5. if (!fp2) err_exit_cannot_open_file(dest);:检查fp2是否为NULL。如果是,则调用err_exit_cannot_open_file函数并传入dest作为参数,该函数会打印错误消息并退出程序。
  6. char buf[MAXBUF];:定义一个大小为MAXBUF的字符数组buf,用于存储从源文件中读取的内容。
  7. while (fgets(buf, MAXBUF, fp1)) fprintf(fp2, "%s", buf);:使用fgets函数从fp1(源文件)中读取一行内容到buf数组中,直到到达文件末尾或读取到错误。然后使用fprintf函数将buf中的内容写入fp2(目标文件)。这个循环会一直重复,直到读取完源文件的所有内容。
  8. fclose(fp1);:使用fclose函数关闭fp1文件流。
  9. fclose(fp2);:使用fclose函数关闭fp2文件流。
  10. }:结束copy函数。

process_template

当然,我会尽力解释这个函数中的每一行代码。这个函数是用C语言编写的,主要用于处理模板文件。

static void process_template(char *src, const char *dest) {

这是函数的声明,函数名为 process_template,它接受两个参数,srcdest,分别代表源文件和目标文件的路径。

	char *arg_start = strchr(src, ',');

这行代码在 src 字符串中查找第一个逗号 , 的位置,并将其地址赋值给 arg_start

	assert(arg_start);

这行代码使用 assert 函数来确保 arg_start 不为 NULL。如果 arg_startNULL,则程序会打印错误信息并终止执行。

	*arg_start = '\0';

这行代码将 arg_start 指向的字符替换为结束符 \0,从而将 src 字符串分割为两部分。

	arg_start++;

这行代码将 arg_start 指针向后移动一位,使其指向参数列表的开始位置。

	if (*arg_start == '\0') {
		fprintf(stderr, "Error fnetfilter: you need to provide at least one argument\n");
		exit(1);
	}

这段代码检查是否提供了至少一个参数。如果没有提供任何参数,程序会打印错误信息并退出。

当然,让我们通过一个具体的例子来解释这个过程。

假设我们有一个字符串 src,它的内容是 "template,arg1,arg2,arg3"。这个字符串包含一个模板名 "template" 和三个参数 "arg1", "arg2", "arg3",它们之间用逗号 , 分隔。

当我们执行 char *arg_start = strchr(src, ','); 这行代码时,arg_start 指针会指向 src 中第一个逗号 , 的位置。所以,arg_start 现在指向的是字符串 ",arg1,arg2,arg3"

然后,当我们执行 *arg_start = '\0'; 这行代码时,逗号 , 被替换为了结束符 \0。所以,src 现在变成了两个字符串:"template""arg1,arg2,arg3"

最后,当我们执行 arg_start++; 这行代码时,arg_start 指针向后移动了一位,跳过了刚刚插入的结束符 \0,从而指向了 "arg1,arg2,arg3"

所以,通过这两行代码,我们将 src 字符串分割为了两部分,并且得到了参数列表的开始位置。

	char *token = strtok(arg_start, ",");

这行代码使用 strtok 函数来分割参数列表,分隔符为逗号 ,

	while (token) {

这是一个 while 循环,只要 token 不为 NULL,循环就会继续。

		if (argcnt == MAXARGS) {
			fprintf(stderr, "Error fnetfilter: only up to %u arguments are supported\n", (unsigned) MAXARGS);
			exit(1);
		}

这段代码检查参数的数量是否超过了最大限制 MAXARGS。如果超过了,程序会打印错误信息并退出。

		int len = strlen(token);
		if (strcspn(token, "\\&!?\"'<>%^(){};,*[]") != (size_t)len) {
			fprintf(stderr, "Error fnetfilter: invalid argument in netfilter command\n");
			exit(1);
		}

这段代码检查参数是否包含任何非法字符。如果包含,程序会打印错误信息并退出。

strcspn是C编程语言中的一个函数,用于查找第一个字符串(称为“源字符串”)中不包含第二个字符串(称为“拒绝字符串”)中的任何字符的初始子串的长度。

strcspn函数的语法如下:

size_t strcspn(const char *s1, const char *s2);

其中:

  • s1 是源字符串。
  • s2 是拒绝字符串。
  • 函数返回s1中不包含s2中任何字符的初始子串的长度。

例如,考虑以下代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello, World!";
    char str2[] = "World";

    size_t result = strcspn(str1, str2);

    printf("不含str2中字符的str1的初始子串的长度为: %zu\n", result);

    return 0;
}

在这个例子中,strcspn函数用于查找不包含str2中任何字符的str1的初始子串的长度。程序的输出将是:

不含str2中字符的str1的初始子串的长度为: 6

这是因为str1的前6个字符("Hello, ")不在str2中出现。

		args[argcnt] = token;
		argcnt++;

这两行代码将参数添加到参数数组 args 中,并将参数计数器 argcnt 加一。

		token = strtok(NULL, ",");
	}

这行代码获取下一个参数。while 循环在此结束。

strtok是C编程语言中的一个函数,用于将字符串分割成一个个标记(token)。这个函数在处理分隔符时非常有用,例如逗号、空格或其他特定字符。

在你的代码片段中:

token = strtok(NULL, ",");

这是对strtok函数的第二次或后续调用。第一次调用strtok需要提供要分割的原始字符串和作为分隔符的字符数组。例如:

char str[] = "apple,banana,grape";
token = strtok(str, ",");

在这个例子中,str是要分割的原始字符串,,是分隔符。首次调用strtok后,它会返回第一个标记(在这种情况下是"apple"),并将内部状态信息保存下来,以便后续调用。

后续的strtok调用应该传入NULL作为第一个参数,表示要继续处理上次调用留下的剩余字符串。第二个参数仍然是分隔符字符数组。所以:

token = strtok(NULL, ",");

这行代码表示从上次分割的位置开始,继续查找以逗号分隔的下一个标记。这次调用可能会返回"banana",然后再下一次调用会返回"grape",直到没有更多的标记可以找到为止。

	FILE *fp1 = fopen(src, "r");
	if (!fp1)
		err_exit_cannot_open_file(src);

这两行代码尝试打开源文件。如果无法打开,程序会打印错误信息并退出。

	FILE *fp2 = fopen(dest, "w");
	if (!fp2)
		err_exit_cannot_open_file(dest);

这两行代码尝试打开目标文件。如果无法打开,程序会打印错误信息并退出。

	int line = 0;
	char buf[MAXBUF];
	while (fgets(buf, MAXBUF, fp1)) {

这是一个 while 循环,用于逐行读取源文件。

		line++;

这行代码将行号加一。

		char *ptr = buf;
		while (*ptr != '\0') {

这是一个 while 循环,用于处理当前行的每一个字符。

			if (*ptr != '$')
				fputc(*ptr, fp2);

这两行代码检查当前字符是否为 $。如果不是,就将其写入到目标文件中。

			else {
				int index = 0;
				int rv = sscanf(ptr, "$ARG%d", &index) ;
				if (rv != 1) {
					fprintf(stderr, "Error fnetfilter: invalid template argument on line %d\n", line);
					exit(1);
				}

这段代码尝试解析 $ARG 标记。如果解析失败,程序会打印错误信息并退出。

这段代码的作用是在处理模板文件时解析和提取 $ARGn 形式的参数占位符。

  1. 初始化 index 变量为0,这个变量将用于存储提取的参数索引值。
  2. 使用 sscanf() 函数尝试从 ptr 指针指向的字符串中解析格式为 $ARG%d 的参数。&index 是一个输出参数,将存储解析出的整数值。
  3. sscanf() 函数返回实际成功赋值的参数数量。在这里,我们期望它返回1,表示成功解析了一个整数。
  4. 如果 rv 不等于1,说明没有成功解析出一个整数或者解析了多个整数,这与我们期望的 $ARGn 格式不符。在这种情况下,程序通过 fprintf() 函数向标准错误输出打印一条错误消息,指出在哪一行出现了无效的模板参数,并使用 exit(1) 函数终止程序执行,表示出现了错误。

这段代码的主要目的是确保模板文件中的参数占位符 $ARGn 符合预期的格式,以便后续正确替换为实际的参数值。

				if (index < 1 || index > argcnt) {
					fprintf(stderr, "Error fnetfilter: $ARG%d on line %d was not defined\n", index, line);
					exit(1);
				}

这段代码检查 $ARG 标记的索引是否有效。如果无效,程序会打印错误信息并退出。

				fprintf(fp2, "%s", args[index - 1]);

这行代码将 $ARG 标记替换为相应的参数,并写入到目标文件中。

				ptr += 4;
				while (isdigit(*ptr))
					ptr++;
				ptr--;
			}
			ptr++;
		}
	}

这段代码将指针移动到当前行的下一个字符。

	fclose(fp1);
	fclose(fp2);
}

这两行代码关闭源文件和目标文件,函数在此结束。希望这个解释对你有所帮助!

main函数

这段代码是用C语言编写的,主要功能是在给定命令和目标文件的情况下,根据特定条件处理它们。以下是逐行解释:

  1. int main(int argc, char **argv):这是程序的主入口点,参数argc表示命令行参数的数量,argv是一个包含所有参数的字符指针数组。

  2. #if 0:这是一个预处理器指令,用于注释掉下面的代码块(在}之前)。

3-4. system("cat /proc/self/status");:运行系统命令cat /proc/self/status以显示进程状态信息。

5-6. int i; for (i = 0; i < argc; i++):声明一个整数变量i并使用它遍历argc中的每个参数。

  1. printf("*%s* ", argv[i]);:打印出每个参数,将其括在星号(*)之间,并在末尾添加空格。

  2. printf("\n");:换行。

  3. #endif:结束预处理器指令。

  4. char *quiet = getenv("FIREJAIL_QUIET");:从环境变量中获取FIREJAIL_QUIET的值,并将其赋给字符指针quiet

  5. if (quiet && strcmp(quiet, "yes") == 0) arg_quiet = 1;:如果quiet不为空且等于字符串"yes",则将arg_quiet设置为1。

12-14. 检查命令行参数是否包含帮助选项(如-h、–help或-?),如果是,则输出帮助信息并返回0。

15-16. 如果参数数量不是2或3,则输出错误消息并返回1。

  1. 调用warn_dumpable()函数。
void warn_dumpable(void) {
	if (getuid() != 0 && prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getenv("FIREJAIL_PLUGIN")) {
		fprintf(stderr, "Error: dumpable process\n");

		// best effort to provide detailed debug information
		// cannot use process name, it is just a file descriptor number
		char path[BUFLEN];
		ssize_t len = readlink("/proc/self/exe", path, BUFLEN - 1);
		if (len < 0)
			return;
		path[len] = '\0';
		// path can refer to a sandbox mount namespace, use basename only
		const char *base = gnu_basename(path);

		struct stat s;
		if (stat("/proc/self/exe", &s) == 0 && s.st_uid != 0)
			fprintf(stderr, "Change owner of %s executable to root\n", base);
		else if (access("/proc/self/exe", R_OK) == 0)
			fprintf(stderr, "Remove read permission on %s executable\n", base);
	}
}

此文件来自 src\lib\common.c

这段代码是一个C语言函数,名为warn_dumpable,其功能是检查当前进程是否可被核心转储(dumpable),并根据需要提供调试信息和警告。

函数执行以下操作:

  1. 使用getuid函数检查当前进程的用户ID是否为0(即root用户)。如果不是,则继续后续检查。
  2. 调用prctl函数,参数为PR_GET_DUMPABLE,用于获取当前进程的dumpable状态。如果返回值为1,表示进程是可dumpable的。
  3. 检查环境变量FIREJAIL_PLUGIN是否存在。如果存在,则继续后续警告和调试信息输出。
  4. 输出错误信息到标准错误流(stderr):“Error: dumpable process”。
  5. 努力提供详细的调试信息:
    a. 使用readlink函数从/proc/self/exe读取当前进程的可执行文件路径,并将其存储在path缓冲区中。
    b. 如果读取失败,则返回。
    c. 使用GNU的basename函数从path中提取出基名(不含路径的部分),并存储在base变量中。
  6. 使用stat函数获取/proc/self/exe的文件状态信息,并检查其所有者用户ID是否为0(即root用户)。如果不是,则输出警告信息,建议将可执行文件的所有者改为root用户。
  7. 如果上述stat函数调用失败或所有者已经是root用户,使用access函数检查当前用户是否有读取/proc/self/exe的权限。如果有,则输出警告信息,建议移除对可执行文件的读取权限。

总的来说,这个函数主要用于在非root用户运行的进程中检测dumpable状态,并在必要时提供警告和调试信息,以增强系统的安全性。

======================================================================================

"是否可被核心转储"指的是一个进程或者系统在遇到严重错误或崩溃时,是否允许操作系统生成核心转储文件。核心转储文件是一个包含进程在崩溃时刻内存状态的详细快照,包括程序数据、堆栈信息、打开的文件描述符、内存映射等。如果一个进程是"可被核心转储"的,那么当它因为某种原因异常终止时,操作系统会自动或根据配置生成一个核心转储文件。这个文件对于调试和分析程序崩溃的原因非常有用,因为它提供了崩溃瞬间程序内部状态的详细视图。

相反,如果一个进程不可被核心转储,那么在它崩溃时,操作系统不会生成核心转储文件。这可能是由于安全考虑、资源限制或者是为了防止敏感信息泄露。

在Linux和其他类Unix系统中,可以通过prctl()系统调用的PR_SET_DUMPABLEPR_GET_DUMPABLE选项来设置和查询进程的dumpable状态。默认情况下,新创建的进程通常是可被核心转储的,但这个行为可以根据需要进行修改。

  1. 根据参数数量分配destfilecommand变量。

19-20. 禁止在目标文件名中使用元字符。

void reject_meta_chars(const char *fname, int globbing) {
	assert(fname);

	reject_cntrl_chars(fname);

	const char *reject = "\\&!?\"<>%^{};,*[]";
	if (globbing)
		reject = "\\&!\"<>%^{};,"; // file globbing ('*?[]') is allowed

	const char *c = strpbrk(fname, reject);
	if (c) {
		fprintf(stderr, "Error: \"%s\" is an invalid filename: rejected character: \"%c\"\n", fname, *c);
		exit(1);
	}
}

此文件来自 src\lib\common.c

21-23. 如果没有提供命令(即command == NULL),则创建一个默认的过滤器文件。

24-26. 如果提供了命令,则根据需要调用process_template()copy()函数来处理命令和目标文件。

  1. 返回0,表示程序执行成功。

样例

程序功能

这个程序的作用是处理和配置Netfilter(Linux中的网络包过滤系统)的规则。它通过以下方式实现这一功能:

  1. 接收命令行参数,包括Netfilter命令和目标文件名。
  2. 如果只提供了目标文件名,程序将创建一个包含默认过滤规则的文件(定义在default_filter变量中)。
  3. 如果提供了Netfilter命令和目标文件名,程序将执行以下操作:
    • 如果命令中包含逗号(,),程序将解析命令作为模板,并使用后续的参数替换模板中的 $ARGn 标记。处理后的规则将写入目标文件。
    • 如果命令中不包含逗号,程序将简单地将命令内容复制到目标文件。

在处理过程中,程序会进行一些错误检查,如确保文件可以打开和读取,检查提供的参数是否有效,以及确保 $ARGn 标记在模板中正确使用。如果发生错误,程序将打印错误消息并退出。

以下是一些不同的输入情况及其结果:

  1. 只提供目标文件名:
fnetfilter /etc/netfilter/rules.conf

在这种情况下,程序将创建一个名为rules.conf的文件,并将默认的Netfilter规则写入该文件。默认规则如下:

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
-A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
-A INPUT -p icmp --icmp-type echo-request -j ACCEPT
-A OUTPUT -p udp --dport 3478 -j DROP
-A OUTPUT -p udp --dport 3479 -j DROP
-A OUTPUT -p tcp --dport 3478 -j DROP
-A OUTPUT -p tcp --dport 3479 -j DROP
COMMIT
  1. 提供Netfilter命令和目标文件名,命令中不包含逗号:
fnetfilter "iptables -A INPUT -p tcp --dport 80 -j ACCEPT" /etc/netfilter/rules.conf

在这种情况下,程序将把"iptables -A INPUT -p tcp --dport 80 -j ACCEPT"这条命令直接复制到rules.conf文件中。

  1. 提供Netfilter命令和目标文件名,命令中包含逗号(模板形式):
fnetfilter "iptables -A INPUT -p $ARG1 --dport $ARG2 -j ACCEPT" ,tcp,80 /etc/netfilter/rules.conf

在这种情况下,程序将解析命令模板并用提供的参数替换 $ARGn 标记。因此,rules.conf 文件将包含以下规则:

iptables -A INPUT -p tcp --dport 80 -j ACCEPT

注意,这里的逗号后紧跟参数列表,参数之间用逗号分隔。在这个例子中,$ARG1 被替换为 tcp$ARG2 被替换为 80

文章来源:https://blog.csdn.net/qq_55125921/article/details/135087647
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。