Iptables防火墙原理及在车联网中的实践

目录 

网络数据包过滤的实现

iptables规则解析

如何处理回环地址

工作在三层的Iptables如何实现对MAC的过滤

车联网安全实践

功能性应用

安全性应用

策略配置技巧

参考文章


车联网是防火墙上在C端少有的应用领域。防火墙的经典功能是实现内外网络的隔离,因此在企业内网、服务器网络等面向企业用户的领域内,防火墙有着广泛的应用。而在PC互联网时代,个人主机因其单一的网络结构基本不会单独配置防火墙应用,除非其安装的安全软件包含了防火墙功能。过渡到移动互联网时代之后,用户使用的智能终端由于使用了运营商级别的NAT,因此终端在IPV4网络上不能够被直接连接,再加上终端系统对权限的严格管控,使得防火墙在个人用户智能终端上显得愈发不那么重要。但进入到车联网时代和IOT时代之后,网络结构重新变得复杂起来,比如车内各种域控制器、传感器、网联模块等,组成了复杂的车内网络,承载着异常复杂的业务,防火墙的隔离功能再次变得重要起来。

网络数据包过滤的实现

Iptables防火墙包含用户空间的Iptables规则管理工具和内核空间的Netfilter包过滤模块。其中,Netfilter实现的包过滤为上层的iptables规则提供了强大和丰富的过滤逻辑。

Netfilter在内核的5个地方设置了HOOK 点,用户可以通过配置 iptables 规则,在HOOK点对报文进行过滤、修改等操作。这五个过滤点在包传输路径上的逻辑位置如下图所示:

Iptables防火墙原理及在车联网中的实践_第1张图片

接收路径

网卡收到报文,通过网卡驱动发送给内核协议栈,当报文达到协议栈的网络层(IP层)时,需要确定报文是应该继续往上层协议栈传送(local deliver),还是通过转发信息表转发(forward)出去;

发送路径

对本机向外发送(local out)的报文,经上层协议栈传递至协议栈的IP层,需要确定是从哪个网卡发送出去。

 明确五个HOOK点的位置,是理解iptables规则逻辑的最佳方式。五个HOOK点在协议栈中的具体位置如下图所示:

Iptables防火墙原理及在车联网中的实践_第2张图片

iptables规则解析

Netfilter的五个HOOK点提供了以规则链(rule chain)的方式管理用户对网络包的处理需求,用户通过iptables工具向规则链添加或删除规则。规则链根据HOOK点或协议栈位置提供对应的包处理功能,而表(table)则是按照功能对规则进行逻辑分类,如用于过滤的filter表,用于网络地址转换的nat表,表与链的组织关系如下表所示:

Iptables防火墙原理及在车联网中的实践_第3张图片

表格来源:Things_You_Should_Know_About_Netfilter[1]

 Netfilter在处理每个规则链时,会按照“raw、mangle、nat、filter、security、rawpost”的顺序依次处理对应的规则。

如何处理回环地址

在一般的认识中,当本机将数据包发送到本机上指定的一个网卡地址时,该数据包会到达这个指定的网络接口。但事实却不是这样,当数据包寻址后发现目的机器即为发送机器,它会被发送到环回接口。比如下面这个例子[1]:

# ip -4 addr ls eth0
2: eth0:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 192.168.1.21/24 brd 192.168.1.255 scope global eth0
# ip route get 192.168.1.21
local 192.168.1.21 dev lo  src 192.168.1.21 
    cache   mtu 16436 advmss 16396 hoplimit 64

我们可以看到,发送到这台机器eth0地址的数据包将在 lo 设备或环回接口上路由,即使目标地址不是 127.0.0.1。

考虑到每个控制器基本都存在域内通信的需求,因此,针对回环地址,我们建议的防火墙策略是应该在任何情况下都要保证其联通性。

工作在三层的Iptables如何实现对MAC的过滤

笔者曾经有一个疑问,因为iptables网络防火墙工作的层级是三层,但是它却能够对处于二层的MAC地址进行过滤,比如下面这个规则:

 iptables -A INPUT -m mac --mac-source XX:XX:XX:XX:XX:XX -j DROP

理论上,二层的MAC地址对工作在三层的功能模块是不可见的,但iptables确实实现了对MAC地址的过滤,即iptables在三层获取到了二层的MAC地址,这是怎么实现的呢?

有一种合理的思路是通过查询ARP表获取MAC地址,这种思路在原理上是行得通的。为了验证iptables是否采用这种方式实现,笔者对iptables进行了源码级别的分析,发现事实并非如此。iptables拿到MAC地址的关键,是使用了sk_buff结构。iptables通过sk_buff携带的mac_header等链路层数据结构,穿越了协议栈。sk_buff结构体定义如下:

//include/linux/skbuff.h
/** 
 *	struct sk_buff - socket buffer
 *	@next: Next buffer in list
 *	@prev: Previous buffer in list
 *	@tstamp: Time we arrived/left
 *	@rbnode: RB tree node, alternative to next/prev for netem/tcp
 *	@sk: Socket we are owned by
 *	@dev: Device we arrived on/are leaving by
 *	@cb: Control buffer. Free for use by every layer. Put private vars here
 *	@_skb_refdst: destination entry (with norefcount bit)
 *	@sp: the security path, used for xfrm
 *	@len: Length of actual data
 *	@data_len: Data length
 *	@mac_len: Length of link layer header
 *	@hdr_len: writable header length of cloned skb
 *	@csum: Checksum (must include start/offset pair)
 *	@csum_start: Offset from skb->head where checksumming should start
 *	@csum_offset: Offset from csum_start where checksum should be stored
 *	@priority: Packet queueing priority
 *	@ignore_df: allow local fragmentation
 *	@cloned: Head may be cloned (check refcnt to be sure)
 *	@ip_summed: Driver fed us an IP checksum
 *	@nohdr: Payload reference only, must not modify header
 *	@nfctinfo: Relationship of this skb to the connection
 *	@pkt_type: Packet class
 *	@fclone: skbuff clone status
 *	@ipvs_property: skbuff is owned by ipvs
 *	@peeked: this packet has been seen already, so stats have been
 *		done for it, don't do them again
 *	@nf_trace: netfilter packet trace flag
 *	@protocol: Packet protocol from driver
 *	@destructor: Destruct function
 *	@nfct: Associated connection, if any
 *	@nf_bridge: Saved data about a bridged frame - see br_netfilter.c
 *	@skb_iif: ifindex of device we arrived on
 *	@tc_index: Traffic control index
 *	@tc_verd: traffic control verdict
 *	@hash: the packet hash
 *	@queue_mapping: Queue mapping for multiqueue devices
 *	@xmit_more: More SKBs are pending for this queue
 *	@ndisc_nodetype: router type (from link layer)
 *	@ooo_okay: allow the mapping of a socket to a queue to be changed
 *	@l4_hash: indicate hash is a canonical 4-tuple hash over transport
 *		ports.
 *	@sw_hash: indicates hash was computed in software stack
 *	@wifi_acked_valid: wifi_acked was set
 *	@wifi_acked: whether frame was acked on wifi or not
 *	@no_fcs:  Request NIC to treat last 4 bytes as Ethernet FCS
  *	@napi_id: id of the NAPI struct this skb came from
 *	@secmark: security marking
 *	@offload_fwd_mark: fwding offload mark
 *	@mark: Generic packet mark
 *	@vlan_proto: vlan encapsulation protocol
 *	@vlan_tci: vlan tag control information
 *	@inner_protocol: Protocol (encapsulation)
 *	@inner_transport_header: Inner transport layer header (encapsulation)
 *	@inner_network_header: Network layer header (encapsulation)
 *	@inner_mac_header: Link layer header (encapsulation)
 *	@transport_header: Transport layer header
 *	@network_header: Network layer header
 *	@mac_header: Link layer header
 *	@tail: Tail pointer
 *	@end: End pointer
 *	@head: Head of buffer
 *	@data: Data head pointer
 *	@truesize: Buffer size
 *	@users: User count - see {datagram,tcp}.c
 */
struct sk_buff {
	union {
		struct {
			/* These two members must be first. */
			struct sk_buff		*next;
			struct sk_buff		*prev;

			union {
				ktime_t		tstamp;
				struct skb_mstamp skb_mstamp;
			};
		};
		struct rb_node	rbnode; /* used in netem & tcp stack */
	};
	struct sock		*sk;
	struct net_device	*dev;

	/*
	 * This is the control buffer. It is free to use for every
	 * layer. Please put your private variables there. If you
	 * want to keep them across layers you have to do a skb_clone()
	 * first. This is owned by whoever has the skb queued ATM.
	 */
	char			cb[48] __aligned(8);

	unsigned long		_skb_refdst;
	void			(*destructor)(struct sk_buff *skb);
#ifdef CONFIG_XFRM
	struct	sec_path	*sp;
#endif
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
	struct nf_conntrack	*nfct;
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
	struct nf_bridge_info	*nf_bridge;
#endif
	unsigned int		len,
				data_len;
	__u16			mac_len,
				hdr_len;

	/* Following fields are _not_ copied in __copy_skb_header()
	 * Note that queue_mapping is here mostly to fill a hole.
	 */
	kmemcheck_bitfield_begin(flags1);
	__u16			queue_mapping;
	__u8			cloned:1,
				nohdr:1,
				fclone:2,
				peeked:1,
				head_frag:1,
				xmit_more:1;
	/* one bit hole */
	kmemcheck_bitfield_end(flags1);

	/* fields enclosed in headers_start/headers_end are copied
	 * using a single memcpy() in __copy_skb_header()
	 */
	/* private: */
	__u32			headers_start[0];
	/* public: */

/* if you move pkt_type around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_TYPE_MAX	(7 << 5)
#else
#define PKT_TYPE_MAX	7
#endif
#define PKT_TYPE_OFFSET()	offsetof(struct sk_buff, __pkt_type_offset)

	__u8			__pkt_type_offset[0];
	__u8			pkt_type:3;
	__u8			pfmemalloc:1;
	__u8			ignore_df:1;
	__u8			nfctinfo:3;

	__u8			nf_trace:1;
	__u8			ip_summed:2;
	__u8			ooo_okay:1;
	__u8			l4_hash:1;
	__u8			sw_hash:1;
	__u8			wifi_acked_valid:1;
	__u8			wifi_acked:1;

	__u8			no_fcs:1;
	/* Indicates the inner headers are valid in the skbuff. */
	__u8			encapsulation:1;
	__u8			encap_hdr_csum:1;
	__u8			csum_valid:1;
	__u8			csum_complete_sw:1;
	__u8			csum_level:2;
	__u8			csum_bad:1;

#ifdef CONFIG_IPV6_NDISC_NODETYPE
	__u8			ndisc_nodetype:2;
#endif
	__u8			ipvs_property:1;
	__u8			inner_protocol_type:1;
	__u8			remcsum_offload:1;
	/* 3 or 5 bit hole */

#ifdef CONFIG_NET_SCHED
	__u16			tc_index;	/* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
	__u16			tc_verd;	/* traffic control verdict */
#endif
#endif

	union {
		__wsum		csum;
		struct {
			__u16	csum_start;
			__u16	csum_offset;
		};
	};
	__u32			priority;
	int			skb_iif;
	__u32			hash;
	__be16			vlan_proto;
	__u16			vlan_tci;
#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)
	union {
		unsigned int	napi_id;
		unsigned int	sender_cpu;
	};
#endif
	union {
#ifdef CONFIG_NETWORK_SECMARK
		__u32		secmark;
#endif
#ifdef CONFIG_NET_SWITCHDEV
		__u32		offload_fwd_mark;
#endif
	};

	union {
		__u32		mark;
		__u32		reserved_tailroom;
	};

	union {
		__be16		inner_protocol;
		__u8		inner_ipproto;
	};

	__u16			inner_transport_header;
	__u16			inner_network_header;
	__u16			inner_mac_header;

	__be16			protocol;
	__u16			transport_header;
	__u16			network_header;
	__u16			mac_header;

	/* private: */
	__u32			headers_end[0];
	/* public: */

	/* These elements must be at the end, see alloc_skb() for details.  */
	sk_buff_data_t		tail;
	sk_buff_data_t		end;
	unsigned char		*head,
				*data;
	unsigned int		truesize;
	atomic_t		users;
};

在三层获取MAC地址的代码实现如下:

 // net/netfilter/xt_mac.c
 25 static bool mac_mt(const struct sk_buff *skb, struct xt_action_param *par)
 26 {
 27     const struct xt_mac_info *info = par->matchinfo;
 28     bool ret;
 29 
 30     if (skb->dev == NULL || skb->dev->type != ARPHRD_ETHER)
 31         return false;
 32     if (skb_mac_header(skb) < skb->head)
 33         return false;
 34     if (skb_mac_header(skb) + ETH_HLEN > skb->data)
 35         return false;
 36     ret  = ether_addr_equal(eth_hdr(skb)->h_source, info->srcaddr);
 37     ret ^= info->invert;
 38     return ret;
 39 }

车联网安全实践

车联网iptables相关实践包括对iptables规则的功能性应用、安全性应用和一些应用的技巧。

功能性应用

1)NAT

整车内各个需要联网的控制器均需要通过TBOX或5G模块上网,实现方式便是通过iptables的NAT功能:

iptables -t nat -A POSTROUTING -o em1 -s 192.168.205.160 -j SNAT --to 218.25.116.165

2)跨VLAN连通网络

通过VLAN划分广播域是保证车内网络安全的重要手段,但同时也会存在跨VLAN通信的需求,这时需要使用iptables来连接不同的VLAN:

iptables -A DFD_ACCESS_RULE -p TCP -s 172.11.20.0/24 -d 172.11.30.1 --dport XXX -j ACCEPT

安全性应用

1)默认拒绝策略

默认策略建议使用DROP的方式,然后将合法的连接以白名单的方式追加到规则链当中。

# default drop
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

2)网络隔离

通过防火墙隔离车内和车外网络,VCU代表整车相关的电控功能单元,HU代表信息娱乐系统,通过iptables防火墙可以有效隔离整车网络和信息娱乐域:

Iptables防火墙原理及在车联网中的实践_第4张图片

3)高危端口过滤

高危端口包括SSH、远程诊断等敏感业务端口,通过iptalbes规则过滤并记录外部对高危端口的访问记录,可以有效阻止和发现潜在的攻击行为:

$IPTABLES -A INPUT -p tcp --dport 22 -j LOG --log-prefix='[IPT] DROP SSH I'
$IPTABLES -A INPUT -p tcp --dport 22 -j DROP

策略配置技巧

1)启用LOG模式

在研发早期开始启用LOG模式搜集车内未知的联网数据包,LOG模式通过-j LOG实现:

# LOG unknow packets
iptables -A INPUT -j LOG --log-ip-options --log-tcp-sequence --log-tcp-options --log-level 1 --log-prefix '[FIREWALL] DROP UNKNOW '

2)减少日志量

由于车内网络可能产生大量的数据连接,特别是自动驾驶相关的应用,其网络数据包的量可能极大,为了避免iptables产生过量的数据影响syslog正常记录内核日志,推荐使用iptables的connbytes模块,仅记录一个连接的前几个数据包,配置方式参考如下:

# Log only the first 3 packets
iptables -A INPUT -m connbytes --connbytes 1:3 --connbytes-mode packets  --connbytes-dir both -j LOG --log-ip-options --log-tcp-sequence --log-tcp-options --log-level 1 --log-prefix '[FIREWALL] DROP UNKNOW '
iptables -A INPUT -j DROP

3)将通用规则放在底部

建议将更加严格的匹配规则放在规则列表的前面,将宽松的匹配规则放在后面,这样可以避免更加严格的规则无法生效的问题。

4)在策略规则的顶部设置ip白名单

如果需要设置ip白名单,建议在规则列表的顶部设置,原因同上。

参考文章

[1]. Things You Should Know About Netfilter - Sfvlughttps://thermicorp.de/netfilter/iptables/sfvlug.editthis.info/wiki/Things_You_Should_Know_About_Netfilter.html

你可能感兴趣的:(车联网安全,系统安全,linux,安全,iptables,物联网,iot)