BPF_MAP_TYPE_SOCKMAP 和 BPF_MAP_TYPE_SOCKHASH¶
注意
BPF_MAP_TYPE_SOCKMAP
在内核版本 4.14 中引入BPF_MAP_TYPE_SOCKHASH
在内核版本 4.18 中引入
BPF_MAP_TYPE_SOCKMAP
和 BPF_MAP_TYPE_SOCKHASH
映射可用于在套接字之间重定向 skb,或在套接字级别根据 BPF (裁决) 程序的结果应用策略,借助 BPF 辅助函数 bpf_sk_redirect_map()
, bpf_sk_redirect_hash()
, bpf_msg_redirect_map()
和 bpf_msg_redirect_hash()
。
BPF_MAP_TYPE_SOCKMAP
由一个数组支持,该数组使用整数键作为索引来查找对 struct sock
的引用。映射值是套接字描述符。类似地,BPF_MAP_TYPE_SOCKHASH
是一个哈希支持的 BPF 映射,它通过套接字描述符保存对套接字的引用。
注意
值类型是 __u32 或 __u64;后者 (__u64) 用于支持将套接字 cookie 返回给用户空间。将映射保存的 struct sock *
返回给用户空间既不安全也无用。
这些映射可以附加 BPF 程序,特别是解析器程序和裁决程序。解析器程序确定已解析多少数据,因此确定需要排队多少数据才能得出裁决。裁决程序本质上是重定向程序,可以返回裁决结果 __SK_DROP
、__SK_PASS
或 __SK_REDIRECT
。
当套接字插入到这些映射之一时,其套接字回调将被替换,并且会附加一个 struct sk_psock
。此外,此 sk_psock
还会继承附加到映射的程序。
一个套接字对象可能存在于多个映射中,但只能继承单个解析器或裁决程序。如果将套接字对象添加到映射会导致存在多个解析器程序,则更新将返回 EBUSY 错误。
附加到这些映射的受支持程序是
struct sk_psock_progs {
struct bpf_prog *msg_parser;
struct bpf_prog *stream_parser;
struct bpf_prog *stream_verdict;
struct bpf_prog *skb_verdict;
};
注意
不允许用户将 stream_verdict
和 skb_verdict
程序附加到同一映射。
映射程序的附加类型是
msg_parser
程序 -BPF_SK_MSG_VERDICT
。stream_parser
程序 -BPF_SK_SKB_STREAM_PARSER
。stream_verdict
程序 -BPF_SK_SKB_STREAM_VERDICT
。skb_verdict
程序 -BPF_SK_SKB_VERDICT
。
还有一些额外的辅助函数可用于解析器和裁决程序:bpf_msg_apply_bytes()
和 bpf_msg_cork_bytes()
。借助 bpf_msg_apply_bytes()
,BPF 程序可以告知基础架构给定的裁决应应用于多少字节。辅助函数 bpf_msg_cork_bytes()
处理不同的情况,即 BPF 程序在收到更多字节之前无法对消息做出裁决,并且该程序不想在已知消息良好之前转发数据包。
最后,辅助函数 bpf_msg_pull_data()
和 bpf_msg_push_data()
可用于 BPF_PROG_TYPE_SK_MSG
BPF 程序,以提取数据并将起始指针和结束指针设置为给定值,或将元数据添加到 struct sk_msg_buff *msg
。
所有这些辅助函数将在下面更详细地描述。
用法¶
内核 BPF¶
bpf_msg_redirect_map()¶
long bpf_msg_redirect_map(struct sk_msg_buff *msg, struct bpf_map *map, u32 key, u64 flags)
此辅助函数用于在套接字级别实施策略的程序中。如果允许消息 msg
通过(即,如果裁决 BPF 程序返回 SK_PASS
),则将其重定向到 map
(类型为 BPF_MAP_TYPE_SOCKMAP
)在索引 key
处引用的套接字。入口和出口接口都可用于重定向。flags
中的 BPF_F_INGRESS
值用于选择入口路径,否则选择出口路径。这是目前唯一支持的标志。
成功时返回 SK_PASS
,错误时返回 SK_DROP
。
bpf_sk_redirect_map()¶
long bpf_sk_redirect_map(struct sk_buff *skb, struct bpf_map *map, u32 key u64 flags)
将数据包重定向到 map
(类型为 BPF_MAP_TYPE_SOCKMAP
)在索引 key
处引用的套接字。入口和出口接口都可用于重定向。flags
中的 BPF_F_INGRESS
值用于选择入口路径,否则选择出口路径。这是目前唯一支持的标志。
成功时返回 SK_PASS
,错误时返回 SK_DROP
。
bpf_map_lookup_elem()¶
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
可以使用 bpf_map_lookup_elem()
辅助函数检索类型为 struct sock *
的套接字条目。
bpf_sock_map_update()¶
long bpf_sock_map_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
添加或更新引用套接字的 map
中的条目。skops
用作与 key
关联的条目的新值。flags
参数可以是以下值之一
BPF_ANY
:创建新元素或更新现有元素。BPF_NOEXIST
:仅当元素不存在时才创建新元素。BPF_EXIST
:更新现有元素。
如果 map
具有 BPF 程序(解析器和裁决),则这些程序将由正在添加的套接字继承。如果套接字已附加到 BPF 程序,则会导致错误。
成功时返回 0,失败时返回负错误。
bpf_sock_hash_update()¶
long bpf_sock_hash_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
添加或更新引用套接字的 sockhash map
中的条目。skops
用作与 key
关联的条目的新值。
flags
参数可以是以下值之一
BPF_ANY
:创建新元素或更新现有元素。BPF_NOEXIST
:仅当元素不存在时才创建新元素。BPF_EXIST
:更新现有元素。
如果 map
具有 BPF 程序(解析器和裁决),则这些程序将由正在添加的套接字继承。如果套接字已附加到 BPF 程序,则会导致错误。
成功时返回 0,失败时返回负错误。
bpf_msg_redirect_hash()¶
long bpf_msg_redirect_hash(struct sk_msg_buff *msg, struct bpf_map *map, void *key, u64 flags)
此辅助函数用于在套接字级别实施策略的程序中。如果允许消息 msg
通过(即,如果裁决 BPF 程序返回 SK_PASS
),则使用哈希 key
将其重定向到 map
(类型为 BPF_MAP_TYPE_SOCKHASH
)引用的套接字。入口和出口接口都可用于重定向。flags
中的 BPF_F_INGRESS
值用于选择入口路径,否则选择出口路径。这是目前唯一支持的标志。
成功时返回 SK_PASS
,错误时返回 SK_DROP
。
bpf_sk_redirect_hash()¶
long bpf_sk_redirect_hash(struct sk_buff *skb, struct bpf_map *map, void *key, u64 flags)
此辅助函数用于在套接字 skb 级别实现策略的程序中。如果允许 skb
通过(即,如果判决 BPF 程序返回 SK_PASS
),则使用哈希 key
将其重定向到由 map
引用的套接字(类型为 BPF_MAP_TYPE_SOCKHASH
)。入口和出口接口都可用于重定向。 flags
中的 BPF_F_INGRESS
值用于选择入口路径,否则选择出口路径。这是目前唯一支持的标志。
成功时返回 SK_PASS
,错误时返回 SK_DROP
。
bpf_msg_apply_bytes() ¶
long bpf_msg_apply_bytes(struct sk_msg_buff *msg, u32 bytes)
对于套接字策略,将 BPF 程序的判决应用于消息 msg
的下一个(bytes
的数量)。例如,此辅助函数可用于以下情况:
单个
sendmsg()
或sendfile()
系统调用包含多个 BPF 程序应该读取并对其应用判决的逻辑消息。BPF 程序只关心读取
msg
的前bytes
。如果消息具有较大的有效负载,那么即使判决已经已知,为所有字节重复设置和调用 BPF 程序也会产生不必要的开销。
返回 0
bpf_msg_cork_bytes() ¶
long bpf_msg_cork_bytes(struct sk_msg_buff *msg, u32 bytes)
对于套接字策略,防止在累积了 bytes
数量之前执行消息 msg
的判决 BPF 程序。
当需要在分配判决之前需要特定数量的字节时,即使数据跨越多个 sendmsg()
或 sendfile()
调用,也可以使用此功能。
返回 0
bpf_msg_pull_data() ¶
long bpf_msg_pull_data(struct sk_msg_buff *msg, u32 start, u32 end, u64 flags)
对于套接字策略,从用户空间为 msg
拉入非线性数据,并将指针 msg->data
和 msg->data_end
分别设置为 msg
的 start
和 end
字节偏移量。
如果在 msg
上运行 BPF_PROG_TYPE_SK_MSG
类型的程序,它只能解析 (data
, data_end
) 指针已经消耗的数据。对于 sendmsg()
钩子,这很可能是第一个散列表元素。但是对于依赖 MSG_SPLICE_PAGES 的调用(例如,sendfile()
),这将是范围 (0, 0),因为数据与用户空间共享,默认的目标是避免允许用户空间在 BPF 判决正在决定时(或之后)修改数据。此辅助函数可用于拉入数据,并将起始和结束指针设置为给定值。如果需要,将复制数据(即,如果数据不是线性的,并且起始和结束指针未指向同一块)。
对此辅助函数的调用可能会更改底层数据包缓冲区。因此,在加载时,验证器先前对指针所做的所有检查都将失效,如果辅助函数与直接数据包访问结合使用,则必须再次执行这些检查。
flags
的所有值都保留供将来使用,必须保留为零。
成功时返回 0,失败时返回负错误。
bpf_map_lookup_elem() ¶
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
在 sockmap 或 sockhash 映射中查找套接字条目。
返回与 key
关联的套接字条目,如果未找到条目,则返回 NULL。
bpf_map_update_elem() ¶
long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)
在 sockmap 或 sockhash 中添加或更新套接字条目。
flags 参数可以是以下之一
BPF_ANY: 创建新元素或更新现有元素。
BPF_NOEXIST: 仅在元素不存在时才创建新元素。
BPF_EXIST: 更新现有元素。
成功时返回 0,失败时返回负错误。
bpf_map_delete_elem() ¶
long bpf_map_delete_elem(struct bpf_map *map, const void *key)
从 sockmap 或 sockhash 中删除套接字条目。
成功时返回 0,失败时返回负错误。
用户空间 ¶
bpf_map_update_elem() ¶
int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags)
可以使用 bpf_map_update_elem()
函数添加或更新 Sockmap 条目。 key
参数是 sockmap 数组的索引值。 value
参数是该套接字的 FD 值。
在底层,sockmap 更新函数使用套接字 FD 值来检索关联的套接字及其附加的 psock。
flags 参数可以是以下之一
BPF_ANY: 创建新元素或更新现有元素。
BPF_NOEXIST: 仅在元素不存在时才创建新元素。
BPF_EXIST: 更新现有元素。
bpf_map_lookup_elem() ¶
int bpf_map_lookup_elem(int fd, const void *key, void *value)
可以使用 bpf_map_lookup_elem()
函数检索 Sockmap 条目。
注意
返回的条目是套接字 cookie 而不是套接字本身。
bpf_map_delete_elem() ¶
int bpf_map_delete_elem(int fd, const void *key)
可以使用 bpf_map_delete_elem()
函数删除 Sockmap 条目。
成功时返回 0,失败时返回负错误。
示例 ¶
内核 BPF ¶
可以在以下位置找到 sockmap API 的几个使用示例
以下代码片段显示了如何声明 sockmap。
struct {
__uint(type, BPF_MAP_TYPE_SOCKMAP);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u64);
} sock_map_rx SEC(".maps");
以下代码片段显示了示例解析器程序。
SEC("sk_skb/stream_parser")
int bpf_prog_parser(struct __sk_buff *skb)
{
return skb->len;
}
以下代码片段显示了一个简单的判决程序,该程序与 sockmap 交互,以基于本地端口将流量重定向到另一个套接字。
SEC("sk_skb/stream_verdict")
int bpf_prog_verdict(struct __sk_buff *skb)
{
__u32 lport = skb->local_port;
__u32 idx = 0;
if (lport == 10000)
return bpf_sk_redirect_map(skb, &sock_map_rx, idx, 0);
return SK_PASS;
}
以下代码片段显示了如何声明 sockhash 映射。
struct socket_key {
__u32 src_ip;
__u32 dst_ip;
__u32 src_port;
__u32 dst_port;
};
struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 1);
__type(key, struct socket_key);
__type(value, __u64);
} sock_hash_rx SEC(".maps");
以下代码片段显示了一个简单的判决程序,该程序与 sockhash 交互,以基于某些 skb 参数的哈希值将流量重定向到另一个套接字。
static inline
void extract_socket_key(struct __sk_buff *skb, struct socket_key *key)
{
key->src_ip = skb->remote_ip4;
key->dst_ip = skb->local_ip4;
key->src_port = skb->remote_port >> 16;
key->dst_port = (bpf_htonl(skb->local_port)) >> 16;
}
SEC("sk_skb/stream_verdict")
int bpf_prog_verdict(struct __sk_buff *skb)
{
struct socket_key key;
extract_socket_key(skb, &key);
return bpf_sk_redirect_hash(skb, &sock_hash_rx, &key, 0);
}
用户空间 ¶
可以在以下位置找到 sockmap API 的几个使用示例
以下代码示例显示了如何创建 sockmap、附加解析器和判决程序,以及如何添加套接字条目。
int create_sample_sockmap(int sock, int parse_prog_fd, int verdict_prog_fd)
{
int index = 0;
int map, err;
map = bpf_map_create(BPF_MAP_TYPE_SOCKMAP, NULL, sizeof(int), sizeof(int), 1, NULL);
if (map < 0) {
fprintf(stderr, "Failed to create sockmap: %s\n", strerror(errno));
return -1;
}
err = bpf_prog_attach(parse_prog_fd, map, BPF_SK_SKB_STREAM_PARSER, 0);
if (err){
fprintf(stderr, "Failed to attach_parser_prog_to_map: %s\n", strerror(errno));
goto out;
}
err = bpf_prog_attach(verdict_prog_fd, map, BPF_SK_SKB_STREAM_VERDICT, 0);
if (err){
fprintf(stderr, "Failed to attach_verdict_prog_to_map: %s\n", strerror(errno));
goto out;
}
err = bpf_map_update_elem(map, &index, &sock, BPF_NOEXIST);
if (err) {
fprintf(stderr, "Failed to update sockmap: %s\n", strerror(errno));
goto out;
}
out:
close(map);
return err;
}