带你手把手解读firejail沙盒源码(0.9.72版本)(四)fnet()

├── fnet
│   ├── Makefile
│   ├── arp.c
│   ├── fnet.h
│   ├── interface.c
│   ├── main.c
│   └── veth.c

功能概述

根据您提供的信息,fnet文件夹包含五个代码文件:arp.c、interface.c、veth.c、 和 macvlan.c。这些文件共同实现了一个用于管理网络接口的工具集。

  1. arp.c - 这个文件主要负责ARP扫描功能,即通过发送ARP请求包来获取网络中的其他设备的信息(IP地址和MAC地址)。它会创建一个RAW套接字,然后发送一系列的ARP请求包,并监听返回的响应包。

  2. interface.c - 这个文件包含了多个函数,用于管理和配置网络接口。例如,它可以添加虚拟以太网设备到网桥中,将接口设置为“UP”状态,获取和设置MTU大小,打印所有接口的信息,获取和设置MAC地址,以及配置IPv4和IPv6地址等。

  3. veth.c - 这个文件实现了创建虚拟以太网(veth)对的功能。虚拟以太网是一种特殊的网络设备,它有两个端点,每个端点都可以被分配给不同的命名空间,使得这两个命名空间可以通过这个设备进行通信。

  4. macvlan.c - 这个文件实现了创建MACVLAN接口的功能。MACVLAN也是一种允许多个逻辑子接口共享同一个物理网络接口的技术,但是与IPvLAN不同的是,每个子接口有自己独立的MAC地址。

这些文件之间的协同工作主要是通过调用彼此定义的函数来完成的。例如,interface.c可能会调用arp.c中的函数来进行ARP扫描,而veth.c或macvlan.c可能需要调用interface.c中的函数来配置新创建的网络接口。这种模块化的设计使得整个fnet工具集更加灵活和可扩展。

main.c


#include "fnet.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>

int arg_quiet = 0;

void fmessage(char* fmt, ...) { // TODO: this function is duplicated in src/firejail/util.c
	if (arg_quiet)
		return;

	va_list args;
	va_start(args,fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	fflush(0);
}


static void usage(void) {
	printf("Usage:\n");
	printf("\tfnet create veth dev1 dev2 bridge child\n");
	printf("\tfnet create macvlan dev parent child\n");
	printf("\tfnet moveif dev proc\n");
	printf("\tfnet printif\n");
	printf("\tfnet printif scan\n");
	printf("\tfnet config interface dev ip mask mtu\n");
	printf("\tfnet config mac addr\n");
	printf("\tfnet config ipv6 dev ip\n");
	printf("\tfnet ifup dev\n");
  printf("\tfnet waitll dev\n");
}

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
	if (argc < 2) {
		usage();
		return 1;
	}
	if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0) {
		usage();
		return 0;
	}

	warn_dumpable();

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

	if (argc == 3 && strcmp(argv[1], "ifup") == 0) {
		net_if_up(argv[2]);
	}
	else if (argc == 2 && strcmp(argv[1], "printif") == 0) {
		net_ifprint(0);
	}
	else if (argc == 3 && strcmp(argv[1], "printif") == 0 && strcmp(argv[2], "scan") == 0) {
		net_ifprint(1);
	}
	else if (argc == 7 && strcmp(argv[1], "create") == 0 && strcmp(argv[2], "veth") == 0) {
		// create veth pair and move one end in the the namespace
		net_create_veth(argv[3], argv[4], atoi(argv[6]));
		// connect the ohter veth end to the bridge ...
		net_bridge_add_interface(argv[5], argv[3]);
		// ... and bring it  up
		net_if_up(argv[3]);
	}
	else if (argc == 6 && strcmp(argv[1], "create") == 0 && strcmp(argv[2], "macvlan") == 0) {
		// use ipvlan for wireless devices
		// ipvlan driver was introduced in Linux kernel 3.19

		// check kernel version
		struct utsname u;
		int rv = uname(&u);
		if (rv != 0)
			errExit("uname");
		int major;
		int minor;
		if (2 != sscanf(u.release, "%d.%d", &major, &minor)) {
			fprintf(stderr, "Error fnet: cannot extract Linux kernel version: %s\n", u.version);
			exit(1);
		}

		if (major <= 3 && minor < 18)
			net_create_macvlan(argv[3], argv[4], atoi(argv[5]));
		else {
			struct stat s;
			char *fname;
			if (asprintf(&fname, "/sys/class/net/%s/wireless", argv[4]) == -1)
				errExit("asprintf");
			if (stat(fname, &s) == 0) // wireless
				net_create_ipvlan(argv[3], argv[4], atoi(argv[5]));
			else // regular ethernet
				net_create_macvlan(argv[3], argv[4], atoi(argv[5]));
		}
	}
	else if (argc == 7 && strcmp(argv[1], "config") == 0 && strcmp(argv[2], "interface") == 0) {
		char *dev = argv[3];
		uint32_t ip = (uint32_t)  atoll(argv[4]);
		uint32_t mask = (uint32_t)  atoll(argv[5]);
		int mtu = atoi(argv[6]);
		// configure interface
		net_if_ip(dev, ip, mask, mtu);
		// ... and bring it  up
		net_if_up(dev);
	}
	else if (argc == 5 && strcmp(argv[1], "config") == 0 && strcmp(argv[2], "mac") == 0) {
		unsigned char mac[6];
		if (atomac(argv[4], mac)) {
			fprintf(stderr, "Error fnet: invalid mac address %s\n", argv[4]);
		}
		net_if_mac(argv[3], mac);
	}
	else if (argc == 4 && strcmp(argv[1], "moveif") == 0) {
		net_move_interface(argv[2], atoi(argv[3]));
	}
	else if (argc == 5 && strcmp(argv[1], "config") == 0 && strcmp(argv[2], "ipv6") == 0) {
		net_if_ip6(argv[3], argv[4]);
	}
  else if (argc == 3 && strcmp(argv[1], "waitll") == 0) {
    net_if_waitll(argv[2]);
  }
	else {
		fprintf(stderr, "Error fnet: invalid arguments\n");
		return 1;
	}

	return 0;
}

这个C程序是一个用于在Linux中管理网络接口的命令行工具。它支持创建和配置虚拟以太网(veth)对、macvlan接口、IPv6地址,以及在网络命名空间之间移动网络接口。
程序的主要功能是首先检查提供的参数数量是否小于2。如果是,则打印使用信息并返回错误代码。否则,它解析命令行参数来确定要执行的操作。

  • fnet create veth dev1 dev2 bridge child: 创建一个veth对,并将其中一端移动到指定的命名空间
  • fnet create macvlan dev parent child: 在指定的父接口上创建一个macvlan接口
  • fnet moveif dev proc: 将网络接口移动到指定的进程命名空间
  • fnet printif: 打印所有可用网络接口的信息
  • fnet printif scan: 扫描新的网络接口并打印它们的信息
  • fnet config interface dev ip mask mtu: 为指定的网络接口配置IP地址、子网掩码和MTU
  • fnet config mac addr: 设置指定网络接口的MAC地址
  • fnet config ipv6 dev ip: 为指定的网络接口配置IPv6地址
  • fnet ifup dev: 启动指定的网络接口
  • fnet waitll dev: 等待直到指定网络接口被分配了一个链路本地地址

程序还具有一些可以通过设置环境变量或编译特定标志启用的附加功能。例如,当使用-DDEBUG标志编译时,它可以将调试消息记录到标准输出;或者通过将FIREJAIL_QUIET环境变量设置为"yes"来启用安静模式。

veth.c

这段代码的功能是创建和移动网络接口设备。它提供了四个函数:

  1. net_create_veth(const char *dev, const char *nsdev, unsigned pid):创建一个veth对(虚拟以太网对),并将其中一个接口放入给定的命名空间中,由pid标识。

  2. net_create_macvlan(const char *dev, const char *parent, unsigned pid):创建一个macvlan接口,并将其放入给定的命名空间中,由pid标识。

  3. net_create_ipvlan(const char *dev, const char *parent, unsigned pid):创建一个ipvlan接口,并将其放入给定的命名空间中,由pid标识。

  4. net_move_interface(const char *dev, unsigned pid):将指定的网络接口移动到由pid标识的命名空间中。

每个函数首先打开并初始化一个与内核进行通信的Netlink套接字,然后构建一个包含所需信息的Netlink消息,并将其发送到内核。如果操作成功,函数返回0;否则,它会打印错误消息并退出程序。

这四个函数是如何协同工作的

这四个函数都是用来管理网络接口设备的,但它们各有不同的作用。在一些场景下,这些函数可以协同工作来创建和配置复杂的网络环境。

例如,假设你想要在一个特定的命名空间中(由一个特定的进程ID标识)创建一个macvlan接口,并将其连接到一个已存在的物理网络接口上。你可以按以下步骤进行操作:

  1. 调用net_create_macvlan()函数,传入新接口的名字、父接口的名字以及目标命名空间的进程ID。这个函数会创建一个新的macvlan接口,并将其放入指定的命名空间中。

  2. 如果需要移动其他的网络接口设备进入相同的命名空间,可以调用net_move_interface()函数,传入要移动的接口名字以及目标命名空间的进程ID。

这种情况下,net_create_macvlan()net_move_interface()两个函数就协同完成了在网络命名空间中创建和配置接口的任务。

类似地,如果你需要创建一个veth对并将其一端放在一个特定的命名空间中,可以先调用net_create_veth()函数,然后再使用net_move_interface()将其中一端移动到目标命名空间。

至于net_create_ipvlan()函数,它的工作方式与net_create_macvlan()相似,只是创建的是ipvlan接口而非macvlan接口。具体如何与其他函数配合使用,取决于你的网络需求。





#include "fnet.h"
#include "../include/libnetlink.h"
#include <linux/veth.h>
#include <net/if.h>

// Debian Jessie and distributions before that don't have support for IPVLAN
// in /usr/include/linux/if_link.h. We only need a definition for IPVLAN_MODE_L2.
// The kernel version detection happens at run time.
#ifndef IFLA_IPVLAN_MAX
#define IPVLAN_MODE_L2 0
#endif

struct iplink_req
{
	struct nlmsghdr         n;
	struct ifinfomsg        i;
	char                    buf[1024];
};

static struct rtnl_handle rth = { .fd = -1 };

int net_create_veth(const char *dev, const char *nsdev, unsigned pid) {
	int len;
	struct iplink_req req;

	assert(dev);
	assert(nsdev);
	assert(pid);

	if (rtnl_open(&rth, 0) < 0) {
		fprintf(stderr, "cannot open netlink\n");
		exit(1);
	}

	memset(&req, 0, sizeof(req));

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
	req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL;
	req.n.nlmsg_type = RTM_NEWLINK;
	req.i.ifi_family = 0;

	if (dev) {
		len = strlen(dev) + 1;
		addattr_l(&req.n, sizeof(req), IFLA_IFNAME, dev, len);
	}

	struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
	addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
	addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "veth", strlen("veth"));

	struct rtattr * data = NLMSG_TAIL(&req.n);
	addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);

	struct rtattr * peerdata = NLMSG_TAIL(&req.n);
	addattr_l (&req.n, sizeof(req), VETH_INFO_PEER, NULL, 0);
	req.n.nlmsg_len += sizeof(struct ifinfomsg);

	// place the link in the child namespace
	addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);

	if (nsdev) {
		int len = strlen(nsdev) + 1;
		addattr_l(&req.n, sizeof(req), IFLA_IFNAME, nsdev, len);
	}
	peerdata->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)peerdata;

	data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
	linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;

	// send message
	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
		exit(2);

	rtnl_close(&rth);

	return 0;
}


int net_create_macvlan(const char *dev, const char *parent, unsigned pid) {
	int len;
	struct iplink_req req;
	assert(dev);
	assert(parent);

	if (rtnl_open(&rth, 0) < 0) {
		fprintf(stderr, "cannot open netlink\n");
		exit(1);
	}

	memset(&req, 0, sizeof(req));

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
	req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL;
	req.n.nlmsg_type = RTM_NEWLINK;
	req.i.ifi_family = 0;

	// find parent ifindex
	int parent_ifindex = if_nametoindex(parent);
	if (parent_ifindex <= 0) {
		fprintf(stderr, "Error: cannot find network device %s\n", parent);
		exit(1);
	}

	// add parent
	addattr_l(&req.n, sizeof(req), IFLA_LINK, &parent_ifindex, 4);

	// add new interface name
	len = strlen(dev) + 1;
	addattr_l(&req.n, sizeof(req), IFLA_IFNAME, dev, len);

	// place the interface in child namespace
	addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);


	// add  link info for the new interface
	struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
	addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
	addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "macvlan", strlen("macvlan"));

	// set macvlan bridge mode
	struct rtattr * data = NLMSG_TAIL(&req.n);
	addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);
	int macvlan_type = MACVLAN_MODE_BRIDGE;
	addattr_l (&req.n, sizeof(req), IFLA_INFO_KIND, &macvlan_type, 4);

	data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
	linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;

	// send message
	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
		exit(2);

	rtnl_close(&rth);

	return 0;
}

int net_create_ipvlan(const char *dev, const char *parent, unsigned pid) {
	int len;
	struct iplink_req req;
	assert(dev);
	assert(parent);

	if (rtnl_open(&rth, 0) < 0) {
		fprintf(stderr, "cannot open netlink\n");
		exit(1);
	}

	memset(&req, 0, sizeof(req));

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
	req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL;
	req.n.nlmsg_type = RTM_NEWLINK;
	req.i.ifi_family = 0;

	// find parent ifindex
	int parent_ifindex = if_nametoindex(parent);
	if (parent_ifindex <= 0) {
		fprintf(stderr, "Error: cannot find network device %s\n", parent);
		exit(1);
	}

	// add parent
	addattr_l(&req.n, sizeof(req), IFLA_LINK, &parent_ifindex, 4);

	// add new interface name
	len = strlen(dev) + 1;
	addattr_l(&req.n, sizeof(req), IFLA_IFNAME, dev, len);

	// place the interface in child namespace
	addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);


	// add  link info for the new interface
	struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
	addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
	addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "ipvlan", strlen("ipvlan"));

	// set macvlan bridge mode
	struct rtattr * data = NLMSG_TAIL(&req.n);
	addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);
	int macvlan_type = IPVLAN_MODE_L2;
	addattr_l (&req.n, sizeof(req), IFLA_INFO_KIND, &macvlan_type, 2);

	data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
//	req.n.nlmsg_len += sizeof(struct ifinfomsg);

	data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
	linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;

	// send message
	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
		exit(2);

	rtnl_close(&rth);

	return 0;
}

// move the interface dev in namespace of program pid
// when the interface is moved, netlink does not preserve interface configuration
int net_move_interface(const char *dev, unsigned pid) {
	struct iplink_req req;
	assert(dev);

	if (rtnl_open(&rth, 0) < 0) {
		fprintf(stderr, "cannot open netlink\n");
		exit(1);
	}

	memset(&req, 0, sizeof(req));

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
	req.n.nlmsg_flags = NLM_F_REQUEST;
	req.n.nlmsg_type = RTM_NEWLINK;
	req.i.ifi_family = 0;

	// find ifindex
	int ifindex = if_nametoindex(dev);
	if (ifindex <= 0) {
		fprintf(stderr, "Error: cannot find interface %s\n", dev);
		exit(1);
	}
	req.i.ifi_index = ifindex;

	// place the interface in child namespace
	addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);

	// send message
	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
		exit(2);

	rtnl_close(&rth);

	return 0;
}

/*
int main(int argc, char **argv) {
	printf("Hello\n");


	char *dev = argv[3];
	char *nsdev = argv[8];
	unsigned pid;
	sscanf(argv[10], "%u", &pid);


	net_create_veth(dev, nsdev, pid);

	return 0;
}
*/

net_create_veth

static struct rtnl_handle rth = { .fd = -1 }; 初始化了一个结构体

libnetlink.h 中的 rtnl_handle 结构体

struct rtnl_handle
{
	int			fd;
	struct sockaddr_nl	local;
	struct sockaddr_nl	peer;
	__u32			seq;
	__u32			dump;
};



这种初始化方法称为结构体成员指定初始化,它是C语言中的一种语法特性。它允许我们在声明结构体变量的同时为其成员进行初始化。 在上面的代码中,我们声明了一个静态的struct rtnl_handle变量rth,并使用大括号{}来指定其初始值。在这个大括号中,我们可以为每个成员指定一个初始值,例如:

cstatic struct rtnl_handle rth = {
    .fd = -1,
    .local = { /* 初始化本地套接字地址 */ },
    .peer = { /* 初始化远程套接字地址 */ },
    .seq = 0, /* 初始化序列号 */
    .dump = 0  /* 初始化dump标志 */
};

在这种情况下,我们可以为所有成员指定初始值。但是,如果只想为部分成员指定初始值,可以使用点操作符(.)来指定要初始化的成员,就像这样:

cstatic struct rtnl_handle rth = {
    .fd = -1
};

这将只初始化rth.fd成员,其他成员将被默认初始化为0或空指针。

这段代码是一个 C 语言函数,用于创建一对虚拟以太网设备(veth)接口。以下是每一行代码的解释:

  1. 定义 net_create_veth 函数,接受三个参数:本地端 veth 接口名称 dev、远程端 veth 接口名称 nsdev 和目标进程命名空间的 PID。
  2. 定义一个整型变量 len,用于存储字符串长度。
  3. 定义一个 iplink_req 结构体变量 req,用于封装 Netlink 消息中的各种属性。

这是一个用于设置或获取网络接口信息的结构体,它被用来和Linux内核进行通信。这个结构体主要包含了三个部分:

  1. struct nlmsghdr n:这是Netlink消息头,用于标识消息类型、长度、源进程ID等信息。在使用Netlink socket与内核通信时,所有的数据都必须封装在这个消息头里。

  2. struct ifinfomsg i:这是接口信息消息,包含了一系列关于网络接口的信息,如接口索引(ifindex)、接口类型(ifi_type)、MTU(maximum transmission unit)等。

  3. char buf[1024]:这是一个可变长的数据缓冲区,用于存储额外的接口属性信息。因为接口可能有很多属性(如IP地址、MAC地址、广播地址等),而这些属性不能全部放在struct ifinfomsg中,所以需要一个额外的缓冲区来存储这些属性。

总结一下,struct iplink_req是一个用于与Linux内核交换网络接口信息的结构体,它包括了一个Netlink消息头、一个基本的接口信息结构以及一个用于存储额外接口属性的缓冲区。

  1. 使用 assert 函数检查 devnsdevpid 是否非空。
  2. 如果无法打开 Netlink 套接字,则打印错误信息并退出程序。

extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions);
这个函数是Linux中的rtnetlink库的一部分,它提供了一种用户空间程序与内核网络堆栈通信的机制。
rtnl_open函数通过使用rtnetlink协议打开一个与内核进行通信的套接字。它接受两个参数:

  • rth: 指向rtnl_handle结构实例的指针,该结构包含有关套接字和其他与rtnetlink连接相关状态的信息。
  • subscriptions: 表示程序感兴趣的rtnetlink消息类型的位掩码。

如果成功,函数返回0,并在rtnl_handle结构中设置套接字的文件描述符。否则,它返回一个负错误代码。

0: 将一个位掩码(表示不订阅任何类型的rtnetlink消息)传递给rtnl_open函数。

请注意,通常将此函数与其他rtnetlink库中的函数一起使用,例如例如rtnl_closertnl_talk`等。

这个套接字是由rtnl_open函数创建的。在调用该函数时,它会打开一个与内核进行通信的套接字,并将文件描述符返回给用户空间程序。
通常情况下,调用rtnl_open之前需要先初始化rtnl_handle结构实例,并且在完成与内核的通信之后,需要关闭套接字和释放资源。因此,通常将rtnl_open与其他rtnetlink库中的函数一起使用,例如rtnl_closertnl_talk等。

  1. 使用 memset 函数将 req 结构体清零。
  2. 设置 Netlink 消息头部的长度和标志位,类型为 RTM_NEWLINK,表示请求创建新的网络接口。
  3. 设置接口信息结构体 ifinfomsg 的家族为 0。

如果将其成员变量ifi_family设置为0,那么内核会返回所有类型的接口信息,包括IPv4和IPv6接口。

  1. 如果给定了 dev 参数,则计算其长度,并使用 addattr_l 函数添加到消息中。

addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
这个代码片段是调用addattr_l()函数向一个Netlink消息结构体中添加一个属性。具体来说,它将以下参数传递给addattr_l()函数:

  • &req.n: 是要添加属性的Netlink消息结构体的地址。
  • sizeof(req): 是Netlink消息结构体的大小。
  • IFLA_LINKINFO: 是要添加的属性类型,表示这是一个链接信息(Link Info)属性。
  • NULL: 是指向属性值的指针,在这里设置为NULL表示不添加任何值。
  • 0: 是属性值的长度。

因此,这段代码的作用是在Netlink消息结构体中添加一个空的链接信息属性,用于后续添加其他链接信息属性。
addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "veth", strlen("veth"));
这个代码片段也是调用addattr_l()函数向一个Netlink消息结构体中添加一个属性。具体来说,它将以下参数传递给addattr_l()函数:

  • &req.n: 是要添加属性的Netlink消息结构体的地址。
  • sizeof(req): 是Netlink消息结构体的大小。
  • IFLA_INFO_KIND: 是要添加的属性类型,表示这是一个链接信息子类(Info Kind)属性。
  • "veth": 是指向属性值的指针,在这里是一个字符串常量,表示要添加的链接信息子类类型是"veth"。
  • strlen("veth"): 是属性值的长度。

因此,这段代码的作用是在Netlink消息结构体中添加一个链接信息子类属性,用于指定链接信息类型是"veth"。
通常情况下,这些属性是用来在创建虚拟接口时向内核提供额外的信息,例如接口类型、名称等。

在代码中,NLMSG_TAIL(&req.n) 的作用是获取Netlink消息结构体(struct nlmsghdr)的尾部地址。这个函数返回一个指向当前消息末尾的指针,以便可以在其后面添加更多的属性。

Netlink协议允许在每个消息中包含多个属性(以struct rtattr形式),这些属性用于传递额外的信息。通过调用NLMSG_TAIL(),开发人员可以确保将新的属性添加到消息的正确位置,即紧接在现有的属性列表之后。

例如,在创建网络设备时,可能会为新接口添加名称、链接类型等属性。为了做到这一点,需要首先找到消息的尾部,然后使用addattr_l()函数将新属性添加到该位置。NLMSG_TAIL() 函数简化了这个过程,使得开发者能够更容易地构建和发送带有多个属性的Netlink消息。

总结来说,NLMSG_TAIL(&req.n) 是一个辅助函数,它返回Netlink消息的末尾地址,使得在现有消息上添加更多属性变得更加容易。

  1. 创建一个指向 Netlink 消息尾部的指针 linkinfo,并添加 IFLA_LINKINFO 属性,值为空。
  2. 添加 IFLA_INFO_KIND 属性,值为 “veth”。
  3. 创建一个指向 Netlink 消息尾部的指针 data,并添加 IFLA_INFO_DATA 属性,值为空。
  4. 创建一个指向 Netlink 消息尾部的指针 peerdata,并添加 VETH_INFO_PEER 属性,值为空。
  5. 将 Netlink 消息的长度增加 struct ifinfomsg 的大小。
  6. 如果给定了 pid 参数,则将其添加到消息中,表示将接口放入指定的进程命名空间。
  7. 如果给定了 nsdev 参数,则计算其长度,并使用 addattr_l 函数添加到消息中。
  8. 计算 peerdata 属性的实际长度,并更新其 rta_len 成员。
  9. 计算 data 属性的实际长度,并更新其 rta_len 成员。
  10. 计算 linkinfo 属性的实际长度,并更新其 rta_len 成员。

在这个代码片段中,peerdata->rta_lendata->rta_lenlinkinfo->rta_len这三个变量分别表示三个不同类型的属性的长度。这些属性都是通过调用addattr_l()函数添加到请求结构体中的。
在添加完所有属性之后,需要更新这些属性的长度值,以便内核可以正确地解析这些属性。这是因为addattr_l()函数只是将属性添加到了请求结构体中,并没有设置属性的长度值。
因此,这里通过计算从属性开始位置到请求结构体末尾的距离来得到属性的实际长度,并将其存储到相应的变量中。这样,在发送请求时,内核就可以根据这些长度值正确地解析请求中的每个属性。

  1. 发送 Netlink 消息,如果发送失败则打印错误信息并退出程序。
  2. 关闭 Netlink 套接字。
  3. 返回 0,表示成功。

这个函数通过 Netlink 协议与内核通信,创建了一对 veth 接口,其中一端在当前网络命名空间中,另一端在指定的进程命名空间中。


补充一段其他代码

/*
 *  Display all network interface names
 */
#include <stdio.h>            
#include <string.h>          
#include <unistd.h>           
#include <sys/socket.h>       
#include <arpa/inet.h>        
#include <linux/netlink.h>    
#include <linux/rtnetlink.h> 

#define BUFSIZE 8192

struct nl_req_s {
  struct nlmsghdr hdr;
  struct rtgenmsg gen;
};

void rtnl_print_link(struct nlmsghdr * h)
{
    struct ifinfomsg * iface;
    struct rtattr * attr;
    int len;

    iface = NLMSG_DATA(h);
    len = RTM_PAYLOAD(h);

    /* loop over all attributes for the NEWLINK message */
    for (attr = IFLA_RTA(iface); RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
    {
        switch (attr->rta_type)
        {
        case IFLA_IFNAME:
            printf("Interface %d : %s\n", iface->ifi_index, (char *)RTA_DATA(attr));
            break;
        default:
            break;
        }
    }
}

int main(void)
{
    struct sockaddr_nl src_addr;
    int s, end=0, len;
    struct msghdr msg;
    struct nl_req_s req;
    struct iovec io;
    char buf[BUFSIZE];

    //build src_addr netlink address
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_groups = 0;

    //create a Netlink socket
    if ((s=socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0){
        perror("socket");
        return -1;
    }

    //build netlink message
    memset(&req, 0, sizeof(req));
    req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
    req.hdr.nlmsg_type = RTM_GETLINK;
    req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
    req.hdr.nlmsg_seq = 1;
    req.hdr.nlmsg_pid = getpid();
    req.gen.rtgen_family = AF_INET;

    memset(&io, 0, sizeof(io));
    io.iov_base = &req;
    io.iov_len = req.hdr.nlmsg_len;

    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_name = &src_addr;
    msg.msg_namelen = sizeof(src_addr);

    //send the message
    if (sendmsg(s, &msg, 0) < 0)
    {
        perror("sendmsg");
    }

    //parse reply
    while (!end){
        memset(buf, 0, BUFSIZE);
        msg.msg_iov->iov_base = buf;
        msg.msg_iov->iov_len = BUFSIZE;
        if ((len=recvmsg(s, &msg, 0)) < 0){
            perror("recvmsg");
        }

        for (struct nlmsghdr * msg_ptr = (struct nlmsghdr *)buf;
             NLMSG_OK(msg_ptr, len); msg_ptr = NLMSG_NEXT(msg_ptr, len)){
            switch (msg_ptr->nlmsg_type)
            {
            case NLMSG_DONE:
                end++;
                break;
            case RTM_NEWLINK:
                rtnl_print_link(msg_ptr);
                break;
            default:
                printf("Ignored msg: type=%d, len=%d\n", msg_ptr->nlmsg_type, msg_ptr->nlmsg_len);
                break;
            }
        }
    }

    close(s);
    return 0;
}

这个代码创建netlink套接字的方式与上面的rtnl_open函数有所不同,但它们都是用来建立用户空间程序和内核之间通信的方法。
在这个代码中,首先定义了一个struct sockaddr_nl类型的源地址结构体src_addr,然后调用socket()函数来创建一个raw类型的Netlink socket。接着,填充了发送给内核的消息结构体,并将其作为参数传递给sendmsg()函数来发送消息。
在接收内核回复时,使用recvmsg()函数来接收数据,并解析内核回复中的每个消息头,根据类型调用相应的处理函数(如rtnl_print_link())进行处理。
rtnl_open()函数则是通过打开一个文件描述符来创建一个与内核进行通信的套接字,并将文件描述符返回给用户空间程序。这种方式更简单,不需要手动构建消息和设置源地址等信息,只需要初始化rtnl_handle结构实例并调用rtnl_open()即可。
因此,这两种方式各有优缺点,可以根据实际需求选择合适的方式来创建Netlink套接字。


rtnl_talk()函数和sendmsg()函数都是用于发送网络消息的函数,但它们之间有一些区别。
首先,sendmsg()函数是一个通用的系统调用,可以用于发送各种类型的网络消息。而rtnl_talk()函数是Netlink库提供的一个专用函数,专门用于与内核进行Netlink通信。
其次,sendmsg()函数需要手动构建消息头和数据部分,并设置正确的参数来指定接收方地址、协议等信息。而rtnl_talk()函数则自动处理这些细节,只需要提供要发送的消息内容即可。
此外,sendmsg()函数通常需要在收到响应后手动解析响应消息的内容。而rtnl_talk()函数则会自动解析内核返回的响应消息,并将解析结果以合适的形式返回给用户。
因此,在使用Netlink进行内核通信时,通常建议使用rtnl_talk()函数,因为它更加方便易用,而且能够避免一些常见的错误和陷阱。


代码一和代码二之间的区别如下:

  1. 代码一的目的是创建一个veth对(虚拟以太网对),并在指定的PID对应的网络命名空间中添加其中的一个接口。而代码二是用来显示所有网络接口名称。

  2. 代码一定义了一个名为"net_create_veth"的函数,该函数接受三个参数:dev(本地设备名称)、nsdev(网络命名空间中的设备名称)和pid(要放置网络接口的进程ID)。相比之下,代码二没有类似的函数定义,而是直接在main函数中实现功能。

  3. 在代码一中,使用了rtnl_open、rtnl_talk和rtnl_close等函数来与内核进行通信,以便创建veth对。而在代码二中,通过socket、sendmsg和recvmsg等函数发送和接收Netlink消息以获取网络接口信息。

  4. 代码一中的结构体类型为struct iplink_req,用于构建和发送创建veth对的请求。而在代码二中,使用了struct nl_req_s来构建请求RTM_GETLINK的消息。

  5. 代码一中的net_create_veth函数返回0表示成功。代码二的main函数则不返回任何值,因为它仅用于打印网络接口名称。

  6. 代码一包含了一系列的addattr_l调用,用于向请求中添加各种属性,如设备名称、链接信息等。而在代码二中,只处理接收到的NLMSG_NEWLINK消息,并从中提取接口名称。

  7. 代码一包含了一个assert宏,用于检查传入的参数是否有效。代码二没有使用assert宏。


为什么代码一 这样就能创建veth对,而代码二则没有

代码一和代码二实现的功能不同,导致它们的实现方式也不同。代码一是用来创建veth对的,而代码二是用来获取网络接口名称的。

代码一之所以能够创建veth对,是因为它通过Netlink套接字向内核发送了一个RTM_NEWLINK类型的请求,并在请求中包含了与veth设备相关的属性。内核收到这个请求后,会根据这些属性来创建一个新的veth对,并将其添加到系统的网络设备列表中。

与此相反,代码二并没有涉及任何关于创建网络设备的操作。它主要通过发送一个RTM_GETLINK类型的请求来获取系统中的所有网络接口名称。这个请求只包含了获取链接信息的参数,而不包含任何有关创建新设备的信息。

因此,代码一和代码二的区别在于它们的目标和实现方法。代码一是为了创建新的veth对,而代码二是为了查询已有的网络接口信息。由于这两种操作在Linux网络子系统中是分开的,所以它们需要使用不同的消息类型和属性来进行通信。


net_create_macvlan

这段代码是一个 C 语言函数,用于创建一个 MAC VLAN 接口。以下是每一行代码的解释:

  1. 定义 net_create_macvlan 函数,接受三个参数:新接口名称 dev、父接口名称 parent 和目标进程命名空间的 PID。
  2. 定义一个整型变量 len,用于存储字符串长度。
  3. 定义一个 iplink_req 结构体变量 req,用于封装 Netlink 消息中的各种属性。
  4. 使用 assert 函数检查 devparent 是否非空。
  5. 如果无法打开 Netlink 套接字,则打印错误信息并退出程序。
  6. 使用 memset 函数将 req 结构体清零。
  7. 设置 Netlink 消息头部的长度和标志位,类型为 RTM_NEWLINK,表示请求创建新的网络接口。
  8. 设置接口信息结构体 ifinfomsg 的家族为 0。
  9. 查找父接口的索引(ifindex),如果找不到则打印错误信息并退出程序。
  10. 将父接口的 ifindex 添加到消息中。
  11. 如果给定了 dev 参数,则计算其长度,并使用 addattr_l 函数添加到消息中。
  12. 如果给定了 pid 参数,则将其添加到消息中,表示将接口放入指定的进程命名空间。
  13. 创建一个指向 Netlink 消息尾部的指针 linkinfo,并添加 IFLA_LINKINFO 属性,值为空。
  14. 添加 IFLA_INFO_KIND 属性,值为 “macvlan”。
  15. 创建一个指向 Netlink 消息尾部的指针 data,并添加 IFLA_INFO_DATA 属性,值为空。
  16. 设置 MAC VLAN 模式为桥模式(MACVLAN_MODE_BRIDGE)。
  17. 计算 data 属性的实际长度,并更新其 rta_len 成员。
  18. 计算 linkinfo 属性的实际长度,并更新其 rta_len 成员。
  19. 发送 Netlink 消息,如果发送失败则打印错误信息并退出程序。
  20. 关闭 Netlink 套接字。
  21. 返回 0,表示成功。

这个函数通过 Netlink 协议与内核通信,创建了一个 MAC VLAN 接口,该接口作为指定父接口的一个虚拟子接口。新接口在指定的进程命名空间中创建,并设置为桥模式。

和之前的 net_create_veth 对比


代码一(net_create_veth)和代码三(net_create_macvlan)都是通过Netlink套接字接口来创建网络设备,但它们创建的设备类型不同。代码一是用来创建veth对(虚拟以太网对),而代码三是用来创建macvlan设备。

具体来说,代码一与代码三的区别如下:

  1. 设备类型:

    • 代码一创建的是veth对,它由一对相互连接的虚拟以太网设备组成。
    • 代码三创建的是macvlan设备,它是一个共享物理接口的逻辑接口,每个macvlan接口都有自己的MAC地址。
  2. 请求属性:

    • 在代码一中,需要添加关于veth设备的相关属性,如本地设备名称、网络命名空间中的设备名称以及将设备放置在特定进程ID对应的网络命名空间中的信息。
    • 在代码三中,除了添加新的接口名称和将其放在指定的网络命名空间外,还需要设置macvlan设备的相关属性,如父接口索引和macvlan模式(这里是MACVLAN_MODE_BRIDGE)。
  3. IFLA_INFO_KIND属性值:

    • 代码一中的IFLA_INFO_KIND属性值为"veth",表示这是一个veth设备请求。
    • 代码三中的IFLA_INFO_KIND属性值为"macvlan",表示这是一个macvlan设备请求。
  4. 添加其他数据属性:

    • 代码一不需要添加其他数据属性,因为veth设备的创建只需要基本的设备名称和网络命名空间信息。
    • 代码三需要添加一个额外的数据属性(IFLA_INFO_DATA),用于设置macvlan设备的模式(MACVLAN_MODE_BRIDGE)。

综上所述,虽然代码一和代码三都使用了类似的Netlink消息结构和发送方式,但它们之间最大的区别在于创建的设备类型和所需的属性设置。


net_create_ipvlan

这段代码是用于在Linux内核中创建一个新的IPVLAN网络接口的C语言函数。下面是每一行代码的解释:

  1. int net_create_ipvlan(const char *dev, const char *parent, unsigned pid) {:定义一个函数net_create_ipvlan,它接受三个参数:dev(新接口的名称),parent(父接口的名称),和pid(网络命名空间的进程ID)。
  2. int len; struct iplink_req req;:定义一个整型变量len和一个iplink_req结构体变量req
  3. assert(dev); assert(parent);:断言devparent非空,如果为空则程序终止。
  4. if (rtnl_open(&rth, 0) < 0) { fprintf(stderr, "cannot open netlink\n"); exit(1); }:尝试打开路由套接字,如果失败则打印错误信息并退出程序。
  5. memset(&req, 0, sizeof(req));:将req的内存空间全部初始化为0。
  6. req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));:设置reqnlmsg_len字段为ifinfomsg结构体的大小。
  7. req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL;:设置reqnlmsg_flags字段,表示这是一个创建新链接的请求。
  8. req.n.nlmsg_type = RTM_NEWLINK;:设置reqnlmsg_type字段,表示这是一个新链接的消息类型。
  9. req.i.ifi_family = 0;:设置reqifi_family字段为0,表示这是一个无特定协议的接口。
  10. int parent_ifindex = if_nametoindex(parent);:获取父接口的接口索引。
  11. if (parent_ifindex <= 0) { fprintf(stderr, "Error: cannot find network device %s\n", parent); exit(1); }:如果父接口的接口索引无效,则打印错误信息并退出程序。
  12. addattr_l(&req.n, sizeof(req), IFLA_LINK, &parent_ifindex, 4);:向req添加一个属性,表示父接口的接口索引。
  13. len = strlen(dev) + 1; addattr_l(&req.n, sizeof(req), IFLA_IFNAME, dev, len);:向req添加一个属性,表示新接口的名称。
  14. addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);:向req添加一个属性,表示网络命名空间的进程ID。
  15. struct rtattr *linkinfo = NLMSG_TAIL(&req.n); addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0); addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "ipvlan", strlen("ipvlan"));:向req添加一个属性,表示新接口的类型为IPVLAN。
  16. struct rtattr * data = NLMSG_TAIL(&req.n); addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0); int macvlan_type = IPVLAN_MODE_L2; addattr_l (&req.n, sizeof(req), IFLA_INFO_KIND, &macvlan_type, 2);:向req添加一个属性,表示新接口的模式为L2。
  17. data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data; linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;:设置datalinkinfo的长度。
  18. if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0) exit(2);:发送req,如果失败则退出程序。
  19. rtnl_close(&rth);:关闭路由套接字。
  20. return 0;:函数返回0,表示成功。

这个函数的主要目的是在指定的网络命名空间中创建一个新的IPVLAN网络接口。它首先打开一个路由套接字,然后构造一个请求消息,包含新接口的名称、父接口的接口索引、网络命名空间的进程ID、新接口的类型和模式等信息,然后发送这个请求,最后关闭路由套接字。如果在任何步骤中出现错误,它都会打印错误信息并退出程序。这是一种典型的Linux内核编程模式,通过Netlink套接字与内核通信,实现对网络接口的管理。这个函数的使用需要一定的Linux内核编程知识。希望这个解释对你有所帮助!

和 net_create_veth 的区别

代码一(net_create_veth)和代码四(net_create_ipvlan)都是通过Netlink套接字接口来创建网络设备,但它们创建的设备类型不同。代码一是用来创建veth对(虚拟以太网对),而代码四是用来创建ipvlan设备。

具体来说,代码一与代码四的区别如下:

  1. 设备类型:

    • 代码一创建的是veth对,它由一对相互连接的虚拟以太网设备组成。
    • 代码四创建的是ipvlan设备,它是一个共享物理接口的逻辑接口,每个ipvlan接口都有自己的IP地址。
  2. 请求属性:

    • 在代码一中,需要添加关于veth设备的相关属性,如本地设备名称、网络命名空间中的设备名称以及将设备放置在特定进程ID对应的网络命名空间中的信息。
    • 在代码四中,除了添加新的接口名称和将其放在指定的网络命名空间外,还需要设置ipvlan设备的相关属性,如父接口索引和ipvlan模式(这里是IPVLAN_MODE_L2)。
  3. IFLA_INFO_KIND属性值:

    • 代码一中的IFLA_INFO_KIND属性值为"veth",表示这是一个veth设备请求。
    • 代码四中的IFLA_INFO_KIND属性值为"ipvlan",表示这是一个ipvlan设备请求。
  4. 添加其他数据属性:

    • 代码一不需要添加其他数据属性,因为veth设备的创建只需要基本的设备名称和网络命名空间信息。
    • 代码四需要添加一个额外的数据属性(IFLA_INFO_DATA),用于设置ipvlan设备的模式(IPVLAN_MODE_L2)。

综上所述,虽然代码一和代码四都使用了类似的Netlink消息结构和发送方式,但它们之间最大的区别在于创建的设备类型和所需的属性设置。

net_move_interface

这段代码是用于在Linux内核中移动一个网络接口到另一个网络命名空间的C语言函数。下面是每一行代码的解释:

  1. int net_move_interface(const char *dev, unsigned pid) {:定义一个函数net_move_interface,它接受两个参数:dev(要移动的接口的名称)和pid(目标网络命名空间的进程ID)。
  2. struct iplink_req req; assert(dev);:定义一个iplink_req结构体变量req,并断言dev非空。
  3. if (rtnl_open(&rth, 0) < 0) { fprintf(stderr, "cannot open netlink\n"); exit(1); }:尝试打开路由套接字,如果失败则打印错误信息并退出程序。
  4. memset(&req, 0, sizeof(req));:将req的内存空间全部初始化为0。
  5. req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));:设置reqnlmsg_len字段为ifinfomsg结构体的大小。
  6. req.n.nlmsg_flags = NLM_F_REQUEST;:设置reqnlmsg_flags字段,表示这是一个请求。
  7. req.n.nlmsg_type = RTM_NEWLINK;:设置reqnlmsg_type字段,表示这是一个新链接的消息类型。
  8. req.i.ifi_family = 0;:设置reqifi_family字段为0,表示这是一个无特定协议的接口。
  9. int ifindex = if_nametoindex(dev);:获取要移动的接口的接口索引。
  10. if (ifindex <= 0) { fprintf(stderr, "Error: cannot find interface %s\n", dev); exit(1); }:如果接口的接口索引无效,则打印错误信息并退出程序。
  11. req.i.ifi_index = ifindex;:设置reqifi_index字段为接口的接口索引。
  12. addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);:向req添加一个属性,表示网络命名空间的进程ID。
  13. if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0) exit(2);:发送req,如果失败则退出程序。
  14. rtnl_close(&rth);:关闭路由套接字。
  15. return 0;:函数返回0,表示成功。

这个函数的主要目的是将一个网络接口移动到另一个网络命名空间。它首先打开一个路由套接字,然后构造一个请求消息,包含要移动的接口的接口索引和目标网络命名空间的进程ID等信息,然后发送这个请求,最后关闭路由套接字。如果在任何步骤中出现错误,它都会打印错误信息并退出程序。这是一种典型的Linux内核编程模式,通过Netlink套接字与内核通信,实现对网络接口的管理。这个函数的使用需要一定的Linux内核编程知识。希望这个解释对你有所帮助!

fnet.h


#ifndef FNET_H
#define FNET_H

#include "../include/common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>

// main.c
extern int arg_quiet;
extern void fmessage(char* fmt, ...); // TODO: this function is duplicated in src/firejail/util.c

// veth.c
int net_create_veth(const char *dev, const char *nsdev, unsigned pid);
int net_create_macvlan(const char *dev, const char *parent, unsigned pid);
int net_create_ipvlan(const char *dev, const char *parent, unsigned pid);
int net_move_interface(const char *dev, unsigned pid);

// interface.c
void net_bridge_add_interface(const char *bridge, const char *dev);
void net_if_up(const char *ifname);
int net_get_mtu(const char *ifname);
void net_set_mtu(const char *ifname, int mtu);
void net_ifprint(int scan);
int net_get_mac(const char *ifname, unsigned char mac[6]);
void net_if_ip(const char *ifname, uint32_t ip, uint32_t mask, int mtu);
int net_if_mac(const char *ifname, const unsigned char mac[6]);
void net_if_ip6(const char *ifname, const char *addr6);
void net_if_waitll(const char *ifname);


// arp.c
void arp_scan(const char *dev, uint32_t ifip, uint32_t ifmask);

#endif

代码解释

这是一个C语言的头文件,定义了一些网络相关的函数。其中包括:

  1. net_create_veth:创建一个veth对(虚拟以太网设备对)。
  2. net_create_macvlan:创建一个macvlan接口。
  3. net_create_ipvlan:创建一个ipvlan接口。
  4. net_move_interface:将网络接口移动到指定的网络命名空间。
  5. net_bridge_add_interface:将网络接口添加到网桥中。
  6. net_if_up:启用网络接口。
  7. net_get_mtu:获取网络接口的MTU(最大传输单元)。
  8. net_set_mtu:设置网络接口的MTU。
  9. net_ifprint:打印所有网络接口的信息。
  10. net_get_mac:获取网络接口的MAC地址。
  11. net_if_ip:为网络接口分配IP地址和子网掩码。
  12. net_if_mac:为网络接口分配MAC地址。
  13. net_if_ip6:为网络接口分配IPv6地址。
  14. net_if_waitll:等待网络接口的LL(链路层)状态变为UP。
  15. arp_scan:进行ARP扫描。

此外,还定义了一个全局变量arg_quiet和一个用于输出信息的函数fmessage

arp.c

这段代码实现了一个简单的ARP扫描功能,用于在网络中查找活动的主机。具体来说,它会发送ARP请求到指定网络接口的所有可能IP地址,并收集响应。响应中的信息(包括MAC地址和IP地址)会被打印出来。

以下是一些关键步骤:

  1. 打开一个原始套接字并获取指定网络接口的MAC地址。
  2. 打开一个新的raw socket来处理数据链路层的通信。
  3. 使用循环尝试发送ARP请求到所有可能的IP地址范围内的地址。
  4. 在发送请求的同时,设置接收超时时间以防止无限等待。
  5. 如果收到ARP响应,则检查响应是否包含正确的MAC地址和目标IP地址。如果满足条件,将响应中的MAC地址和源IP地址打印出来。

这个函数可以帮助了解网络上有哪些活跃的设备,但请注意,使用此功能需要root权限,因为它涉及到发送原始数据包和读取网络接口的信息。


#include "fnet.h"
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if_ether.h>			  //TCP/IP Protocol Suite for Linux
#include <net/if.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/if_packet.h>

typedef struct arp_hdr_t {
	uint16_t htype;
	uint16_t ptype;
	uint8_t hlen;
	uint8_t plen;
	uint16_t opcode;
	uint8_t sender_mac[6];
	uint8_t sender_ip[4];
	uint8_t target_mac[6];
	uint8_t target_ip[4];
} ArpHdr;


// scan interface (--scan option)
void arp_scan(const char *dev, uint32_t ifip, uint32_t ifmask) {
	assert(dev);
	assert(ifip);

//	printf("Scanning interface %s (%d.%d.%d.%d/%d)\n",
//		dev, PRINT_IP(ifip & ifmask), mask2bits(ifmask));

	if (strlen(dev) > IFNAMSIZ) {
		fprintf(stderr, "Error: invalid network device name %s\n", dev);
		exit(1);
	}

	// find interface mac address
	int sock;
	if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
		errExit("socket");
	struct ifreq ifr;
	memset(&ifr, 0, sizeof (ifr));
	strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1);
	if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0)
		errExit("ioctl");
	close(sock);
	uint8_t mac[6];
	memcpy (mac, ifr.ifr_hwaddr.sa_data, 6);

	// open layer2 socket
	if ((sock = socket(PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
		errExit("socket");

	// try all possible ip addresses in ascending order
	uint32_t range = ~ifmask + 1; // the number of potential addresses
	// this software is not supported for /31 networks
	if (range < 4) {
		fprintf(stderr, "Warning: this option is not supported for /31 networks\n");
		close(sock);
		return;
	}

	uint32_t dest = (ifip & ifmask) + 1;
	uint32_t last = dest + range - 1;
	uint32_t src = htonl(ifip);

	// wait not more than one second for an answer
	int header_printed = 0;
	uint32_t last_ip = 0;
	struct timeval ts;
	ts.tv_sec = 2; // 2 seconds receive timeout
	ts.tv_usec = 0;

	while (1) {
		fd_set rfds;
		FD_ZERO(&rfds);
		FD_SET(sock, &rfds);
		fd_set wfds;
		FD_ZERO(&wfds);
		FD_SET(sock, &wfds);
		int maxfd = sock;

		uint8_t frame[ETH_FRAME_LEN]; // includes eht header, vlan, and crc
		memset(frame, 0, ETH_FRAME_LEN);

		int nready;
		if (dest < last)
			nready = select(maxfd + 1,  &rfds, &wfds, (fd_set *) 0, NULL);
		else
			nready = select(maxfd + 1,  &rfds,  (fd_set *) 0, (fd_set *) 0, &ts);

		if (nready < 0)
			errExit("select");

		if (nready == 0) { // timeout
			break;
		}

		if (FD_ISSET(sock, &wfds) && dest < last) {
			// configure layer2 socket address information
			struct sockaddr_ll addr;
			memset(&addr, 0, sizeof(addr));
			if ((addr.sll_ifindex = if_nametoindex(dev)) == 0)
				errExit("if_nametoindex");
			addr.sll_family = AF_PACKET;
			memcpy (addr.sll_addr, mac, 6);
			addr.sll_halen = ETH_ALEN;

			// build the arp packet header
			ArpHdr hdr;
			memset(&hdr, 0, sizeof(hdr));
			hdr.htype = htons(1);
			hdr.ptype = htons(ETH_P_IP);
			hdr.hlen = 6;
			hdr.plen = 4;
			hdr.opcode = htons(1); //ARPOP_REQUEST
			memcpy(hdr.sender_mac, mac, 6);
			memcpy(hdr.sender_ip, (uint8_t *)&src, 4);
			uint32_t dst = htonl(dest);
			memcpy(hdr.target_ip, (uint8_t *)&dst, 4);

			// build ethernet frame
			uint8_t frame[ETH_FRAME_LEN]; // includes eht header, vlan, and crc
			memset(frame, 0, sizeof(frame));
			frame[0] = frame[1] = frame[2] = frame[3] = frame[4] = frame[5] = 0xff;
			memcpy(frame + 6, mac, 6);
			frame[12] = ETH_P_ARP / 256;
			frame[13] = ETH_P_ARP % 256;
			memcpy (frame + 14, &hdr, sizeof(hdr));

			// send packet
			if (sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr)) <= 0)
				errExit("send");
			fflush(0);
			dest++;
		}

		if (FD_ISSET(sock, &rfds)) {
			// read the incoming packet
			int len = recvfrom(sock, frame, ETH_FRAME_LEN, 0, NULL, NULL);
			if (len < 0) {
				perror("recvfrom");
			}

			// parse the incoming packet
			if ((unsigned int) len < 14 + sizeof(ArpHdr))
				continue;

			// look only at ARP packets
			if (frame[12] != (ETH_P_ARP / 256) || frame[13] != (ETH_P_ARP % 256))
				continue;

			ArpHdr hdr;
			memcpy(&hdr, frame + 14, sizeof(ArpHdr));

			if (hdr.opcode == htons(2)) {
				// check my mac and my address
				if (memcmp(mac, hdr.target_mac, 6) != 0)
					continue;
				uint32_t ip;
				memcpy(&ip, hdr.target_ip, 4);
				if (ip != src)
					continue;
				memcpy(&ip, hdr.sender_ip, 4);
				ip = ntohl(ip);

				if (ip == last_ip) // filter duplicates
					continue;
				last_ip = ip;

				// printing
				if (header_printed == 0) {
					fmessage("   Network scan:\n");
					header_printed = 1;
				}
				fmessage("   %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n",
					PRINT_MAC(hdr.sender_mac), PRINT_IP(ip));
			}
		}
	}

	close(sock);
}


arp_scan

这段代码是一个名为arp_scan的函数,它用于在指定的网络接口上执行ARP扫描。下面是每行代码的解释:

  • void arp_scan(const char *dev, uint32_t ifip, uint32_t ifmask) {...}:这是函数的定义,它接受三个参数:一个指向设备名称的字符串,一个表示接口IP地址的32位整数,和一个表示接口掩码的32位整数。
  • assert(dev);assert(ifip);:这两行代码使用assert函数来检查devifip是否为非零值。如果任一值为零,程序将终止。
  • if (strlen(dev) > IFNAMSIZ) {...}:这段代码检查设备名称的长度是否超过了IFNAMSIZ(接口名称的最大长度)。如果超过,程序将打印错误消息并退出。
  • int sock; if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) errExit("socket");:这段代码创建一个原始套接字,并将其文件描述符存储在sock变量中。如果创建套接字失败,程序将打印错误消息并退出。
  • struct ifreq ifr; memset(&ifr, 0, sizeof (ifr)); strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1);:这段代码创建一个ifreq结构体,用于存储接口请求。然后,它将结构体的内存设置为零,并将设备名称复制到ifr.ifr_name字段。
  • if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) errExit("ioctl");:这段代码使用ioctl函数来获取接口的硬件地址(MAC地址)。如果ioctl调用失败,程序将打印错误消息并退出。

ioctl() 是一个在 Unix 和类 Unix 系统中广泛使用的系统调用,它允许用户空间的程序向内核发送特定设备的控制命令或获取其状态信息。“ioctl” 是 “input/output control” 的缩写,虽然这个名字暗示了它主要应用于 I/O 设备,但它的用途实际上已经扩展到了其他类型的内核资源。

函数原型如下:

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ... /* void *arg */);

参数说明:

  • fd: 这是文件描述符,通常代表一个打开的设备文件,如终端、磁盘、网络接口等。
  • request: 这是一个请求代码,用来指定要执行的操作。每个设备驱动都会定义一组自己的 ioctl 请求代码,以提供对设备特定功能的访问。
  • ... /* void *arg */: 这是一个可变参数列表,通常指向一个包含附加信息的结构体。这个参数的内容和大小取决于 request 参数指定的具体操作。

ioctl 函数的实现包括以下技术环节:

  1. 返回值:根据命令执行的结果,ioctl 可能返回 0(成功)或 -1(失败)。如果发生错误,可以通过检查 errno 值来确定具体的错误原因。

  2. switch-case 结构:在驱动程序中实现的 ioctl 函数体内,通常有一个 switch-case 结构,每个 case 对应一个命令码,执行相应的操作。

  3. 处理未知命令:当命令不能匹配任何一个设备所支持的命令时,ioctl 函数通常会返回 -EINVAL(非法参数)。

总的来说,ioctl 提供了一种通用的方法,让应用程序能够与内核进行交互,并控制系统设备的行为。由于它是设备驱动级别的调用,所以使用 ioctl 需要深入理解底层硬件和内核的工作原理,否则容易导致不可预期的行为或安全问题。

ioctl() 函数的第三个参数通常是传入传出参数。这意味着它可以用于向内核传递数据,也可以从内核接收数据。

对于某些 ioctl 请求,这个参数可能只包含输入数据,这些数据被内核用来执行请求的操作。例如,你可能会提供一个结构体指针,其中包含了要设置设备配置的值。

对于其他 ioctl 请求,这个参数可能包含了输出数据,这些数据由内核填充并返回给调用者。例如,当你查询设备状态时,内核可能会将结果写入到你提供的结构体中。

在很多情况下,同一个参数同时包含了输入和输出数据。例如,在上面提到的 SIOCGIFADDR 命令的例子中,你提供了一个 struct ifreq 结构体,其中包含了接口名称(输入),而内核会将接口的 IP 地址写入到 ifr_ifru.ifru_addr 成员中(输出)。

因此,你需要根据具体的操作来确定如何使用第三个参数,并确保正确地初始化和检查该参数的内容。

  • close(sock);:这行代码关闭了原始套接字。
  • uint8_t mac[6]; memcpy (mac, ifr.ifr_hwaddr.sa_data, 6);:这段代码创建一个数组来存储MAC地址,并从ifr.ifr_hwaddr.sa_data字段中复制MAC地址。
  • if ((sock = socket(PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) errExit("socket");:这段代码创建一个数据包套接字,并将其文件描述符存储在sock变量中。如果创建套接字失败,程序将打印错误消息并退出。

在这行代码中,使用socket()函数创建了一个套接字,并指定了以下参数:

  • PF_PACKET: 这是一个地址族(Address Family),用于表示数据链路层协议。在Linux系统中,PF_PACKET被用来创建原始套接字,它允许直接访问和操作数据链路层的数据帧。
  • SOCK_RAW: 这是套接字类型(Socket Type),表示这是一个原始套接字。原始套接字允许应用程序直接发送和接收数据包,而不需要经过任何高层协议的处理。这对于实现低级别的网络功能非常有用,比如广播或多播。
  • htons(ETH_P_ALL): 这是指定要使用的协议类型。在这个例子中,htons()是一个将主机字节序转换为网络字节序的函数。ETH_P_ALL是Ethernet协议类型常量,其值为0x0003,表示接收所有以太网协议类型的数据包。

因此,这行代码创建了一个可以处理数据链路层数据帧的原始套接字,即一个二层套接字。这个套接字允许直接访问和操作数据链路层的数据帧,而不是只能处理更高层次的协议,如TCP/IP。

  • uint32_t range = ~ifmask + 1;:这行代码计算可能的地址数量。它通过对接口掩码进行按位取反,然后加1来实现。

    在计算机网络中,IP地址和子网掩码通常以32位二进制数表示。一个IP地址的前缀长度(或称子网掩码)决定了可分配给该子网中的主机的数量。

    例如,对于一个/24的子网(子网掩码为255.255.255.0),其前缀长度为24位,因此有8位可以用来指定子网内的主机。这意味着这个子网最多可以有2^8-2个有效主机地址(减去全0和全1的特殊地址)。

    在这个例子中,代码计算可能的地址数量如下:

    1. ifmask是输入的子网掩码。
    2. ~ifmask是对子网掩码进行按位取反操作。这将所有“1”位变为“0”,所有“0”位变为“1”。结果是一个值,其中包含子网掩码中未设置为主机部分的位数。
    3. ~ifmask + 1将上一步的结果加1。这样就得到了可用于主机的位数。

    最后,range变量存储了可用的地址数量。这个数量包括了子网中的所有有效主机地址,但不包括网络地址和广播地址。

  • if (range < 4) {...}:这段代码检查可能的地址数量是否小于4。如果是,程序将打印警告消息并退出。

  • uint32_t dest = (ifip & ifmask) + 1;:这行代码计算目标IP地址的起始值。它通过将接口IP地址与接口掩码进行按位与运算,然后加1来实现。

  • uint32_t last = dest + range - 1;:这行代码计算目标IP地址的结束值。它通过将起始值加上可能的地址数量,然后减1来实现。

这段代码的作用是计算一个IP地址范围内的目标IP地址的起始值和结束值。这个范围是由接口IP地址、接口掩码和可能的地址数量确定的。

uint32_t dest = (ifip & ifmask) + 1;

在这行代码中,ifip是接口IP地址,ifmask是接口掩码。按位与运算符"&"用于获取接口IP地址中的网络部分。例如,如果接口IP地址是192.168.1.10,接口掩码是255.255.255.0,那么按位与运算的结果就是192.168.1.0。然后在这个结果的基础上加1,得到的就是目标IP地址的起始值。在这个例子中,目标IP地址的起始值就是192.168.1.1。

uint32_t last = dest + range - 1;

在这行代码中,range是可能的地址数量。将起始值加上可能的地址数量,然后减1,就可以得到目标IP地址的结束值。例如,如果起始值是192.168.1.1,可能的地址数量是254(因为网络地址和广播地址不能作为目标地址),那么结束值就是192.168.1.254。

  • uint32_t src = htonl(ifip);:这行代码将接口IP地址转换为网络字节序,并将结果存储在src变量中。
  • int header_printed = 0;uint32_t last_ip = 0;:这两行代码初始化两个变量,分别用于跟踪是否已经打印了头部,以及最后一个接收到的IP地址。
  • struct timeval ts; ts.tv_sec = 2; ts.tv_usec = 0;:这段代码创建一个timeval结构体,并将其秒和微秒字段设置为2和0,表示接收超时时间为2秒。
  • while (1) {...}:这是一个无限循环,用于发送ARP请求并接收ARP响应。在循环中,程序首先创建一个文件描述符集合,并将数据包套接字的文件描述符添加到集合中。然后,它调用select函数来等待套接字上的可读或可写事件。如果select调用返回一个非零值,程序将检查是哪个事件发生了,并相应地处理。如果发生了可写事件,程序将构造并发送一个ARP请求。如果发生了可读事件,程序将读取并解析接收到的数据包。如果数据包是一个ARP响应,并且其目标MAC地址和IP地址与我们发送的ARP请求中的源MAC地址和IP地址匹配,程序将打印出响应的源IP地址和MAC地址。这个过程一直持续到遍历完所有可能的IP地址,或者接收超时。

这部分代码主要负责打印ARP响应的源MAC地址和IP地址,以及关闭套接字。下面是每行代码的解释:

  • if (header_printed == 0) {...}:这段代码检查是否已经打印了头部。如果没有,它将打印一个消息,并将header_printed设置为1,表示头部已经被打印。
  • fmessage(" %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n", PRINT_MAC(hdr.sender_mac), PRINT_IP(ip));:这行代码打印ARP响应的源MAC地址和IP地址。它使用fmessage函数来打印消息,PRINT_MACPRINT_IP宏来格式化MAC地址和IP地址。
  • if (FD_ISSET(sock, &rfds)) {...}:这段代码检查数据包套接字是否在可读文件描述符集合中。如果是,它将读取并解析接收到的数据包。如果数据包是一个ARP响应,并且其目标MAC地址和IP地址与我们发送的ARP请求中的源MAC地址和IP地址匹配,程序将打印出响应的源IP地址和MAC地址。
  • close(sock);:这行代码关闭了数据包套接字。

这段代码是用于ARP扫描网络接口的。在while循环之前的部分,主要完成了以下功能:

  1. 打开socket文件描述符:首先创建一个socket文件描述符(sock),然后使用ioctl系统调用获取指定网络设备(通过dev参数指定)的硬件地址。

  2. 初始化相关变量:初始化目标IP地址范围(destlast)、源IP地址(src)、超时时间(ts)等。

  3. 设置select监听:设置两个文件描述符集(rfdswfds)并将其与当前的socket文件描述符关联。同时设置最大文件描述符值为maxfd

  4. 创建帧缓冲区:定义一个以太网帧缓冲区(frame),大小为ETH_FRAME_LEN字节,并清零。

这个部分的主要目的是为后续的while循环中的ARP扫描操作做好准备工作,包括打开必要的socket、设置好相关的变量以及初始化需要的数据结构。


while 循环

这段代码是一个无限循环,用于发送ARP请求并接收ARP响应。下面是每行代码的解释:

  • while (1) {...}:这是一个无限循环,用于发送ARP请求并接收ARP响应。
  • fd_set rfds; FD_ZERO(&rfds); FD_SET(sock, &rfds);:这段代码创建一个文件描述符集合rfds,并将数据包套接字的文件描述符添加到集合中。
  • fd_set wfds; FD_ZERO(&wfds); FD_SET(sock, &wfds);:这段代码创建一个文件描述符集合wfds,并将数据包套接字的文件描述符添加到集合中。
  • int maxfd = sock;:这行代码将数据包套接字的文件描述符存储在maxfd变量中。
  • uint8_t frame[ETH_FRAME_LEN]; memset(frame, 0, ETH_FRAME_LEN);:这段代码创建一个数组frame来存储以太网帧,并将其内存设置为零。
  • int nready; if (dest < last) nready = select(maxfd + 1, &rfds, &wfds, (fd_set *) 0, NULL); else nready = select(maxfd + 1, &rfds, (fd_set *) 0, (fd_set *) 0, &ts);:这段代码调用select函数来等待套接字上的可读或可写事件。如果目标IP地址小于最后的IP地址,它将同时等待可读和可写事件。否则,它只等待可读事件,并设置一个超时时间。

在这个代码中,last变量表示最后的IP地址,它是通过将子网掩码与网络接口的IP地址进行“与”运算得到的。这个值代表了该子网上最后一个有效的IP地址。

dest变量表示当前的目标IP地址,它是从第一个可分配的IP地址开始,每次递增1来遍历整个子网上的所有IP地址。在循环中,当dest小于last时,会继续发送ARP请求并等待应答;当dest等于或大于last时,说明已经扫描完所有的IP地址,此时只需要等待可能存在的ARP应答即可。

当目标IP地址小于最后的IP地址时,我们需要不断地发送ARP请求并接收ARP应答,因为我们要扫描整个子网中的所有IP地址。在发送一个ARP请求后,我们需要等待一段时间以接收可能的ARP应答。同时,我们也需要准备下一个ARP请求,以便尽快地发送出去。因此,在这个阶段,我们需要同时监听套接字上的可读和可写事件。
而当目标IP地址大于等于最后的IP地址时,我们只需要等待可能存在的ARP应答即可。因为我们已经扫描了所有的IP地址,所以没有必要再发送ARP请求。此时,我们只需要监听套接字上的可读事件,以便接收可能的ARP应答。

  • if (nready < 0) errExit("select");:这行代码检查select调用是否失败。如果失败,程序将打印错误消息并退出。
  • if (nready == 0) { break; }:这行代码检查是否发生了超时。如果是,它将跳出循环。
  • if (FD_ISSET(sock, &wfds) && dest < last) {...}:这段代码检查是否发生了可写事件,并且目标IP地址是否小于最后的IP地址。如果是,它将构造并发送一个ARP请求。

这段代码是在while循环中发送ARP请求的部分。以下是每一行代码的详细解释:

  1. if (FD_ISSET(sock, &wfds) && dest < last):检查文件描述符集合rfds中的sock是否已准备好写入,同时检查当前目标IP地址(dest)是否小于最后一个可能的IP地址(last)。如果满足这两个条件,则继续执行。

  2. struct sockaddr_ll addr; memset(&addr, 0, sizeof(addr));:定义一个结构体sockaddr_ll变量addr并将其内容清零。

  3. if ((addr.sll_ifindex = if_nametoindex(dev)) == 0) errExit("if_nametoindex");:调用if_nametoindex()函数获取网络设备名称对应的接口索引,并将结果赋值给addr.sll_ifindex。如果返回值为0,说明出现错误,调用errExit()函数输出错误信息并退出程序。

  4. addr.sll_family = AF_PACKET;:设置addr的协议族为AF_PACKET,表示数据链路层套接字。

  5. memcpy (addr.sll_addr, mac, 6);:复制之前获取的硬件地址(mac)到addr.sll_addr数组中。

  6. addr.sll_halen = ETH_ALEN;:设置以太网硬件地址长度为6字节。

这段代码是用于创建一个 struct sockaddr_ll 结构体,它用于表示链路层(Link Layer)套接字地址。与TCP/IP协议栈中的其他套接字地址结构不同,sockaddr_ll 是专门为链路层套接字(AF_PACKET 套接字)设计的。

在 TCP/IP 协议栈中,网络通信通常涉及到 IP 地址和端口号,它们分别由 struct sockaddr_instruct sockaddr_in6 结构体来表示。这些结构体适用于传输层的套接字,如 TCP 和 UDP。然而,在链路层,我们不关注 IP 地址或端口号,而是关注设备接口(例如以太网卡)和硬件地址(MAC 地址)。

让我们逐行解释代码:

  1. 定义了一个 struct sockaddr_ll 类型的变量 addr
  2. 使用 memset() 函数将 addr 变量的所有字节设置为0,这样可以确保所有未显式初始化的字段都具有默认值。
  3. 调用 if_nametoindex() 函数,传入参数 dev(可能是一个字符串,代表设备名称,如 “eth0”),返回对应的接口索引。如果返回 0,则说明发生了错误,调用 errExit() 函数输出错误信息并退出程序。
  4. addr.sll_family 字段设置为 AF_PACKET,表明这是一个链路层套接字地址。
  5. 使用 memcpy() 函数将长度为6个字节的 MAC 地址(存储在 mac 变量中)复制到 addr.sll_addr 字段中。MAC 地址是链路层地址的一种形式。
  6. 设置 addr.sll_halen 字段为 ETH_ALEN(定义为6),表示硬件地址的长度(即MAC地址的长度)。

这个结构体的创建方式与TCP/IP套接字的地址结构体创建方式不同,是因为它们对应的是不同的层次和用途。sockaddr_ll 是为了处理链路层直接的数据包收发,而像 sockaddr_in 这样的结构体是为了处理更高层次的网络通信。

  1. ArpHdr hdr; memset(&hdr, 0, sizeof(hdr));:定义一个ArpHdr结构体变量hdr并将其内容清零。

  2. hdr.htype = htons(1);:设置硬件类型为以太网(值为1),并使用htons()函数进行主机字节序转换。

  3. hdr.ptype = htons(ETH_P_IP);:设置协议类型为IPv4(值为0x0800),并使用htons()函数进行主机字节序转换。

  4. hdr.hlen = 6;:设置硬件地址长度为6字节。

  5. hdr.plen = 4;:设置协议地址长度为4字节。

  6. hdr.opcode = htons(1); //ARPOP_REQUEST:设置操作码为ARP请求(值为1),并使用htons()函数进行主机字节序转换。

  7. memcpy(hdr.sender_mac, mac, 6);:复制之前获取的硬件地址(mac)到hdr.sender_mac数组中。

  8. memcpy(hdr.sender_ip, (uint8_t *)&src, 4);:复制源IP地址(src)到hdr.sender_ip数组中。

  9. uint32_t dst = htonl(dest);:将目标IP地址(dest)转换为主机字节序并存储在dst变量中。

  10. memcpy(hdr.target_ip, (uint8_t *)&dst, 4);:将dst变量的内容复制到hdr.target_ip数组中。

  11. uint8_t frame[ETH_FRAME_LEN]; // includes eht header, vlan, and crc:定义一个以太网帧缓冲区frame,大小为ETH_FRAME_LEN字节。

  12. memset(frame, 0, sizeof(frame));:将frame缓冲区内容清零。

  13. frame[0] = frame[1] = frame[2] = frame[3] = frame[4] = frame[5] = 0xff;:设置前6个字节为广播地址(全1)。

  14. memcpy(frame + 6, mac, 6);:复制之前获取的硬件地址(mac)到frame缓冲区的第7-12字节。

  15. frame[12] = ETH_P_ARP / 256;:设置以太网帧类型字段的高位字节为ARP(值为0x08)。

  16. frame[13] = ETH_P_ARP % 256;:设置以太网帧类型字段的低位字节为ARP(值为0x00)。

  17. memcpy (frame + 14, &hdr, sizeof(hdr));:将hdr结构体内容复制到frame缓冲区的第15字节开始的位置。

  18. if (sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr)) <= 0) errExit("send");:使用sendto()函数发送构建好的以太网帧。如果发送失败,则调用errExit()函数输出错误信息并退出程序。

  19. fflush(0);:刷新标准输出流。

  20. dest++;:将目标IP地址递增1,以便扫描下一个IP地址。

这段代码是用于构建一个 ARP 请求数据包,并通过 AF_PACKET 套接字发送出去。ARP(Address Resolution Protocol)是一种网络层协议,它用于将IP地址解析为MAC地址。在以太网中,设备需要知道目标的MAC地址才能直接发送数据帧。

让我们逐行解释代码:

  1. 定义了一个 ArpHdr 类型的变量 hdr
  2. 使用 memset() 函数将 hdr 变量的所有字节设置为0,这样可以确保所有未显式初始化的字段都具有默认值。
  3. hdr.htype 字段设置为 htons(1),表示硬件类型为以太网(ETH)。
  4. hdr.ptype 字段设置为 htons(ETH_P_IP),表示协议类型为IPv4。
  5. 设置 hdr.hlen 字段为6,表示硬件地址长度(即MAC地址长度)。
  6. 设置 hdr.plen 字段为4,表示协议地址长度(即IPv4地址长度)。
  7. hdr.opcode 字段设置为 htons(1),表示这是一个 ARP 请求(ARPOP_REQUEST)。
  8. 使用 memcpy() 函数将长度为6个字节的 MAC 地址(存储在 mac 变量中)复制到 hdr.sender_mac 字段中。
  9. 将 IPv4 源地址(src)转换为网络字节序(大端),然后使用 memcpy() 函数将转换后的值复制到 hdr.sender_ip 字段中。
  10. 将 IPv4 目标地址(dest)转换为网络字节序(大端),然后使用 memcpy() 函数将转换后的值复制到 hdr.target_ip 字段中。

接下来,我们构建一个以太网帧:

  1. 定义一个长度为 ETH_FRAME_LEN 的字节数组 frame 用于存储以太网帧。
  2. 使用 memset() 函数将 frame 数组的所有字节设置为0,这样可以确保所有未显式初始化的字段都具有默认值。
  3. 设置前6个字节为FF FF FF FF FF FF,这是以太网广播地址。
  4. 使用 memcpy() 函数将长度为6个字节的 MAC 地址(存储在 mac 变量中)复制到 frame 数组中的相应位置。
  5. 设置以太网类型字段为 EtherType_ARP(即 ETH_P_ARP),这告诉接收者这个数据帧包含一个 ARP 数据包。
  6. 使用 memcpy() 函数将之前构建的 ARP 请求头(hdr)复制到 frame 数组中的相应位置。

最后,我们将构建好的以太网帧通过 AF_PACKET 套接字发送出去:

  1. 调用 sendto() 函数,将 frame 数组、帧长度(14 + sizeof(ArpHdr))、目的地套接字地址结构体 addr 等参数传入。如果发送失败,则调用 errExit() 函数输出错误信息并退出程序。
  2. 使用 fflush() 函数刷新标准输出流(这里是为了确保错误信息能及时输出到终端)。
  3. 将目标 IP 地址加1,以便向下一个目标发送 ARP 请求。

这段代码的主要目的是构造并发送 ARP 请求数据包,用于获取目标设备的 MAC 地址。

  • if (FD_ISSET(sock, &rfds)) {...}:这段代码检查是否发生了可读事件。如果是,它将读取并解析接收到的数据包。如果数据包是一个ARP响应,并且其目标MAC地址和IP地址与我们发送的ARP请求中的源MAC地址和IP地址匹配,程序将打印出响应的源IP地址和MAC地址。

这段代码是用于接收和处理ARP响应的。以下是每一行代码的详细解释:

  1. if (FD_ISSET(sock, &rfds)):检查文件描述符集合rfds中的sock是否已准备好读取。如果准备好了,则继续执行。

  2. int len = recvfrom(sock, frame, ETH_FRAME_LEN, 0, NULL, NULL);:使用recvfrom()函数从套接字sock中接收数据,并将数据存储在frame缓冲区中,同时获取数据长度并存储在len变量中。参数ETH_FRAME_LEN表示以太网帧的最大长度。

  3. if (len < 0) { perror("recvfrom"); }:如果接收数据失败(即len小于0),则调用perror()函数输出错误信息。

  4. if ((unsigned int) len < 14 + sizeof(ArpHdr)) continue;:如果收到的数据长度小于以太网帧头(14字节)和ARP头部之和(通常为28字节),则跳过当前循环迭代。

  5. if (frame[12] != (ETH_P_ARP / 256) || frame[13] != (ETH_P_ARP % 256)) continue;:检查以太网帧类型字段是否等于ARP协议类型(值为0x0800)。如果不匹配,则跳过当前循环迭代。

  6. ArpHdr hdr; memcpy(&hdr, frame + 14, sizeof(ArpHdr));:定义一个ArpHdr结构体变量hdr,并将从以太网帧中提取的ARP头部内容复制到hdr结构体中。

  7. if (hdr.opcode == htons(2)):检查ARP操作码是否等于ARP应答(值为2),如果是,则继续执行。

  8. if (memcmp(mac, hdr.target_mac, 6) != 0) continue;:比较之前获取的硬件地址(mac)与ARP响应中的目标MAC地址。如果不匹配,则跳过当前循环迭代。

  9. uint32_t ip; memcpy(&ip, hdr.target_ip, 4);:定义一个整型变量ip,并将ARP响应中的目标IP地址复制到ip变量中。

  10. if (ip != src) continue;:比较ip变量中的目标IP地址与源IP地址(src)。如果不匹配,则跳过当前循环迭代。

  11. memcpy(&ip, hdr.sender_ip, 4);:将ARP响应中的发送者IP地址复制到ip变量中。

  12. ip = ntohl(ip);:将IP地址从网络字节序转换为主机字节序。

  13. if (ip == last_ip) // filter duplicates continue;:比较ip变量中的IP地址与上一次记录的IP地址(last_ip)。如果相同,则跳过当前循环迭代,以过滤重复的ARP响应。

  14. last_ip = ip;:将ip变量中的IP地址赋值给last_ip变量,以便在下一次循环中进行重复检测。

  15. if (header_printed == 0) { fmessage(" Network scan:\n"); header_printed = 1; }:如果header_printed变量尚未设置,则输出"Network scan:"消息,并将header_printed变量设为1。

  16. fmessage(" %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n", PRINT_MAC(hdr.sender_mac), PRINT_IP(ip));:输出发现的ARP响应,包括发送者的MAC地址和IP地址。

  • close(sock);:这行代码关闭了数据包套接字。

这个函数的主要目的是在指定的网络接口上执行ARP扫描,以发现活动的IP地址。它通过发送ARP请求并等待响应来实现这一目标。如果收到一个有效的ARP响应,程序将打印出响应的源IP地址和MAC地址。这是一个基本的网络扫描工具,可以用于网络诊断和安全分析。


interface.c

net_bridge_add_interface 要参考另一篇文章《详细解读Linuxl网络命名空间,veth,birdge.与路由》

这段代码提供了与网络接口管理相关的各种功能,包括:

  1. check_if_name():检查给定的网络设备名称是否有效(长度不超过IFNAMSIZ)。
  2. net_bridge_add_interface():将一个veth设备添加到指定的网桥。
  3. net_if_up():启用指定的网络接口。
  4. net_get_mtu():获取指定网络接口的MTU(最大传输单元)。
  5. net_set_mtu():设置指定网络接口的MTU。
  6. net_ifprint():扫描当前命名空间中的所有接口,并打印出每个接口的IP地址、子网掩码、MAC地址和状态。如果启用了扫描选项,则对每个接口执行ARP扫描。
  7. net_get_mac():获取指定网络接口的MAC地址。
  8. net_if_ip():配置指定网络接口的IPv4地址和子网掩码,可选地设置MTU。
  9. net_if_mac():配置指定网络接口的MAC地址。
  10. net_if_ip6():配置指定网络接口的IPv6地址和前缀长度。

这些函数主要用于管理和配置Linux系统的网络接口,例如创建和删除接口、更改接口状态、配置接口的IP地址和MAC地址等。


#include "fnet.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/route.h>
#include <linux/if_bridge.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

static void check_if_name(const char *ifname) {
	if (strlen(ifname) > IFNAMSIZ) {
		fprintf(stderr, "Error fnet: invalid network device name %s\n", ifname);
		exit(1);
	}
}

// add a veth device to a bridge
void net_bridge_add_interface(const char *bridge, const char *dev) {
	check_if_name(bridge);
	check_if_name(dev);

	// somehow adding the interface to the bridge resets MTU on bridge device!!!
	// workaround: restore MTU on the bridge device
	// todo: put a real fix in
	int mtu1 = net_get_mtu(bridge);

	struct ifreq ifr;
	int err;
	int ifindex = if_nametoindex(dev);

	if (ifindex <= 0)
		errExit("if_nametoindex");

	int sock;
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
              	errExit("socket");

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1);
    
    
#ifdef SIOCBRADDIF
	ifr.ifr_ifindex = ifindex;
	err = ioctl(sock, SIOCBRADDIF, &ifr);
	if (err < 0)
#endif
	{
		unsigned long args[4] = { BRCTL_ADD_IF, ifindex, 0, 0 };

		ifr.ifr_data = (char *) args;
		err = ioctl(sock, SIOCDEVPRIVATE, &ifr);
	}
	(void) err;
	close(sock);

	int mtu2 = net_get_mtu(bridge);
	if (mtu1 != mtu2)
		net_set_mtu(bridge, mtu1);
}


// bring interface up
void net_if_up(const char *ifname) {
	check_if_name(ifname);

	int sock = socket(AF_INET,SOCK_DGRAM,0);
	if (sock < 0)
		errExit("socket");

	// get the existing interface flags
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_addr.sa_family = AF_INET;

	// read the existing flags
	if (ioctl(sock, SIOCGIFFLAGS, &ifr ) < 0)
		errExit("ioctl");

	ifr.ifr_flags |= IFF_UP;

	// set the new flags
	if (ioctl( sock, SIOCSIFFLAGS, &ifr ) < 0)
		errExit("ioctl");

	// checking
	// read the existing flags
	if (ioctl(sock, SIOCGIFFLAGS, &ifr ) < 0)
		errExit("ioctl");

	// wait not more than 500ms for the interface to come up
	int cnt = 0;
	while (cnt < 50) {
		usleep(10000);			  // sleep 10ms

		// read the existing flags
		if (ioctl(sock, SIOCGIFFLAGS, &ifr ) < 0)
			errExit("ioctl");
		if (ifr.ifr_flags & IFF_RUNNING)
			break;
		cnt++;
	}

	close(sock);
}

int net_get_mtu(const char *ifname) {
	check_if_name(ifname);
	int mtu = 0;
	int s;
	struct ifreq ifr;

	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		errExit("socket");

	memset(&ifr, 0, sizeof(ifr));
	ifr.ifr_addr.sa_family = AF_INET;
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == 0)
		mtu = ifr.ifr_mtu;
	close(s);


	return mtu;
}

void net_set_mtu(const char *ifname, int mtu) {
	check_if_name(ifname);
	int s;
	struct ifreq ifr;

	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		errExit("socket");

	memset(&ifr, 0, sizeof(ifr));
	ifr.ifr_addr.sa_family = AF_INET;
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_mtu = mtu;
	if (ioctl(s, SIOCSIFMTU, (caddr_t)&ifr) != 0) {
		if (!arg_quiet)
			fprintf(stderr, "Warning fnet: cannot set mtu for interface %s\n", ifname);
	}
	close(s);
}

// scan interfaces in current namespace and print IP address/mask for each interface
void net_ifprint(int scan) {
	uint32_t ip;
	uint32_t mask;
	struct ifaddrs *ifaddr, *ifa;

	if (getifaddrs(&ifaddr) == -1)
		errExit("getifaddrs");

	fmessage("%-17.17s%-19.19s%-17.17s%-17.17s%-6.6s\n",
		"Interface", "MAC", "IP", "Mask", "Status");
	// walk through the linked list
	for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL)
			continue;

		if (ifa->ifa_addr->sa_family == AF_INET) {
			struct sockaddr_in *si = (struct sockaddr_in *) ifa->ifa_netmask;
			mask = ntohl(si->sin_addr.s_addr);
			si = (struct sockaddr_in *) ifa->ifa_addr;
			ip = ntohl(si->sin_addr.s_addr);

			// interface status
			char *status;
			if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP)
				status = "UP";
			else
				status = "DOWN";

			// ip address and mask
			char ipstr[30];
			sprintf(ipstr, "%d.%d.%d.%d", PRINT_IP(ip));
			char maskstr[30];
			sprintf(maskstr, "%d.%d.%d.%d", PRINT_IP(mask));

			// mac address
			unsigned char mac[6];
			net_get_mac(ifa->ifa_name, mac);
			char macstr[30];
			if (strcmp(ifa->ifa_name, "lo") == 0)
				macstr[0] = '\0';
			else
				sprintf(macstr, "%02x:%02x:%02x:%02x:%02x:%02x", PRINT_MAC(mac));

			// print
			fmessage("%-17.17s%-19.19s%-17.17s%-17.17s%-6.6s\n",
				ifa->ifa_name, macstr, ipstr, maskstr, status);

			// network scanning
			if (!scan)				// scanning disabled
				continue;
			if (strcmp(ifa->ifa_name, "lo") == 0)	// no loopbabck scanning
				continue;
			if (mask2bits(mask) < 16)		// not scanning large networks
				continue;
			if (!ip)					// if not configured
				continue;
			// only if the interface is up and running
			if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP)
				arp_scan(ifa->ifa_name, ip, mask);
		}
	}
	freeifaddrs(ifaddr);
}

int net_get_mac(const char *ifname, unsigned char mac[6]) {
	check_if_name(ifname);

	struct ifreq ifr;
	int sock;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
              	errExit("socket");

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;

	if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1)
		errExit("ioctl");
	memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);

	close(sock);
	return 0;
}

// configure interface ipv4 address
void net_if_ip(const char *ifname, uint32_t ip, uint32_t mask, int mtu) {
	check_if_name(ifname);
	int sock = socket(AF_INET,SOCK_DGRAM,0);
	if (sock < 0)
		errExit("socket");

	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_addr.sa_family = AF_INET;

	((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr = htonl(ip);
	if (ioctl( sock, SIOCSIFADDR, &ifr ) < 0)
		errExit("ioctl");

	if (ip != 0) {
		((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr =  htonl(mask);
		if (ioctl( sock, SIOCSIFNETMASK, &ifr ) < 0)
			errExit("ioctl");
	}

	// configure mtu
	if (mtu > 0) {
		ifr.ifr_mtu = mtu;
		if (ioctl( sock, SIOCSIFMTU, &ifr ) < 0)
			errExit("ioctl");
	}

	close(sock);
	usleep(10000);				  // sleep 10ms
	return;
}

int net_if_mac(const char *ifname, const unsigned char mac[6]) {
	check_if_name(ifname);
	struct ifreq ifr;
	int sock;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
              	errExit("socket");

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
	memcpy(ifr.ifr_hwaddr.sa_data, mac, 6);

	if (ioctl(sock, SIOCSIFHWADDR, &ifr) == -1)
		errExit("ioctl");
	close(sock);
	return 0;
}

// configure interface ipv6 address
// ex: firejail --net=eth0 --ip6=2001:0db8:0:f101::1/64
struct ifreq6 {
	struct in6_addr ifr6_addr;
	uint32_t ifr6_prefixlen;
	unsigned int ifr6_ifindex;
};
void net_if_ip6(const char *ifname, const char *addr6) {
	check_if_name(ifname);
	if (strchr(addr6, ':') == NULL) {
		fprintf(stderr, "Error fnet: invalid IPv6 address %s\n", addr6);
		exit(1);
	}

	// extract prefix
	unsigned long prefix;
	char *ptr;
	if ((ptr = strchr(addr6, '/'))) {
		prefix = atol(ptr + 1);
		if (prefix > 128) {
			fprintf(stderr, "Error fnet: invalid prefix for IPv6 address %s\n", addr6);
			exit(1);
		}
		*ptr = '\0';	// mark the end of the address
	}
	else
		prefix = 128;

	// extract address
	struct sockaddr_in6 sin6;
	memset(&sin6, 0, sizeof(sin6));
	sin6.sin6_family = AF_INET6;
	int rv = inet_pton(AF_INET6, addr6, sin6.sin6_addr.s6_addr);
	if (rv <= 0) {
		fprintf(stderr, "Error fnet: invalid IPv6 address %s\n", addr6);
		exit(1);
	}

	// open socket
	int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
	if (sock < 0) {
		fprintf(stderr, "Error fnet: IPv6 is not supported on this system\n");
		exit(1);
	}

	// find interface index
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_addr.sa_family = AF_INET;
	if (ioctl(sock, SIOGIFINDEX, &ifr) < 0) {
		perror("ioctl SIOGIFINDEX");
		exit(1);
	}

	// configure address
	struct ifreq6 ifr6;
	memset(&ifr6, 0, sizeof(ifr6));
	ifr6.ifr6_prefixlen = prefix;
	ifr6.ifr6_ifindex = ifr.ifr_ifindex;
	memcpy((char *) &ifr6.ifr6_addr, (char *) &sin6.sin6_addr, sizeof(struct in6_addr));
	if (ioctl(sock, SIOCSIFADDR, &ifr6) < 0) {
		perror("ioctl SIOCSIFADDR");
		exit(1);
	}

	close(sock);
}



这部分代码是用于操作网络接口的函数。主要包括以下几个功能:

  • net_bridge_add_interface:将指定的网络设备添加到指定的网桥中。
  • net_if_up:将指定的网络设备设置为“UP”状态,即启用该设备。
  • net_get_mtu:获取指定网络设备的MTU(最大传输单元)大小。
  • net_set_mtu:设置指定网络设备的MTU大小。
  • net_ifprint:打印当前命名空间中的所有网络设备的信息,包括接口名、MAC地址、IP地址和子网掩码等。
  • net_get_mac:获取指定网络设备的MAC地址。
  • net_if_ip:设置指定网络设备的IPv4地址和子网掩码,并可以同时设置MTU大小。
  • net_if_mac:设置指定网络设备的MAC地址。
  • net_if_ip6:设置指定网络设备的IPv6地址和前缀长度。

这些函数主要用于在Linux系统中操作网络接口,例如添加或删除网络设备、设置网络设备的状态、配置网络设备的IP地址和子网掩码等。

static int net_netlink_address_tentative(struct nlmsghdr *current_header) {
	struct ifaddrmsg *msg = NLMSG_DATA(current_header);
	int has_flags = 0;
#ifdef IFA_FLAGS
	struct rtattr *rta = IFA_RTA(msg);
	size_t msg_len = IFA_PAYLOAD(current_header);
	while (RTA_OK(rta, msg_len)) {
		if (rta->rta_type == IFA_FLAGS) {
			has_flags = 1;
			uint32_t *flags = RTA_DATA(rta);
			if (*flags & IFA_F_TENTATIVE)
				return 1;
		}
		rta = RTA_NEXT(rta, msg_len);
	}
#endif
	// According to <linux/if_addr.h>, if an IFA_FLAGS attribute is present,
	// the field ifa_flags should be ignored.
	return !has_flags && (msg->ifa_flags & IFA_F_TENTATIVE);
}

static int net_netlink_if_has_ll(int sock, uint32_t index) {
	struct {
		struct nlmsghdr header;
		struct ifaddrmsg message;
	} req;
	memset(&req, 0, sizeof(req));
	req.header.nlmsg_len = NLMSG_LENGTH(sizeof(req.message));
	req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
	req.header.nlmsg_type = RTM_GETADDR;
	req.message.ifa_family = AF_INET6;
	if (send(sock, &req, req.header.nlmsg_len, 0) != req.header.nlmsg_len)
		errExit("send");

	int found = 0;
	int all_parts_processed = 0;
	while (!all_parts_processed) {
		char buf[16384];
		ssize_t len = recv(sock, buf, sizeof(buf), 0);
		if (len < 0)
			errExit("recv");
		if (len < (ssize_t) sizeof(struct nlmsghdr)) {
			fprintf(stderr, "Received incomplete netlink message\n");
			exit(1);
		}

		struct nlmsghdr *current_header = (struct nlmsghdr *) buf;
		while (NLMSG_OK(current_header, len)) {
			switch (current_header->nlmsg_type) {
			case RTM_NEWADDR: {
				struct ifaddrmsg *msg = NLMSG_DATA(current_header);
				if (!found && msg->ifa_index == index && msg->ifa_scope == RT_SCOPE_LINK &&
						!net_netlink_address_tentative(current_header))
					found = 1;
			}
				break;
			case NLMSG_NOOP:
				break;
			case NLMSG_DONE:
				all_parts_processed = 1;
				break;
			case NLMSG_ERROR: {
				struct nlmsgerr *err = NLMSG_DATA(current_header);
				fprintf(stderr, "Netlink error: %d\n", err->error);
				exit(1);
			}
				break;
			default:
				fprintf(stderr, "Unknown netlink message type: %u\n", current_header->nlmsg_type);
				exit(1);
				break;
			}

			current_header = NLMSG_NEXT(current_header, len);
		}
	}

	return found;
}

// wait for a link-local IPv6 address for DHCPv6
// ex: firejail --net=br0 --ip6=dhcp
void net_if_waitll(const char *ifname) {
	// find interface index
	int inet6_sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
	if (inet6_sock < 0) {
		fprintf(stderr, "Error fnet: IPv6 is not supported on this system\n");
		exit(1);
	}
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_addr.sa_family = AF_INET;
	if (ioctl(inet6_sock, SIOGIFINDEX, &ifr) < 0) {
		perror("ioctl SIOGIFINDEX");
		exit(1);
	}
	close(inet6_sock);
	if (ifr.ifr_ifindex < 0) {
		fprintf(stderr, "Error fnet: interface index is negative\n");
		exit(1);
	}
	uint32_t index = (uint32_t) ifr.ifr_ifindex;

	// poll for link-local address
	int netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (netlink_sock < 0)
		errExit("socket");
	int tries = 0;
	int found = 0;
	while (tries < 60 && !found) {
		if (tries >= 1)
			usleep(500000);

		found = net_netlink_if_has_ll(netlink_sock, index);

		tries++;
	}
	close(netlink_sock);

	if (!found) {
		fprintf(stderr, "Waiting for link-local IPv6 address of %s timed out\n", ifname);
		exit(1);
	}
}
#include "fnet.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/route.h>
#include <linux/if_bridge.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

static void check_if_name(const char *ifname) {
	if (strlen(ifname) > IFNAMSIZ) {
		fprintf(stderr, "Error fnet: invalid network device name %s\n", ifname);
		exit(1);
	}
}


这段代码是用于将一个veth设备添加到一个网络桥的C语言程序。下面是每行代码的解释:

  • #include "fnet.h":包含"fnet.h"头文件,这是一个网络库,提供了一些网络编程的功能。
  • #include <arpa/inet.h>:包含Internet地址操作的头文件,提供了一些用于处理网络地址的函数。
  • #include <sys/socket.h>:包含系统套接字头文件,提供了套接字编程的基本功能。
  • #include <sys/ioctl.h>:包含输入/输出控制头文件,提供了对设备的直接控制。
  • #include <netdb.h>:包含网络数据库操作的头文件,提供了一些用于处理网络数据库的函数。
  • #include <ifaddrs.h>:包含接口地址操作的头文件,提供了一些用于处理接口地址的函数。
  • #include <net/if.h>:包含网络接口头文件,提供了网络接口的相关定义和结构。
  • #include <net/if_arp.h>:包含ARP协议头文件,提供了ARP协议的相关定义和结构。
  • #include <net/route.h>:包含路由表操作的头文件,提供了一些用于处理路由表的函数。
  • #include <linux/if_bridge.h>:包含Linux桥接操作的头文件,提供了一些用于处理Linux桥接的函数。
  • #include <linux/netlink.h>:包含Linux网络链路操作的头文件,提供了一些用于处理Linux网络链路的函数。
  • #include <linux/rtnetlink.h>:包含Linux路由链路操作的头文件,提供了一些用于处理Linux路由链路的函数。
  • static void check_if_name(const char *ifname) {...}:这是一个静态函数,用于检查接口名称的长度是否超过了IFNAMSIZ(接口名称的最大长度)。如果超过,程序将打印错误消息并退出。

net_bridge_add_interface

这个函数net_bridge_add_interface的目的是将一个veth设备(虚拟以太网设备)添加到一个网络桥。下面是每行代码的解释:

  • void net_bridge_add_interface(const char *bridge, const char *dev) {...}:这是函数的定义,它接受两个参数:一个指向桥接名称的字符串,和一个指向设备名称的字符串。
  • check_if_name(bridge); check_if_name(dev);:这两行代码调用check_if_name函数来检查桥接名称和设备名称的长度是否超过了IFNAMSIZ(接口名称的最大长度)。如果超过,程序将打印错误消息并退出。
  • int mtu1 = net_get_mtu(bridge);:这行代码调用net_get_mtu函数来获取桥接的MTU(最大传输单元)值,并将结果存储在mtu1变量中。
  • struct ifreq ifr;:这行代码定义了一个ifreq结构体变量ifr,用于存储接口请求。

struct ifreq 是一个在 Unix-like 系统(例如 Linux)中用于网络接口操作的数据结构。这个数据结构通常用于获取和设置网络接口的各种参数,比如 IP 地址、子网掩码、MAC 地址等。

以下是一个典型的 struct ifreq 的定义:

struct ifreq {
    char ifr_name[IFNAMSIZ];  /* Interface name */
    union {
        struct sockaddr ifru_addr;
        struct sockaddr ifru_dstaddr;
        struct sockaddr ifru_broadaddr;
        struct sockaddr ifru_netmask;
        struct sockaddr ifru_hwaddr;
        short           ifru_flags;
        int             ifru_ivalue;
        int             ifru_mtu;
        struct ifmap    ifru_map;
        char            ifru_slave[IFNAMSIZ];
        char            ifru_newname[IFNAMSIZ];
#if defined(__FreeBSD__)
        caddr_t         ifru_data;
#endif
    } ifr_ifru;
};

在这个数据结构中,ifr_name 字段用于存储网络接口的名称,如 “eth0”。ifr_ifru 是一个联合体,包含了多个不同的字段,可以用来存储各种不同的信息,具体取决于你想获取或设置什么信息。

  • int err; int ifindex = if_nametoindex(dev);:这两行代码定义了一个错误码变量err,并调用if_nametoindex函数来获取设备的接口索引,并将结果存储在ifindex变量中。
  • if (ifindex <= 0) errExit("if_nametoindex");:这行代码检查接口索引是否有效。如果无效(即ifindex小于或等于0),程序将打印错误消息并退出。
  • int sock; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) errExit("socket");:这两行代码定义了一个套接字变量sock,并调用socket函数来创建一个套接字,并将结果存储在sock变量中。如果创建套接字失败,程序将打印错误消息并退出。

在这个特定的场景中,创建套接字是为了获得一个有效的文件描述符,以便将其传递给 ioctl() 系统调用。ioctl() 函数通常需要一个文件描述符作为其第一个参数,而这个文件描述符应该与你想要控制的设备相关联。

在本例中,我们不是使用套接字进行网络通信,而是利用它来访问底层网络设备接口。这是因为 Unix-like 系统(包括 Linux)通常将网络设备视为文件系统中的特殊文件,因此可以使用文件描述符和相关的系统调用来操作这些设备。

通过创建一个套接字并获取其文件描述符,我们可以执行如 SIOCBRADDIF 或 SIOCDEVPRIVATE 这样的 ioctl() 命令,这些命令允许我们向内核发送请求以添加一个新的网络设备到网桥。这里的套接字实际上起到了“桥梁”或“中介”的作用,使我们能够通过系统调用与内核进行交互,从而实现对网络设备的操作。

所以,在这种情况下,套接字的作用是作为一个载体,提供了一个与操作系统进行低级别网络设备操作的接口,而不是用于传统的网络数据传输。

  • memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1);:这两行代码将ifr结构体的内存设置为零,并将桥接名称复制到ifr.ifr_name字段。

在这个示例中,网桥的名称是通过 ifr.ifr_name 字段来指定的。在调用 ioctl() 函数之前,你需要将该字段设置为网桥的接口名称。

例如:

strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1);

这里的 bridge 是一个指向字符串的指针,它包含了网桥的名称(如 “br0”)。strncpy() 函数用于将这个名称复制到 ifr.ifr_name 字段中。IFNAMSIZ 是一个宏,定义了最大接口名称长度。

  • #ifdef SIOCBRADDIF ... #endif:这是一个条件编译块,只有当SIOCBRADDIF被定义时,它才会被编译。在这个块中,程序尝试使用SIOCBRADDIF命令来将设备添加到桥接。如果这个命令失败,程序将尝试使用SIOCDEVPRIVATE命令。
  1. #ifdef SIOCBRADDIF
    这是一个预处理器指令,用于检查是否定义了 SIOCBRADDIF 常量。如果已定义,则执行接下来的代码块。

  2. ifr.ifr_ifindex = ifindex;
    ifr.ifr_ifindex 成员设置为所要添加到网桥的设备的接口索引。

  3. err = ioctl(sock, SIOCBRADDIF, &ifr);
    使用 ioctl() 函数调用 SIOCBRADDIF 请求,将指定的设备添加到网桥中。返回值存储在 err 变量中。

ioctl() 函数并不会将 ifr 结构体中的数据保存在套接字(sock)中。相反,它使用套接字作为与内核通信的通道,向内核发送一个请求,并可能接收内核的响应。

在这个例子中,SIOCBRADDIF 请求表示我们想要添加一个网络设备到网桥中。当调用 ioctl() 函数时,我们需要提供一个指向已初始化的 struct ifreq 结构体的指针作为第三个参数。这个结构体包含了我们想要操作的设备的信息,如设备名称和接口索引。

内核处理请求后,可能会更新设备或网桥的状态,但这些更改不会直接反映在 sockifr 中。通常情况下,你需要再次调用 ioctl() 函数并传入不同的请求来查询当前状态。

总之,ioctl() 函数通过套接字传递信息给内核,但它并不修改套接字本身的数据。

struct ifreq 中保存的数据在与内核进行网络接口控制操作时发挥作用。具体来说,当你调用 ioctl() 函数并传入一个指向 struct ifreq 结构体的指针时,这些数据将被用于描述你要执行的操作以及涉及的网络接口。

struct ifreq 中保存的数据在每次调用 ioctl() 函数时发挥作用,告诉内核你想执行的操作(如查询 IP 地址、设置 IP 地址或添加设备到网桥),以及涉及到的网络接口信息。

  1. if (err < 0)
    检查 err 是否小于零,如果是,则表示 ioctl() 调用失败。

  2. unsigned long args[4] = { BRCTL_ADD_IF, ifindex, 0, 0 };
    如果上一步中的 ioctl() 失败,那么使用这个数组作为参数来尝试另一种方法添加设备到网桥中。args 数组包含四个元素:BRCTL_ADD_IF 是一个常量,表示我们想要添加一个设备到网桥;ifindex 是我们要添加的设备的接口索引;剩余两个元素暂时未使用,因此设置为零。

  3. ifr.ifr_data = (char *) args;
    ifr.ifr_data 成员设置为指向 args 数组的指针。

  4. err = ioctl(sock, SIOCDEVPRIVATE, &ifr);
    再次调用 ioctl() 函数,这次使用 SIOCDEVPRIVATE 请求和新的参数。这通常是为了兼容旧版本的内核或特定的设备驱动程序。

  5. (void) err;
    强制忽略 err 的值,防止编译器发出警告。这是因为在这个例子中,我们没有处理 ioctl() 返回值的情况。

  • close(sock);:这行代码关闭了套接字。
  • int mtu2 = net_get_mtu(bridge); if (mtu1 != mtu2) net_set_mtu(bridge, mtu1);:这两行代码获取桥接的当前MTU值,并将结果存储在mtu2变量中。然后,它检查mtu1mtu2是否相等。如果不相等,它将调用net_set_mtu函数来将桥接的MTU值设置回mtu1

这些代码的目的是检查将设备添加到网桥后,网桥的 MTU(最大传输单元)值是否发生了变化。如果发现网桥的 MTU 值被重置了,则重新设置为原来的值。

在添加设备到网桥之前,这段代码首先获取网桥当前的 MTU 值,并将其存储在 mtu1 中:

int mtu1 = net_get_mtu(bridge);

然后执行添加设备到网桥的操作。

在操作完成后,再次获取网桥的 MTU 值,并将其存储在 mtu2 中:

int mtu2 = net_get_mtu(bridge);

接下来,比较 mtu1mtu2 的值,看它们是否相等:

if (mtu1 != mtu2)

如果不相等,说明添加设备到网桥后,网桥的 MTU 值被重置了。在这种情况下,需要重新设置网桥的 MTU 为原来的值:

net_set_mtu(bridge, mtu1);

通过这种方式,这段代码确保了添加设备到网桥不会意外地改变网桥的 MTU 值。

保持网桥的 MTU(最大传输单元)值不变通常是出于以下原因:

  1. 网络性能
    网络设备的 MTU 值决定了能够通过该设备发送的最大数据包大小。如果在添加设备到网桥后,网桥的 MTU 值被重置为默认值(如 1500 字节),这可能不适用于某些应用场景,导致网络性能下降或产生其他问题。

  2. 兼容性
    某些应用程序或服务可能依赖于特定的 MTU 值来确保其正常运行。如果网桥的 MTU 值发生变化,可能会导致这些应用程序或服务无法正常工作。

  3. 配置一致性
    在某些情况下,管理员可能已经根据特定需求手动设置了网桥的 MTU 值。为了保持网络配置的一致性和可预测性,应该尽量避免自动更改已设置的 MTU 值。

  4. 防止 IP 分片
    如果 MTU 值设置得过小,可能导致较大的数据包需要进行分片才能通过网络。IP 分片会增加网络延迟,并且每个分片都可能导致额外的网络开销。因此,为了避免不必要的 IP 分片,通常希望保持一个合适的 MTU 值。

综上所述,确保网桥的 MTU 值在添加设备后不变,有助于维护网络的稳定性和性能。

net_if_up

这个函数net_if_up的目的是启动一个网络接口。下面是每行代码的解释:

  • void net_if_up(const char *ifname) {...}:这是函数的定义,它接受一个参数:一个指向接口名称的字符串。

  • check_if_name(ifname);:这行代码调用check_if_name函数来检查接口名称的长度是否超过了IFNAMSIZ(接口名称的最大长度)。如果超过,程序将打印错误消息并退出。

  • int sock = socket(AF_INET,SOCK_DGRAM,0); if (sock < 0) errExit("socket");:这两行代码定义了一个套接字变量sock,并调用socket函数来创建一个套接字,并将结果存储在sock变量中。如果创建套接字失败,程序将打印错误消息并退出。

  • struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); ifr.ifr_addr.sa_family = AF_INET;:这四行代码定义了一个ifreq结构体变量ifr,用于存储接口请求。然后,它将ifr结构体的内存设置为零,并将接口名称复制到ifr.ifr_name字段,最后设置ifr.ifr_addr.sa_family字段为AF_INET

  • if (ioctl(sock, SIOCGIFFLAGS, &ifr ) < 0) errExit("ioctl");:这行代码使用ioctl函数来获取接口的标志。如果ioctl调用失败,程序将打印错误消息并退出。

  • ifr.ifr_flags |= IFF_UP;:这行代码将接口的标志设置为IFF_UP,表示接口应该被启动。

  • if (ioctl( sock, SIOCSIFFLAGS, &ifr ) < 0) errExit("ioctl");:这行代码使用ioctl函数来设置接口的新标志。如果ioctl调用失败,程序将打印错误消息并退出。

SIOCSIFFLAGS 是一个用于在 Unix-like 系统(如 Linux)中与 ioctl() 函数一起使用的常量。它表示我们想要设置网络接口的状态标志。

当你调用 ioctl() 函数时,需要将这个常量作为第二个参数传递给它,并提供一个指向已初始化的 struct ifreq 结构体的指针作为第三个参数。struct ifreq 结构体中的 ifr_flags 成员应该设置为你要设置的状态标志。

例如,在下面的代码片段中:

if (ioctl( sock, SIOCSIFFLAGS, &ifr ) < 0)
    errExit("ioctl");

这段代码使用 SIOCSIFFLAGS 请求来设置网络接口的状态标志。这里的 sock 是一个有效的套接字文件描述符,通常通过 socket() 函数创建;&ifr 是指向一个已经初始化的 struct ifreq 结构体的指针。如果 ioctl() 调用成功,它会返回零。否则,它会返回一个负值并设置 errno 值以指示错误原因。

  • while (cnt < 50) {...}:这是一个循环,用于等待接口启动。在循环中,程序首先休眠10毫秒,然后使用ioctl函数来获取接口的当前标志。如果接口已经启动(即标志包含IFF_RUNNING),程序将跳出循环。否则,它将增加计数器cnt并继续循环。如果在500毫秒内接口没有启动,程序将跳出循环。
  • close(sock);:这行代码关闭了套接字。

这个函数的主要目的是启动一个网络接口。它通过操作网络接口和IP地址,以及调用系统调用和库函数来实现这一目标。


net_get_mtu

这个函数net_get_mtu的目的是获取一个网络接口的MTU(最大传输单元)值。下面是每行代码的解释:

  • int net_get_mtu(const char *ifname) {...}:这是函数的定义,它接受一个参数:一个指向接口名称的字符串。
  • check_if_name(ifname);:这行代码调用check_if_name函数来检查接口名称的长度是否超过了IFNAMSIZ(接口名称的最大长度)。如果超过,程序将打印错误消息并退出。
  • int mtu = 0;:这行代码定义了一个整数变量mtu,并将其初始化为0。这个变量用于存储MTU值。
  • int s; struct ifreq ifr;:这两行代码定义了一个套接字变量s和一个ifreq结构体变量ifrs用于存储套接字的文件描述符,ifr用于存储接口请求。
  • if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) errExit("socket");:这行代码调用socket函数来创建一个套接字,并将结果存储在s变量中。如果创建套接字失败,程序将打印错误消息并退出。
  • memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sa_family = AF_INET; strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);:这三行代码将ifr结构体的内存设置为零,设置ifr.ifr_addr.sa_family字段为AF_INET,并将接口名称复制到ifr.ifr_name字段。
  • if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == 0) mtu = ifr.ifr_mtu;:这行代码使用ioctl函数来获取接口的MTU值。如果ioctl调用成功,它将将MTU值存储在mtu变量中。
  • SIOCGIFMTU 是一个用于在 Unix-like 系统(如 Linux)中与 ioctl() 函数一起使用的常量。它表示我们想要查询网络接口的 MTU(最大传输单元)值。

    当你调用 ioctl() 函数时,需要将这个常量作为第二个参数传递给它,并提供一个指向已初始化的 struct ifreq 结构体的指针作为第三个参数。struct ifreq 结构体中的 ifr_name 成员应该设置为你要查询的网络接口的名称。

    例如,在下面的代码片段中:

    if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == 0)
        mtu = ifr.ifr_mtu;
    

    这段代码使用 SIOCGIFMTU 请求来查询网络接口的当前 MTU 值。这里的 s 是一个有效的套接字文件描述符,通常通过 socket() 函数创建;(caddr_t)&ifr 是指向一个已经初始化的 struct ifreq 结构体的指针。如果 ioctl() 调用成功,它会返回零,并将查询到的 MTU 值存储在 ifr.ifr_mtu 成员中。否则,它会返回一个负值并设置 errno 值以指示错误原因。

  • close(s);:这行代码关闭了套接字。
  • return mtu;:这行代码返回MTU值。

这个函数的主要目的是获取一个网络接口的MTU值。它通过操作网络接口和IP地址,以及调用系统调用和库函数来实现这一目标。



net_set_mtu

这段代码的主要功能是设置网络接口的MTU(最大传输单元)。下面是每一行代码的解释:

  1. void net_set_mtu(const char *ifname, int mtu) {:定义一个函数net_set_mtu,它接受一个字符串参数ifname(网络接口的名称)和一个整数参数mtu(要设置的MTU值)。
  2. check_if_name(ifname);:调用check_if_name函数检查网络接口的名称是否有效。
  3. int s;:定义一个整数s,用于存储套接字的文件描述符。
  4. struct ifreq ifr;:定义一个ifreq结构ifrifreq结构用于存储网络接口的信息。
  5. if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) errExit("socket");:创建一个IPv4的数据报套接字,如果失败则退出程序。
  6. memset(&ifr, 0, sizeof(ifr));:将ifr结构的内存区域清零。
  7. ifr.ifr_addr.sa_family = AF_INET;:设置ifr结构的地址族为IPv4。
  8. strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);:将网络接口的名称复制到ifr结构的ifr_name字段。
  9. ifr.ifr_mtu = mtu;:将MTU值赋给ifr结构的ifr_mtu字段。
  10. if (ioctl(s, SIOCSIFMTU, (caddr_t)&ifr) != 0) {:调用ioctl函数设置网络接口的MTU值,如果失败则执行以下操作。

SIOCSIFMTU 是一个用于在 Unix-like 系统(如 Linux)中与 ioctl() 函数一起使用的常量。它表示我们想要设置网络接口的 MTU(最大传输单元)值。

当你调用 ioctl() 函数时,需要将这个常量作为第二个参数传递给它,并提供一个指向已初始化的 struct ifreq 结构体的指针作为第三个参数。struct ifreq 结构体中的 ifr_name 成员应该设置为你要设置的网络接口的名称,而 ifr.ifr_mtu 成员则应该包含你要设置的新 MTU 值。

例如,在下面的代码片段中:

ifr.ifr_mtu = mtu;
if (ioctl(s, SIOCSIFMTU, (caddr_t)&ifr) != 0)

这段代码使用 SIOCSIFMTU 请求来设置网络接口的当前 MTU 值。这里的 s 是一个有效的套接字文件描述符,通常通过 socket() 函数创建;(caddr_t)&ifr 是指向一个已经初始化的 struct ifreq 结构体的指针,其中 ifr.ifr_mtu 成员包含了要设置的新 MTU 值。如果 ioctl() 调用成功,它会返回零。否则,它会返回一个负值并设置 errno 值以指示错误原因。

  1. if (!arg_quiet) fprintf(stderr, "Warning fnet: cannot set mtu for interface %s\n", ifname);:如果arg_quiet参数为0,则向标准错误输出流打印一条警告信息。
  2. }:结束if语句。
  3. close(s);:关闭套接字。
  4. }:结束net_set_mtu函数。
net_ifprint

这段代码的主要功能是扫描当前命名空间中的网络接口,并为每个接口打印IP地址和掩码。下面是每一行代码的解释:

  1. void net_ifprint(int scan) {:定义一个函数net_ifprint,它接受一个整数参数scan
  2. uint32_t ip;:定义一个无符号32位整数ip,用于存储IP地址。
  3. uint32_t mask;:定义一个无符号32位整数mask,用于存储网络掩码。
  4. struct ifaddrs *ifaddr, *ifa;:定义两个指向ifaddrs结构的指针ifaddrifaifaddrs结构用于存储网络接口信息。
  5. if (getifaddrs(&ifaddr) == -1) errExit("getifaddrs");:调用getifaddrs函数获取所有网络接口的信息,如果失败则退出程序。
  6. fmessage("%-17.17s%-19.19s%-17.17s%-17.17s%-6.6s\n", "Interface", "MAC", "IP", "Mask", "Status");:打印表头,包括"Interface"、“MAC”、“IP”、“Mask"和"Status”。
  7. for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {:遍历所有的网络接口。
  8. if (ifa->ifa_addr == NULL) continue;:如果接口没有分配地址,则跳过当前接口。
  9. if (ifa->ifa_addr->sa_family == AF_INET) {:如果接口的地址属于IPv4地址,则执行以下操作。
  10. struct sockaddr_in *si = (struct sockaddr_in *) ifa->ifa_netmask;:获取接口的网络掩码。
  11. mask = ntohl(si->sin_addr.s_addr);:将网络掩码从网络字节序转换为主机字节序。
  12. si = (struct sockaddr_in *) ifa->ifa_addr;:获取接口的IP地址。
  13. ip = ntohl(si->sin_addr.s_addr);:将IP地址从网络字节序转换为主机字节序。
  14. char *status;:定义一个字符指针status,用于存储接口的状态。
  15. if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP) status = "UP"; else status = "DOWN";:如果接口正在运行并且已启动,则状态为"UP",否则状态为"DOWN"。
  16. char ipstr[30]; sprintf(ipstr, "%d.%d.%d.%d", PRINT_IP(ip));:将IP地址转换为字符串格式。
  17. char maskstr[30]; sprintf(maskstr, "%d.%d.%d.%d", PRINT_IP(mask));:将网络掩码转换为字符串格式。
  18. unsigned char mac[6]; net_get_mac(ifa->ifa_name, mac);:获取接口的MAC地址。
  19. char macstr[30]; if (strcmp(ifa->ifa_name, "lo") == 0) macstr[0] = '\0'; else sprintf(macstr, "%02x:%02x:%02x:%02x:%02x:%02x", PRINT_MAC(mac));:如果接口名为"lo",则MAC地址为空,否则将MAC地址转换为字符串格式。
  20. fmessage("%-17.17s%-19.19s%-17.17s%-17.17s%-6.6s\n", ifa->ifa_name, macstr, ipstr, maskstr, status);:打印接口的名称、MAC地址、IP地址、网络掩码和状态。
  21. if (!scan) continue;:如果scan参数为0,则跳过当前接口。
  22. if (strcmp(ifa->ifa_name, "lo") == 0) continue;:如果接口名为"lo",则跳过当前接口。
  23. if (mask2bits(mask) < 16) continue;:如果网络掩码对应的子网大小大于等于65536,则跳过当前接口。
  24. if (!ip) continue;:如果IP地址为0,则跳过当前接口。
  25. if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP) arp_scan(ifa->ifa_name, ip, mask);:如果接口正在运行并且已启动,则对接口进行ARP扫描。
  26. }:结束if语句。
  27. }:结束for循环。
  28. freeifaddrs(ifaddr);:释放ifaddrs结构的内存。
  29. }:结束net_ifprint函数。
net_get_mac

这段代码的主要功能是获取网络接口的MAC地址。下面是每一行代码的解释:

  1. int net_get_mac(const char *ifname, unsigned char mac[6]) {:定义一个函数net_get_mac,它接受一个字符串参数ifname(网络接口的名称)和一个无符号字符数组mac(用于存储MAC地址)。
  2. check_if_name(ifname);:调用check_if_name函数检查网络接口的名称是否有效。
  3. struct ifreq ifr;:定义一个ifreq结构ifrifreq结构用于存储网络接口的信息。
  4. int sock;:定义一个整数sock,用于存储套接字的文件描述符。
  5. if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) errExit("socket");:创建一个IPv4的流套接字,如果失败则退出程序。
  6. memset(&ifr, 0, sizeof(ifr));:将ifr结构的内存区域清零。
  7. strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);:将网络接口的名称复制到ifr结构的ifr_name字段。
  8. ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;:设置ifr结构的硬件地址类型为以太网地址。
  9. if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) errExit("ioctl");:调用ioctl函数获取网络接口的硬件地址,如果失败则退出程序。

SIOCGIFHWADDR 是一个用于在 Unix-like 系统(如 Linux)中与 ioctl() 函数一起使用的常量。它表示我们想要查询网络接口的硬件地址(MAC 地址)。

当你调用 ioctl() 函数时,需要将这个常量作为第二个参数传递给它,并提供一个指向已初始化的 struct ifreq 结构体的指针作为第三个参数。struct ifreq 结构体中的 ifr_name 成员应该设置为你要查询的网络接口的名称。

例如,在下面的代码片段中:

if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0)
    errExit("ioctl");

这段代码使用 SIOCGIFHWADDR 请求来查询网络接口的当前硬件地址(MAC 地址)。这里的 s 是一个有效的套接字文件描述符,通常通过 socket() 函数创建;&ifr 是指向一个已经初始化的 struct ifreq 结构体的指针。如果 ioctl() 调用成功,它会返回零,并将查询到的 MAC 地址存储在 ifr.ifr_hwaddr.sa_data 成员中。否则,它会返回一个负值并设置 errno 值以指示错误原因。

请注意,不同系统上可能有多个版本的 struct ifreq 结构体,它们可能会有不同的字段名称和布局。因此,你需要根据你的目标系统的实现来适当地处理这些结构体。

  1. memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);:将网络接口的MAC地址复制到mac数组。
  2. close(sock);:关闭套接字。
  3. return 0;:函数返回0,表示成功获取了MAC地址。
  4. }:结束net_get_mac函数。
net_if_ip

这段代码的主要功能是配置网络接口的IPv4地址。下面是每一行代码的解释:

  1. void net_if_ip(const char *ifname, uint32_t ip, uint32_t mask, int mtu) {:定义一个函数net_if_ip,它接受一个字符串参数ifname(网络接口的名称)、两个无符号32位整数参数ipmask(分别表示IP地址和网络掩码)以及一个整数参数mtu(表示最大传输单元)。
  2. check_if_name(ifname);:调用check_if_name函数检查网络接口的名称是否有效。
  3. int sock = socket(AF_INET,SOCK_DGRAM,0);:创建一个IPv4的数据报套接字,如果失败则退出程序。
  4. struct ifreq ifr;:定义一个ifreq结构ifrifreq结构用于存储网络接口的信息。
  5. memset(&ifr, 0, sizeof(ifr));:将ifr结构的内存区域清零。
  6. strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);:将网络接口的名称复制到ifr结构的ifr_name字段。
  7. ifr.ifr_addr.sa_family = AF_INET;:设置ifr结构的地址族为IPv4。
  8. ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr = htonl(ip);:将IP地址从主机字节序转换为网络字节序,并赋给ifr结构的sin_addr.s_addr字段。
  9. if (ioctl( sock, SIOCSIFADDR, &ifr ) < 0) errExit("ioctl");:调用ioctl函数设置网络接口的IP地址,如果失败则退出程序。
  10. if (ip != 0) {:如果IP地址不为0,则执行以下操作。
  11. ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr = htonl(mask);:将网络掩码从主机字节序转换为网络字节序,并赋给ifr结构的sin_addr.s_addr字段。
  12. if (ioctl( sock, SIOCSIFNETMASK, &ifr ) < 0) errExit("ioctl");:调用ioctl函数设置网络接口的网络掩码,如果失败则退出程序。
  13. }:结束if语句。
  14. if (mtu > 0) {:如果MTU值大于0,则执行以下操作。
  15. ifr.ifr_mtu = mtu;:将MTU值赋给ifr结构的ifr_mtu字段。
  16. if (ioctl( sock, SIOCSIFMTU, &ifr ) < 0) errExit("ioctl");:调用ioctl函数设置网络接口的MTU值,如果失败则退出程序。
  17. }:结束if语句。
  18. close(sock);:关闭套接字。
  19. usleep(10000);:使程序暂停10毫秒。
  20. return;:函数返回。
  21. }:结束net_if_ip函数。
net_if_mac

这段代码的主要功能是设置网络接口的MAC地址和IPv6地址。下面是每一行代码的解释:

  1. int net_if_mac(const char *ifname, const unsigned char mac[6]) {:定义一个函数net_if_mac,它接受一个字符串参数ifname(网络接口的名称)和一个无符号字符数组mac(表示MAC地址)。
  2. check_if_name(ifname);:调用check_if_name函数检查网络接口的名称是否有效。
  3. struct ifreq ifr;:定义一个ifreq结构ifrifreq结构用于存储网络接口的信息。
  4. int sock;:定义一个整数sock,用于存储套接字的文件描述符。
  5. if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) errExit("socket");:创建一个IPv4的流套接字,如果失败则退出程序。
  6. memset(&ifr, 0, sizeof(ifr));:将ifr结构的内存区域清零。
  7. strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);:将网络接口的名称复制到ifr结构的ifr_name字段。
  8. ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;:设置ifr结构的硬件地址类型为以太网地址。
  9. memcpy(ifr.ifr_hwaddr.sa_data, mac, 6);:将MAC地址复制到ifr结构的ifr_hwaddr.sa_data字段。
  10. if (ioctl(sock, SIOCSIFHWADDR, &ifr) == -1) errExit("ioctl");:调用ioctl函数设置网络接口的MAC地址,如果失败则退出程序。
  11. close(sock);:关闭套接字。
  12. return 0;:函数返回0,表示成功设置了MAC地址。
  13. }:结束net_if_mac函数。
  14. struct ifreq6 {:定义一个ifreq6结构。ifreq6结构用于存储网络接口的IPv6地址信息。
  15. struct in6_addr ifr6_addr;:定义一个in6_addr结构ifr6_addrin6_addr结构用于存储IPv6地址。
  16. uint32_t ifr6_prefixlen;:定义一个无符号32位整数ifr6_prefixlen,用于存储网络前缀的长度。
  17. unsigned int ifr6_ifindex;:定义一个无符号整数ifr6_ifindex,用于存储网络接口的索引。
  18. };:结束ifreq6结构的定义。这个结构可以用于配置网络接口的IPv6地址,但是这段代码并没有给出具体的实现。你可能需要查找相关的资料或者参考其他的代码来了解如何使用这个结构。
net_if_ip6

这段代码的主要功能是配置网络接口的IPv6地址。下面是每一行代码的解释:

  1. void net_if_ip6(const char *ifname, const char *addr6) {:定义一个函数net_if_ip6,它接受一个字符串参数ifname(网络接口的名称)和一个字符串参数addr6(表示IPv6地址)。
  2. check_if_name(ifname);:调用check_if_name函数检查网络接口的名称是否有效。
  3. if (strchr(addr6, ':') == NULL) {:如果IPv6地址中不包含冒号,说明这个地址无效,执行以下操作。
  4. fprintf(stderr, "Error fnet: invalid IPv6 address %s\n", addr6);:向标准错误输出流打印一条错误信息。
  5. exit(1);:退出程序。
  6. }:结束if语句。
  7. unsigned long prefix;:定义一个无符号长整数prefix,用于存储网络前缀的长度。
  8. char *ptr;:定义一个字符指针ptr
  9. if ((ptr = strchr(addr6, '/'))) {:如果IPv6地址中包含斜杠,说明这个地址包含网络前缀,执行以下操作。
  10. prefix = atol(ptr + 1);:将网络前缀从字符串转换为无符号长整数。
  11. if (prefix > 128) {:如果网络前缀的长度大于128,说明这个前缀无效,执行以下操作。
  12. fprintf(stderr, "Error fnet: invalid prefix for IPv6 address %s\n", addr6);:向标准错误输出流打印一条错误信息。
  13. exit(1);:退出程序。
  14. }:结束if语句。
  15. *ptr = '\0';:在IPv6地址的字符串中,将网络前缀的起始位置替换为字符串结束符,这样IPv6地址就只包含了地址部分,不再包含网络前缀。
  16. else prefix = 128;:如果IPv6地址中不包含斜杠,说明这个地址没有指定网络前缀,那么默认网络前缀的长度为128。
  17. struct sockaddr_in6 sin6;:定义一个sockaddr_in6结构sin6sockaddr_in6结构用于存储IPv6地址。
  18. memset(&sin6, 0, sizeof(sin6));:将sin6结构的内存区域清零。
  19. sin6.sin6_family = AF_INET6;:设置sin6结构的地址族为IPv6。
  20. int rv = inet_pton(AF_INET6, addr6, sin6.sin6_addr.s6_addr);:将IPv6地址从字符串格式转换为网络字节序的二进制格式,并存储到sin6结构的s6_addr字段。
  21. if (rv <= 0) {:如果转换失败,执行以下操作。
  22. fprintf(stderr, "Error fnet: invalid IPv6 address %s\n", addr6);:向标准错误输出流打印一条错误信息。
  23. exit(1);:退出程序。
  24. }:结束if语句。
  25. int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);:创建一个IPv6的数据报套接字,如果失败则执行以下操作。
  26. fprintf(stderr, "Error fnet: IPv6 is not supported on this system\n");:向标准错误输出流打印一条错误信息。
  27. exit(1);:退出程序。
  28. struct ifreq ifr;:定义一个ifreq结构ifrifreq结构用于存储网络接口的信息。
  29. memset(&ifr, 0, sizeof(ifr));:将ifr结构的内存区域清零。
  30. strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);:将网络接口的名称复制到ifr结构的ifr_name字段。
  31. ifr.ifr_addr.sa_family = AF_INET;:设置ifr结构的地址族为IPv4。
  32. if (ioctl(sock, SIOGIFINDEX, &ifr) < 0) {:调用ioctl函数获取网络接口的索引,如果失败则执行以下操作。
  33. perror("ioctl SIOGIFINDEX");:打印一条错误信息。
  34. exit(1);:退出程序。
  35. }:结束if语句。
  36. struct ifreq6 ifr6;:定义一个ifreq6结构ifr6ifreq6结构用于存储网络接口的IPv6地址信息。
  37. memset(&ifr6, 0, sizeof(ifr6));:将ifr6结构的内存区域清零。
  38. ifr6.ifr6_prefixlen = prefix;:将网络前缀的长度赋给ifr6结构的ifr6_prefixlen字段。
  39. ifr6.ifr6_ifindex = ifr.ifr_ifindex;:将网络接口的索引赋给ifr6结构的ifr6_ifindex字段。
  40. memcpy((char *) &ifr6.ifr6_addr, (char *) &sin6.sin6_addr, sizeof(struct in6_addr));:将IPv6地址复制到ifr6结构的ifr6_addr字段。
  41. if (ioctl(sock, SIOCSIFADDR, &ifr6) < 0) {:调用ioctl函数设置网络接口的IPv6地址,如果失败则执行以下操作。
  42. perror("ioctl SIOCSIFADDR");:打印一条错误信息。
  43. exit(1);:退出程序。
  44. }:结束if语句。
  45. close(sock);:关闭套接字。
  46. }:结束net_if_ip6函数。这个函数可以用于配置网络接口的IPv6地址,但是在实际使用中,你可能需要根据你的网络环境和需求进行一些修改。

ifname(网络接口的名称)和一个字符串参数addr6(表示IPv6地址)。
2. check_if_name(ifname);:调用check_if_name函数检查网络接口的名称是否有效。
3. if (strchr(addr6, ':') == NULL) {:如果IPv6地址中不包含冒号,说明这个地址无效,执行以下操作。
4. fprintf(stderr, "Error fnet: invalid IPv6 address %s\n", addr6);:向标准错误输出流打印一条错误信息。
5. exit(1);:退出程序。
6. }:结束if语句。
7. unsigned long prefix;:定义一个无符号长整数prefix,用于存储网络前缀的长度。
8. char *ptr;:定义一个字符指针ptr
9. if ((ptr = strchr(addr6, '/'))) {:如果IPv6地址中包含斜杠,说明这个地址包含网络前缀,执行以下操作。
10. prefix = atol(ptr + 1);:将网络前缀从字符串转换为无符号长整数。
11. if (prefix > 128) {:如果网络前缀的长度大于128,说明这个前缀无效,执行以下操作。
12. fprintf(stderr, "Error fnet: invalid prefix for IPv6 address %s\n", addr6);:向标准错误输出流打印一条错误信息。
13. exit(1);:退出程序。
14. }:结束if语句。
15. *ptr = '\0';:在IPv6地址的字符串中,将网络前缀的起始位置替换为字符串结束符,这样IPv6地址就只包含了地址部分,不再包含网络前缀。
16. else prefix = 128;:如果IPv6地址中不包含斜杠,说明这个地址没有指定网络前缀,那么默认网络前缀的长度为128。
17. struct sockaddr_in6 sin6;:定义一个sockaddr_in6结构sin6sockaddr_in6结构用于存储IPv6地址。
18. memset(&sin6, 0, sizeof(sin6));:将sin6结构的内存区域清零。
19. sin6.sin6_family = AF_INET6;:设置sin6结构的地址族为IPv6。
20. int rv = inet_pton(AF_INET6, addr6, sin6.sin6_addr.s6_addr);:将IPv6地址从字符串格式转换为网络字节序的二进制格式,并存储到sin6结构的s6_addr字段。
21. if (rv <= 0) {:如果转换失败,执行以下操作。
22. fprintf(stderr, "Error fnet: invalid IPv6 address %s\n", addr6);:向标准错误输出流打印一条错误信息。
23. exit(1);:退出程序。
24. }:结束if语句。
25. int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);:创建一个IPv6的数据报套接字,如果失败则执行以下操作。
26. fprintf(stderr, "Error fnet: IPv6 is not supported on this system\n");:向标准错误输出流打印一条错误信息。
27. exit(1);:退出程序。
28. struct ifreq ifr;:定义一个ifreq结构ifrifreq结构用于存储网络接口的信息。
29. memset(&ifr, 0, sizeof(ifr));:将ifr结构的内存区域清零。
30. strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);:将网络接口的名称复制到ifr结构的ifr_name字段。
31. ifr.ifr_addr.sa_family = AF_INET;:设置ifr结构的地址族为IPv4。
32. if (ioctl(sock, SIOGIFINDEX, &ifr) < 0) {:调用ioctl函数获取网络接口的索引,如果失败则执行以下操作。
33. perror("ioctl SIOGIFINDEX");:打印一条错误信息。
34. exit(1);:退出程序。
35. }:结束if语句。
36. struct ifreq6 ifr6;:定义一个ifreq6结构ifr6ifreq6结构用于存储网络接口的IPv6地址信息。
37. memset(&ifr6, 0, sizeof(ifr6));:将ifr6结构的内存区域清零。
38. ifr6.ifr6_prefixlen = prefix;:将网络前缀的长度赋给ifr6结构的ifr6_prefixlen字段。
39. ifr6.ifr6_ifindex = ifr.ifr_ifindex;:将网络接口的索引赋给ifr6结构的ifr6_ifindex字段。
40. memcpy((char *) &ifr6.ifr6_addr, (char *) &sin6.sin6_addr, sizeof(struct in6_addr));:将IPv6地址复制到ifr6结构的ifr6_addr字段。
41. if (ioctl(sock, SIOCSIFADDR, &ifr6) < 0) {:调用ioctl函数设置网络接口的IPv6地址,如果失败则执行以下操作。
42. perror("ioctl SIOCSIFADDR");:打印一条错误信息。
43. exit(1);:退出程序。
44. }:结束if语句。
45. close(sock);:关闭套接字。
46. }:结束net_if_ip6函数。这个函数可以用于配置网络接口的IPv6地址,但是在实际使用中,你可能需要根据你的网络环境和需求进行一些修改。

  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值