Bro脚本语法2-数据类型
@(教程)[Bro]
内建类型
Name | Description |
---|---|
bool | 布尔类型 |
count, int, double | 数字类型 |
time, interval | 时间类型 |
string | 字符串 |
pattern | 正则表达式 |
port, addr, subnet | 网络类型 |
enum | 枚举(用户自定义类型) |
table, set, vector, record | 容器类型 |
function, event, hook | 可执行类型 |
file | 文件类型 (only for writing) |
opaque | 透明类型 (for some built-in functions) |
any | Any 类型 (for functions or containers) |
bool
使用 T 和 F 表示
|T| 的值是1, |F| 的值是0
int
64位有符号整数
可以用16进制表示,如 -0xFF,+0xabc123
count
64位无符号整形
double
双精度浮点数
time
绝对时间类型,
可以通过 double_to_time , current_time ,network_time 来给time类型分配值
interval
相对时间类型
string
既可以保存文本,也可以保存二进制数据
跨行注意使用换行连接符 (\ )
local orig = "0123456789";
local second_char = orig[1];
local first_two_char = orig[:2];
local cop_orig = orig[:]
pattern
用于文本搜索的正则表达式,通过括号内的斜杠创建 (/)
正则表达式规则参照flex 正则表达式
pattern支持两种匹配,精确匹配(exact)和嵌入匹配(embedded)
精确匹配使用 “==” 来关联匹配的字符
例:
/foo|bar/ =="foo"; 将返回true
/foo|bar/ == "foobar" 返回false
embedded 匹配使用 “in” 来进行匹配(!in 也支持)
/foo|bar/ in "foobar" ; 返回true
/^oob/ in "foobar"; 返回false
port
表示传输层的端口号类型
无符号整型跟上 /tcp./udp,/icmp./unknow中的一个
不同协议的端口号比较操作是
unknown < tcp < udp < icmp
比如 65535/tcp 就小于 0/udp
可以通过内建函数 get_port_transport_proto 来获得传输层协议类型
或者通过内建函数 port_to_count 获得数字类型的端口号
addr
表示IP地址的数据类型
local a: addr = 192.168.1.100
local s:subnet = 192.168.0.0/16
if(a/16 == s)
print "true";
检查一个地址是否属于某个 subnet ,可以通过in ,!in 来判断
local a: addr = 192.168.1.100;
local s: subnet = 192.168.0.0/16;
if(a in s)
print "true";
可以通过内建函数 is_v4_addr, is_v6_addr 来检查一个地址是ipv4还是ipv6
另外 hostname 也是可以使用的,但是主机名可能包含多个IP地址,所以这种情况下这个变量其实是 “set[addr]”
local a = www.google.com;
subnet
表示一段网络的数据类型
格式是addr + /
192.168.0.0/16
[fe90::]/64
enum
允许用户定义一个关联的集合
例子:
type color: enum {Red,White,Blue,};
类型名称color 和 枚举的值(Red.Blue…)都有全局的作用域
只支持”==” 和 “!=” 及“=” 操作
table
类似于map的概念,一个集合映射到另一个集合
定义table的语法:
table [ type^+] of type
索引:type^+ 可以指定一个或者多种类型,通过逗号分割,索引不能是pattern,table,set, vector, file,opaque,any
例:
global a: table[count] of string
或者可以更复杂一定
global a:table[count] of table[addr,port] of string;
table a 索引到 另一个table 再索引到一个string
初始化table
global t: table[count] of sting = {
[11] = "eleven",
[5] = "five",
};
# 通过另一个table初始化
global t2 = table{
[192.168.0.2,22/tcp] = "ssh",
[192.168.0.3,80/tcp] = "http"
};
table 结构也可以被type命名为一个类型,这可以用在当一个复杂的索引类型双关
type MyRec:record {
a:count &optional;
b:count;
}
type MyTable: table[MyRec] of strubg;
global t3 = MyTable{[[$b=5]] = "b5",[[$b=7]] = "b7" };
获取table 的元素可以通过索引值加”[ ]”的方式
print t[11];
元素成员可以通过”in” 和 “!in” 来检测
if (13 in t)
...
if ([192.168.0.2,22/tcp] in t2)
...
也可以通过索引来重新赋值
t[13] = "thirteen";
删除table 中的元素可以通过 “delete “关键字
delete t[13];
如果不存在索引为13的元素,则什么也不会发生
通过取绝对值的操作,可以获得元素的个数
|t|
这个操作可以用于for 语句中
set
和类型 table 相似,但是这个集合没有映射关系
声明语法:
set [type^+]
通过逗号分割多种类型,有效的类型不包含 pattern,table,set,vector,file,opaque,any
set 可以通过大括号列出元素值来初始化
global s: set[port] = {21/tcp,23/tcp};
global s2: set[port,string] = {[21/tcp,"ftp"],[23/tcp,"telnet"]};
也可以直接通过另一个set来创建
global s3 = set(21/tcp,23/tcp,80/tcp,442/tcp);
set 结构也可以被type命名为一个类型,这可以用在当一个复杂的索引类型双关
type MyRec: record {
a: count &optional;
b: count;
};
type MySet : set[MyRec];
global s4 = MySet([$b=1],[$b=2]);
元素成员可以通过 “in” 和 “!in” 来判断
if (21/tcp in s)
...
if ([21/tcp,"ftp"] !in s2)
...
通过关键字 add来添加set成员
add s[22/tcp];
如果成员已经存在那么什么也不会发生
通过关键字 delete 来删除一个set成员
delete s[21/tcp];
如果成员不存在,什么也不会发生;
通过取绝对值的操作,可以获得元素的个数
|s|
这个操作可以用于for 语句中
vector
同样的类似于table,但始终是通过count类型来进行索引的且始终是从零开始索引的,声明语句如下
global v: vector of string;
可以通过vector来行径构造
local v = vector("one","two","three");
vector 结构也可以被type命名为一个类型,这可以用在当一个复杂的索引类型双关
type MyRec : record {
a:count &optional;
b:count;
};
type MyVec :vector of MyRec;
global v2 = myVec([$b=1],[$b=2],[$b=3]);
通过索引来和”[ ]”来获取元素
print v[2];
通过索引来直接赋值
v[3] = "four";
通过取绝对值的操作,可以获得元素的个数
|v|
如果vectors的数据是整数(int 或 count),则支持直接进行“++” , “–”才操作;
如果是数字型(int ,count ,double),则可以支持“+”,“-”,”*”,”/”,“%”的操作
但是这些操作需要有相同的元素个数
如果是布尔型(bool),则可以直接进行“&&”,“||”的操作
reocrd
record类型是数据的集合,所有的数据都具有名称和值两个域,他们不需要具有相同的数据类型,类型也没有限制(有点像结构体),例:
type MyRecordType:record {
c:count;
s:string &optional;
};
records 类型可以通过三种方式进行初始化或者赋值,所有没有 &optinal 或者 &default属性的元素必须被明确赋值,例:
local r:MyRecordType = record($c = 7);
因为record类型一般通过type定义,所以可以使用更可读的方式进行声明和赋值
local r = MyRecordType($c = 42);
第三种方式像这样:
local r:MyRecordType = [$c = 13,$s="thirteen"];
获取元素域通过 “$” 符号来操作,如下
local r: MyRecordType;
r$c = 12;
测试具有 “&optional “属性的域是否被赋值,可以通过?$ 操作符返回一个bool类型来进行判断(T:有,F:没有)
if (r ?$ s)
...
function
在bro 中,function 类型通过如下方法声明:
function(argument *): type
参数(argument )可以为空或者是通过逗号来分割,返回值(type) 是可选的,例子:
global greeting :function(name: string):string;
这里 greeting 是函数的标识,函数体还未定义,函数的函数体甚至可以在不同的时间有不同的函数体,定义一个包含函数体的函数:
function greeting(name: string):string
{
return "Heloow," + name;
}
通过这种方式定义函数,就不需要像上面那样对函数进行声明,如果要声明的话,那函数类型返回值,参数列表必须完全一致
定义一个没有参数没有返回值的函数:
function my_func()
{
print "my_func";
}
赋值一个匿名函数
greeting = function(name:string):string {return "Hi, "+ name};
调用函数:
print greeting("Dave");
默认参数的参数放在参数列表的最后:
global foo:function(s: string,t :string &default="abc",u: count &default =0);
如果一个函数之前被声明为使用默认参数,当定义函数体的时候这些默认参数可以不写,在调用的时候任然有效
function foo(s: string,t:string.u:count)
{
print s,t,u;
}
调用该函数可以省略从参数列表中的默认值:
foo("test");
event
Event 处理器的语法和 function 很接近,不同的两点是event没有返回值且你不能调用他,例:
event my_event(r:bool,s:string)
{
print "my_event",r,s;
}
event handler 不能从脚本中被直接调用,当event 被一下三种方法触发将会执行event handler的函数体:
* 来自event engine
当event engine 检测到一个你定义的evnet handler 相关的event时,将这个事件放入event的处理队列,一旦event engine 处理完当前的数据包,将会调用队列里的event handler
* 来自脚本中关键字event
立即触发一个event handler :
event password_exposed(user,password);
假设 password_exposed 之前已经被声明且定义的event handler 具有相同类型的参数
* 脚本中通过 schedule 来传递
将会延时触发 event handler直到未来的某个时间,例:
schedule 5 secs {passwod_exposed(user,password)};
不同的event handler 处理体可以用同一个envent 标识,每个evnet handler 实体将会顺序执行,通过关键字 &priority 可以对event handler 进行排序
hook
Hook 是另一种具备 function 和 evnet 特点的 “function”
具有相同标识符的 hook 可以通过&priority 属性来按照优先级执行,调用的时候他们更像是function,因为和envent不同,他们不能通过scheduled 放入event队列而是被立即执行,同时,hook的一个独有特点是hook的handler 函数体可以通过”break ” 关键字让剩余的处理程序的语句中断,(return 是在函数体结束的时候退出)
hook的声明方式
hook (argument *)
argument 可以为空或者是逗号分割的列表,例子
global myhook: hook(s:string)
这里只是定义了hook的标识,没有handler body,我们可以定义多个handler bodies:
hook myhook(s:string) &porority=10
{
print "porority 10 myhook handler",s;
s = "bye";
}
hook myhook(s:string)
{
print "break out of myhook handling ",s;
}
hook myhook(s: string) &porority=-5
{
print "not going to happend",s;
}
要立即执行 hook 处理,调用方式和function 类似,只不过要通过hook 关键字来调用:
hook myhook("hi");
或者
if(hook myhook("hi"))
print "all handlers ran";
输出如下:
priority 10 myhook handler,hi
break out of myhook handling ,bye
注意到参数可以被先执行的hook 修改
hook 执行的返回值是 bool型的(T or F),T 说明所有的hook都被执行了,F指示只有部分hook被执行了,一般F是break 语句执行的结果
file
Bro 支持写文件,但是不支持读文件(读文件参考 Input Framework),通过内建函数open 或者 open_for_append来打开文件,通过close 来关闭文件。声明,打开,写文件,关闭的操作示例:
local f = open("myfile");
print f, "hello world";
close(f);
通过这种方式写入日志是不推荐的,更好的日志支持参考 Logging Framework
opaque
被故意隐藏的数据类型,但是这些数据可以被传递给一些内建的函数,这些数据是内部的/隐藏的资源,通过一些修饰词来对这些不透明的数据类型进行区分,比如 “opaque of md5”,”opaque of sha1”,
内部hash函数的 类型 示例:
local handle = md5_hash_init();
md5_hash_update(handle,"test");
md5_hash_update(handle,"testing");
print md5_hash_finish(handle);
这里展示了opaque类型用于md5的计算,但是这个数据类型的细节我们不清楚,也不需要了解
any
用来绕过强类型,比如一个函数(function)可以通过定义参数类型为any 当这个参数可以为多种类型的时候,这种数据类型只支持赋值操作,
注意用户不被期望使用这种类型,这种类型主要是用于内建函数的
void
一种内部的bro 类型(它不是Bro脚本的关键字),代表没有返回类型的函数(function).