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;
}