精华区 [关闭][返回]

当前位置:网易精华区>>讨论区精华>>电脑技术>>● FreeBSD>>OpenBSD 数据包过滤 PF FAQ中文版(转)-1

主题:OpenBSD 数据包过滤 PF FAQ中文版(转)-1
发信人: drillwater(灌水)
整理人: sungang(2004-11-15 10:14:50), 站内信件
 
                        PF:OpenBSD数据包过滤 
 
                                 张文通 
                                         翻译 
                                 唐  亮 
 

本文版权与原文英文版保持完全一致,鉴于译者水平有限,错误之处难免,请读者自己认真鉴别。 
 

============================================================================== 
 
 
 
PF: OpenBSD 数据包过滤 
 
------------------------------------------------------------------------------ 
目录 
 
  * 基本配置 
      + 开始 
      + 列表和宏 
      + 表 
      + 包过滤 
      + 网络地址转换 
      + 流量重定向 (端口转发) 
      + 规则生成捷径 
  * 高级配置 
      + 运行选项 
      + 流量整形 (数据包标准化) 
      + 锚定和命名(子)规则集 
      + 队列和优先级 
      + 地址池和负载均衡 
      + 数据包标记 
  * 附加主题 
      + 日志 
      + 性能 
      + 研究 FTP 
      + pf验证: 用Shell 进行网关验证 
  * 实例规则集 
      + 实例: 家庭和小办公室防火墙 
 
------------------------------------------------------------------------------ 
 
包过滤 (以下简称 PF) 是OpenBSD 系统上进行TCP/IP流量过滤和网络地址转换的软件系统。 PF 同样也能提供TCP/IP流量的整形和控制,并且提供带宽控制和数据包优先集控制。PF自openbsd 3.0以后作为内核的默认安装配置。以前版本的openbsd发行版使用一个不同的防火墙/NAT软件包,现在已经不再被支持。 
 
PF 最早是由 Daniel Hartmeier 开发的,现在的开发和维护由Daniel 和openbsd小组的其他成员负责。 
 
本文档对openbsd上运行的PF系统做一个简明的简介。本文可被用作是man手册页的补充,而不是它们的替代。本文档涵盖了PF的主要特性。要完整和深入的了解PF能做什么,请阅读pf(4)man手册页。 
 
本FAQ文档是针对使用openbsd 3.5的用户的。由于pf是不断成长和开发中的,3.5版本和当前版本之间存在软件的变化和功能增强。建议读者阅读正在运行系统的man手册页。 
 
------------------------------------------------------------------------------ 
 
$OpenBSD: index.html,v 1.16 2004/05/07 01:55:23 nick Exp $ 
============================================================================== 
  * 基本配置 
 
PF: 开始 
 
------------------------------------------------------------------------------ 
 
目录 
 
  * 激活 
  * 配置 
  * 控制 
 
------------------------------------------------------------------------------ 
 
激活 
 
要激活pf并且使它在启动时调用配置文件,编辑/etc/rc.conf文件,修改配置pf的一行: 
 
    pf=YES 
 
重启系统让配置生效。 
 
你也可以通过pfctl程序启动和停止pf 
 
    # pfctl -e 
    # pfctl -d 
 
注意这仅仅是启动和关闭PF,实际它不会载入规则集,规则集要么在系统启动时载入,要么在PF启动后通过命令单独载入。 
 
配置 
 
系统引导到在rc脚本文件运行PF时PF从/etc/pf.conf文件载入配置规则。注意当/etc/pf.conf文件是默认配置文件,在系统调用rc脚本文件时,它仅仅是作为文本文件由pfctl(8)装入并解释和插入pf(4)的。对于一些应用来说,其他的规则集可以在系统引导后由其他文件载入。对于一些设计的非常好的unix程序,PF提供了足够的灵活性。 
 
pf.conf 文件有7个部分: 
 
  * 宏:用户定义的变量,包括IP地址,接口名称等等  
  * 表: 一种用来保存IP地址列表的结构 
  * 选项: 控制PF如何工作的变量 
  * 整形: 重新处理数据包,进行正常化和碎片整理 
  * 排队: 提供带宽控制和数据包优先级控制. 
  * 转换: 控制网络地址转换和数据包重定向. 
  * 过滤规则: 在数据包通过接口时允许进行选择性的过滤和阻止 
 
除去宏和表,其他的段在配置文件中也应该按照这个顺序出现,尽管对于一些特定的应用并不是所有的段都是必须的。 
 
空行会被忽略,以#开头的行被认为是注释. 
 
控制 
 
引导之后,PF可以通过pfctl(8)程序进行操作,以下是一些例子: 
 
     # pfctl -f /etc/pf.conf     载入 pf.conf 文件 
     # pfctl -nf /etc/pf.conf    解析文件,但不载入 
     # pfctl -Nf /etc/pf.conf    只载入文件中的NAT规则 
     # pfctl -Rf /etc/pf.conf    只载入文件中的过滤规则 
 
     # pfctl -sn                 显示当前的NAT规则 
     # pfctl -sr                 显示当前的过滤规则 
     # pfctl -ss                 显示当前的状态表 
     # pfctl -si                 显示过滤状态和计数 
     # pfctl -sa                 显示任何可显示的 
 
完整的命令列表,参阅pfctl的man手册页。 
 
------------------------------------------------------------------------------ 
$OpenBSD: config.html,v 1.12 2004/05/07 01:55:23 nick Exp $ 
============================================================================== 
 
PF: 列表和宏 
 
------------------------------------------------------------------------------ 
 
目录 
 
  * 列表 
  * 宏 
 
------------------------------------------------------------------------------ 
 
列表 
 
一个列表允许一个规则集中指定多个相似的标准。例如,多个协议,端口号,地址等等。因此,不需要为每一个需要阻止的IP地址编写一个过滤规则,一条规则可以在列表中指定多个IP地址。列表的定义是将要指定的条目放在{  }大括号中。 
 
当pfctl(8)在载入规则集碰到列表时,它产生多个规则,每条规则对于列表中的一个条目。例如: 
 
    block out on fxp0 from { 192.168.0.1, 10.5.32.6 } to any 
 
展开后: 
 
    block out on fxp0 from 192.168.0.1 to any 
    block out on fxp0 from 10.5.32.6 to any 
 
多种列表可以在规则中使用,并不仅仅限于过滤规则: 
 
    rdr on fxp0 proto tcp from any to any port { 22 80 } -> \ 
       192.168.0.6 
    block out on fxp0 proto { tcp udp } from { 192.168.0.1, \ 
       10.5.32.6 } to any port { ssh telnet } 
 
注意逗号在列表条目之间是可有可无的。 
 
宏 
 
宏是用户定义变量用来指定IP地址,端口号,接口名称等等。宏可以降低PF规则集的复杂度并且使得维护规则集变得容易。 
 
宏名称必须以字母开头,可以包括字母,数字和下划线。宏名称不能包括保留关键字如: 
pass, out, 以及 queue. 
 
    ext_if = "fxp0" 
 
    block in on $ext_if from any to any 
 
这生成了一个宏名称为 ext_if. 当一个宏在它产生以后被引用时,它得名称前面以$字符开头。 
 
宏也可以展开成列表,如: 
 
    friends = "{ 192.168.1.1, 10.0.2.5, 192.168.43.53 }" 
 
宏能够被重复定义,由于宏不能在引号内被扩展,因此必须使用下面得语法: 
 
    host1 = "192.168.1.1" 
    host2 = "192.168.1.2" 
    all_hosts = "{" $host1 $host2 "}" 
 
宏 $all_hosts 现在会展开成 192.168.1.1, 192.168.1.2. 
 
------------------------------------------------------------------------------ 
$OpenBSD: macros.html,v 1.11 2004/05/07 01:55:23 nick Exp $ 
============================================================================== 
 
PF: 表 
 
------------------------------------------------------------------------------ 
 
目录 
 
  * 简介 
  * 配置 
  * 用 pfctl 进行操作 
  * 指定地址 
  * 地址匹配 
 
------------------------------------------------------------------------------ 
 
简介 
 
表是用来保存一组IPv4 或者 IPv6地址。在表中进行查询是非常快的,并且比列表消耗更少的内存和cpu时间。由于这个原因,表是保存大量地址的最好方法,在50,000个地址中查询仅比在50个地址中查询稍微多一点时间。表可以用于下列用途: 
 
  * 过滤,整形,NAT和重定向中的源或者目的地址. 
  * NAT规则中的转换地址. 
  * 重定向规则中的重定向地址. 
  * 过滤规则选项中 route-to, reply-to, 和 dup-to的目的地址. 
 
表可以通过在pf.conf里配置和使用pfctl生成。 
 
配置 
 
在 pf.conf文件中, 表是使用table关键字创建出来的。下面得关键字必须在创建表时指定。 
 
  * constant – 这类表得内容一旦创建出来就不能被改变。如果这个属性没有指定,可以使用pfctl(8)添加和删除表里得地址,即使系统是运行在2或者更高得安全级别上。 

  * persist – 即使没有规则引用这类表,内核也会把它保留在内存中。如果这个属性没有指定,当最后引用它得规则被取消后内核自动把它移出内存。 
 
实例: 
 
    table <goodguys> { 192.0.2.0/24 } 
    table <rfc1918> const { 192.168.0.0/16, 172.16.0.0/12, \ 
       10.0.0.0/8 } 
    table <spammers> persist 
 
    block in on fxp0 from { <rfc1918>, <spammers> } to any 
    pass  in on fxp0 from <goodguys> to any 
 
地址也可以用“非”来进行修改,如: 
 
    table <goodguys> { 192.0.2.0/24, !192.0.2.5 } 
 
goodguy表将匹配除192.0.2.5外192.0.2.0/24网段得所有地址。 
 
注意表名总是在<>符号得里面。 
 
表也可以由包含IP地址和网络地址的文本文件中输入: 
 
    table <spammers> persist file "/etc/spammers" 
 
    block in on fxp0 from <spammers> to any 
 
文件 /etc/spammers 应该包含被阻塞的IP地址或者CIDR网络地址,每个条目一行。以#开头的行被认为是注释会被忽略。 
 
用 pfctl 进行操作 
 
表可以使用pfctl(8)进行灵活的操作。例如,在上面产生的<spammer>表中增加条目可以这样写: 
 
    # pfctl -t spammers -T add 218.70.0.0/16 
 
如果<spammer>这个表不存在,这样会创建出这个表来。列出表中的内容可以这样: 
 
    # pfctl -t spammers -T show 
 
 -v 参数也可以使用-Tshow 来显示每个表的条目内容统计。要从表中删除条目,可以这样: 
 
    # pfctl -t spammers -T delete 218.70.0.0/16 
 
更多使用pfctl操作的信息可以参阅pfctl(8)。 
 
指定地址 
 
除了使用IP地址来指定主机外,也可以使用主机名。当主机名被解析成IP地址时,IPv4 和 IPv6地址都被插进规则中。IP地址也可以通过合法的接口名称或者self关键字输入表中,这样的表会分别包含接口或者机器上(包括loopback地址)上配置的所有IP地址。 
 
一个限制时指定地址0.0.0.0/0 以及 0/0在表中不能工作。替代方法是明确输入该地址或者使用宏。 
 
地址匹配 
 
表中的地址查询会匹配最接近的规则,比如: 
 
    table <goodguys> { 172.16.0.0/16, !172.16.1.0/24, 172.16.1.100 } 
 
    block in on dc0 all 
    pass  in on dc0 from <goodguys> to any 
 
任何自dc0上数据包都会把它的源地址和goodguys表中的地址进行匹配: 
 
  * 172.16.50.5 – 精确匹配172.16.0.0/16; 数据包符合可以通过 
  * 172.16.1.25 – 精确匹配!172.16.1.0/24; 数据包匹配表中的一条规则,但规则是“非”(使用“!”进行了修改);数据包不匹配表会被阻塞。 
  * 172.16.1.100 – 准确匹配172.16.1.100; 数据包匹配表,运行通过 
  * 10.1.4.55 – 不匹配表,阻塞。 
 
------------------------------------------------------------------------------ 
$OpenBSD: tables.html,v 1.12 2004/06/03 16:08:12 nick Exp $ 
============================================================================== 
 
PF: 包过滤 
 
------------------------------------------------------------------------------ 
 
目录 
 
  * 简介 
  * 规则语法 
  * 默认拒绝 
  * 通过流量 
  *  quick 关键字 
  * 状态保持 
  * UDP状态保持 
  * TCP Flags 
  * TCP SYN Proxy 
  * 阻塞欺骗包 
  * 被动操作系统识别 
  * IP 选项 
  * 过滤规则集实例 
 
------------------------------------------------------------------------------ 
 
简介 
 
包过滤是在数据包通过网络接口时进行选择性的运行通过或者阻塞。Pf(4)检查包时使用的标准是基于的3层(IPV4或者IPV6)和4层(TCP, UDP, ICMP, ICMPv6)包头。最常用的标准是源和目的地址,源和目的端口,以及协议。 
 
过滤规则集指定了数据包必须匹配的标准和规则集作用后的结果,在规则集匹配时通过或者阻塞。规则集由开始到结束顺序执行。除非数据包匹配的规则包含quick关键字,否则数据包在最终执行动作前会通过所有的规则检验。最后匹配的规则具有决定性,决定了数据包最终的执行结果。存在一条潜在的规则是如果数据包和规则集中的所有规则都不匹配,则它会被通过。 
 
规则语法 
 
一般而言,最简单的过滤规则语法是这样的: 
 
    action direction [log] [quick] on interface [af] [proto protocol] \ 
       from src_addr [port src_port] to dst_addr [port dst_port] \ 
       [tcp_flags] [state] 
 
action 
    数据包匹配规则时执行的动作,放行或者阻塞。放行动作把数据包传递给核心进行进一步出来,阻塞动作根据block-

policy 选项指定的方法进行处理。默认的动作可以修改为阻塞丢弃或者阻塞返回。 

direction 
    数据包传递的方向,进或者出 

log 
    指定数据包被pflogd(8)进行日志记录。如果规则指定了keep state, modulate state, or synproxy state 选项,则只有建立了连接的状态被日志。要记录所有的日志,使用log-all 

quick 
    如果数据包匹配的规则指定了quick关键字,则这条规则被认为时最终的匹配规则,指定的动作会立即执行。 

interface 
    数据包通过的网络接口的名称或组。组是接口的名称但没有最后的整数。比如ppp 或 fxp,会使得规则分别匹配任何ppp或者fxp接口上的任意数据包。 

af 
    数据包的地址类型,inet代表Ipv4,inet6代表Ipv6。通常PF能够根据源或者目标地址自动确定这个参数。 

protocol 
    数据包的4层协议: 
      + tcp 
      + udp 
      + icmp 
      + icmp6 
      + /etc/protocols中的协议名称 
      + 0~255之间的协议号 
      + 使用列表的一系列协议. 

src_addr, dst_addr 
    IP头中的源/目标地址。地址可以指定为: 
      + 单个的Ipv4或者Ipv6地址. 
      +  CIDR 网络地址. 
      + 能够在规则集载入时通过DNS解析到的合法的域名,IP地址会替代规则中的域名。 
      + 网络接口名称。网络接口上配置的所有ip地址会替代进规则中。 
      + 带有/掩码(例如/24)的网络接口的名称。每个根据掩码确定的CIDR网络地址都会被替代进规则中。. 
      + 带有()的网络接口名称。这告诉PF如果网络接口的IP地址改变了,就更新规则集。这个对于使用DHCP或者拨号来获得IP地址的接口特别有用,IP地址改变时不需要重新载入规则集。 
      + 带有如下的修饰词的网络接口名称: 
          o :network – 替代CIDR网络地址段 (例如, 192.168.0.0/24) 
          o :broadcast – 替代网络广播地址(例如, 192.168.0.255) 
          o :peer – 替代点到点链路上的ip地址。 
 
            另外,:0修饰词可以附加到接口名称或者上面的修饰词后面指示PF在替代时不包括网络接口的其余附加(alias)地址。这些修饰词也可以在接口名称在括号()内时使用。例如:fxp0:network:0 
 
      + 表. 
+ 上面的所有项但使用!(非)修饰词 
+ 使用列表的一系列地址. 
      + 关键字 any 代表所有地址 
      + 关键字 all 是 from any to any的缩写。 
src_port, dst_port 
    4层数据包头中的源/目标端口。端口可以指定为: 
      + 1 到 65535之间的整数 
      + /etc/services中的合法服务名称 
+ 使用列表的一系列端口 
+ 一个范围: 
          o != (不等于) 
          o < (小于)
o > (大于) 
          o <= (小于等于)
o >= (大于等于) 
          o >< (范围)
o <> (反转范围) 
 
                最后2个是二元操作符(他们需要2个参数),在范围内不包括参数。 
 
          o : (inclusive range) 
 
                inclusive range 也是二元操作符但范围内包括参数。 
 
tcp_flags 
    指定使用TCP协议时TCP头中必须设定的标记。 标记指定的格式是: flags check/mask. 例如: flags S/SA –这指引PF只检查S和A(SYN and ACK)标记,如果SYN标记是“on”则匹配。 
     
state 
    指定状态信息在规则匹配时是否保持。 
      + keep state – 对 TCP, UDP, ICMP起作用 
      + modulate state – 只对 TCP起作用. PF会为匹配规则的数据包产生强壮的初始化序列号。 
      + synproxy state – 代理外来的TCP连接以保护服务器不受TCP SYN FLOODs欺骗。这个选项包含了keep state 和 modulate state 的功能。 
 
默认拒绝 
 
按照惯例建立防火墙时推荐执行的是默认拒绝的方法。也就是说先拒绝所有的东西,然后有选择的允许某些特定的流量通过防火墙。这个方法之所以是推荐的是因为它宁可失之过于谨慎(也不放过任何风险),而且使得编写规则集变得简单。 
 
产生一个默认拒绝的过滤规则,开始2行过滤规则必须是: 
 
    block in  all 
    block out all 
这会阻塞任何通信方在任何方向上进入任意接口的所有流量。 
 
通过流量 
 
流量必须被明确的允许通过防火墙或者被默认拒绝的策略丢弃。这是数据包标准如源/目的端口,源/目的地址和协议开始活动的地方。无论何时数据包在被允许通过防火墙时规则都要设计的尽可能严厉。这是为了保证设计中的流量,也只有设计中的流量可以被允许通过。 
 
实例: 
 
# 允许本地网络192.168.0.0/24流量通过dc0接口进入访问openbsd机器的IP地址 
#192.168.0.1,同时也允许返回的数据包从dc0接口出去。 
    pass in  on dc0 from 192.168.0.0/24 to 192.168.0.1 
    pass out on dc0 from 192.168.0.1 to 192.168.0.0/24 
 
 
    # Pass TCP traffic in on fxp0 to the web server running on the 
    # OpenBSD machine. The interface name, fxp0, is used as the 
    # destination address so that packets will only match this rule if 
    # they're destined for the OpenBSD machine. 
    pass in on fxp0 proto tcp from any to fxp0 port www 
 
quick 关键字 
 
前面已经说过,每个数据包都要按自上至下的顺序按规则进行过滤。默认情况下,数据包被标记为通过,这个可以被任一规则改变,在到达最后一条规则前可以被来回改变多次,最后的匹配规则是“获胜者”。存在一个例外是:过滤规则中的quick关键字具有取消进一步往下处理的作用,使得规则指定的动作马上执行。看一下下面的例子: 
 
错误: 
 
    block in on fxp0 proto tcp from any to any port ssh 
    pass  in all 
 
在这样的条件下,block行会被检测,但永远也不会有效果,因为它后面的一行允许所有的流量通过。 
 
正确: 
 
    block in quick on fxp0 proto tcp from any to any port ssh 
    pass  in all 
 
这些规则执行的结果稍有不同,如果block行被匹配,由于quick选项的原因,数据包会被阻塞,而且剩下的规则也会被忽略。 
 
 
状态保持 
 
PF一个非常重要的功能是“状态保持”或者“状态检测”。状态检测指PF跟踪或者处理网络连接状态的能力。通过存贮每个连接的信息到一个状态表中,PF能够快速确定一个通过防火墙的数据包是否属于已经建立的连接。如果是,它会直接通过防火墙而不用再进行规则检验。 
 
状态保持有许多的优点,包括简单的规则集和优良的数据包处理性能。 
PF is able to match packets moving in either direction 
to state table entries meaning that filter rules which pass returning traffic 
don't need to be written. 并且,由于数据包匹配状态连接时不再进行规则集的匹配检测,PF用于处理这些数据包的时间大为减少。 
 
当一条规则使用了keep state选项,第一个匹配这条规则的数据包在收发双方之间建立了一个状态。现在,不仅发送者到接收者之间的数据包匹配这个状态绕过规则检验,而且接收者回复发送者的数据包也是同样的。例如: 
 
    pass out on fxp0 proto tcp from any to any keep state 
 
这允许fxp0接口上的任何TCP流量通过,并且允许返回的流量通过防火墙。状态保持是一个非常有用的特性,由于状态查询比使用规则进行数据包检验快的多,因此它可以大幅度提高防火墙的性能。 
 
状态调整选项和状态保持的功能在除了仅适用于TCP数据包以为完全相同。在使用状态调整时,输入连接的初始化序列号(ISN)是随机的,这对于保护某些选择ISN存在问题的操作系统的连接初始化非常有用。从openbsd 3.5开始,状态调整选项可以应用于包含非TCP的协议规则。 
 
对输出的TCP, UDP, ICMP数据包保持状态,并且调整TCP ISN。 
 
    pass out on fxp0 proto { tcp, udp, icmp } from any \ 
        to any modulate state 
状态保持的另一个优点是ICMP通信流量可以直接通过防火墙。例如,如果一个TCP连接使用了状态保持,当和这个TCP连接相关的ICMP数据包到来时,它会自动找到合适的状态记录,直接通过防火墙。 
 
状态记录的范围被state-policy runtime选项总体控制,也能基于单条规则由if-bound, group-bound, 和 floating state选项关键字设定。这些针对单条规则的关键字在使用时具有和state-policy选项同样的意义。例如: 
 
    pass out on fxp0 proto { tcp, udp, icmp } from any \ 
        to any modulate state (if-bound) 
 
状态规则指示为了使数据包匹配状态条目,它们必须通过fxp0网络接口传递。 
 
需要注意的是,nat,binat,rdr规则隐含在连接通过过滤规则集审核的过程中产生匹配连接的状态。 
 
UDP状态保持 
 
大家都听说过,“不能为UDP产生状态,因为UDP是无状态的协议”。确实,UDP通信会话没有状态的概念(明确的开始和结束通信),这丝毫不影响PF为UDP会话产生状态的能力。对于没有开始和结束数据包的协议,PF仅简单追踪匹配的数据部通过的时间。如果到达超时限制,状态被清除,超时的时间值可以在pf.conf配置文件中设定。 
 
TCP 标记 
 
基于标记的TCP包匹配经常被用于过滤试图打开新连接的TCP数据包。TCP标记和他们的意义如下所列: 
 
  * F : FIN – 结束; 结束会话 
  * S : SYN – 同步; 表示开始会话请求 
  * R : RST – 复位;中断一个连接 
  * P : PUSH – 推送; 数据包立即发送 
  * A : ACK – 应答 
  * U : URG – 紧急 
  * E : ECE – 显式拥塞提醒回应 
  * W : CWR – 拥塞窗口减少 
 
要使PF在规则检查过程中检查TCP标记,flag关键需按如下语法设置。 
 
    flags check/mask 
 
mask部分告诉PF仅检查指定的标记,check部分说明在数据包头中哪个标记设置为“on”才算匹配。 
 
    pass in on fxp0 proto tcp from any to any port ssh flags S/SA 
 
上面的规则通过的带SYN标记的TCP流量仅查看SYN和ACK标记。带有SYN和ECE标记的数据包会匹配上面的规则,而带有SYN和ACK的数据包或者仅带有ACK的数据包不会匹配。 
 
注意:在前面的openbsd版本中,下面的语法是支持的: 
 
    . . . flags S 
 
现在,这个不再支持,mask必须被说明。 
 
标记常常和状态保持规则联合使用来控制创建状态条目: 
 
    pass out on fxp0 proto tcp all flags S/SA keep state 
 
这条规则允许为所有输出中带SYN和ACK标记的数据包中的仅带有SYN标记的TCP数据包创建状态。 
 
大家使用标记时必须小心,理解你在做什么和为什么这样做。小心听取大家的建议,因为相当多的建议时不好的。一些人建议创建状态“只有当SYN标记设定而没有其他标记”时,这样的规则如下: 
     . . . flags S/FSRPAUEW  糟糕的主意!! 
 
这个理论是,仅为TCP开始会话时创建状态,会话会以SYN标记开始,而没有其他标记。问题在于一些站点使用ECN标记开始会话,而任何使用ECN连接你的会话都会被那样的规则拒绝。比较好的规则是: 
 
    . . . flags S/SAFR 

(待续)


----
   

[关闭][返回]