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_sk_redirect_map()
、bpf_sk_redirect_hash()
、bpf_msg_redirect_map()
和 bpf_msg_redirect_hash()
,根据 BPF (verdict) 程序的执行结果在套接字级别应用策略。
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)
类型为 struct sock *
的套接字条目可以使用 bpf_map_lookup_elem()
辅助函数检索。
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 套接字级别实现策略的程序中。如果 sk_buff 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)
对于套接字策略,阻止对消息 msg
执行判决 BPF 程序,直到已累积了指定数量的 bytes
。
当需要在指定数量的字节之后才能分配判决时,即使数据跨越多个 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
字节偏移量。
如果类型为 BPF_PROG_TYPE_SK_MSG
的程序在 msg
上运行,它只能解析 (data
, data_end
) 指针已消耗的数据。对于 sendmsg()
钩子,这可能是第一个 scatterlist 元素。但对于依赖 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)
Sockmap 条目可以使用 bpf_map_update_elem()
函数添加或更新。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)
Sockmap 条目可以使用 bpf_map_lookup_elem()
函数检索。
注意
返回的条目是套接字 cookie 而不是套接字本身。
bpf_map_delete_elem()¶
int bpf_map_delete_elem(int fd, const void *key)
Sockmap 条目可以使用 bpf_map_delete_elem()
函数删除。
成功时返回 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;
}