blob: 1c27f9e265c4bb400261ebed0d1042f109e0571e [file] [log] [blame]
Joel Fernandesd76a2002018-10-16 13:19:58 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "LibBpfLoader"
18
19#include <errno.h>
20#include <linux/bpf.h>
21#include <linux/elf.h>
22#include <log/log.h>
23#include <stdint.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
Maciej Żenczykowski83f29772020-01-27 03:11:51 -080027#include <sys/stat.h>
Joel Fernandesd76a2002018-10-16 13:19:58 -070028#include <sys/utsname.h>
29#include <unistd.h>
30
Maciej Żenczykowski730a3862020-01-27 01:10:48 -080031#include "../progs/include/bpf_map_def.h"
Joel Fernandesd76a2002018-10-16 13:19:58 -070032#include "LoaderUtils.h"
33#include "include/libbpf_android.h"
34
35#include <cstdlib>
36#include <fstream>
37#include <iostream>
38#include <string>
Christopher Ferrisc151c672019-02-01 15:31:26 -080039#include <vector>
Joel Fernandesd76a2002018-10-16 13:19:58 -070040
Steven Moreland4891e612020-01-10 15:35:52 -080041#include <android-base/properties.h>
Joel Fernandesd76a2002018-10-16 13:19:58 -070042#include <android-base/strings.h>
Connor O'Brien8d49fc72019-10-24 18:23:49 -070043#include <android-base/unique_fd.h>
Joel Fernandesd76a2002018-10-16 13:19:58 -070044
45#define BPF_FS_PATH "/sys/fs/bpf/"
46
47// Size of the BPF log buffer for verifier logging
48#define BPF_LOAD_LOG_SZ 0x1ffff
49
50using android::base::StartsWith;
Connor O'Brien8d49fc72019-10-24 18:23:49 -070051using android::base::unique_fd;
Joel Fernandesd76a2002018-10-16 13:19:58 -070052using std::ifstream;
53using std::ios;
Christopher Ferrisc151c672019-02-01 15:31:26 -080054using std::string;
Joel Fernandesd76a2002018-10-16 13:19:58 -070055using std::vector;
56
57namespace android {
58namespace bpf {
59
60typedef struct {
61 const char* name;
62 enum bpf_prog_type type;
63} sectionType;
64
65/*
66 * Map section name prefixes to program types, the section name will be:
67 * SEC(<prefix>/<name-of-program>)
68 * For example:
69 * SEC("tracepoint/sched_switch_func") where sched_switch_funcs
70 * is the name of the program, and tracepoint is the type.
71 */
72sectionType sectionNameTypes[] = {
Chenbo Feng5aee2f12018-12-26 16:14:05 -080073 {"kprobe", BPF_PROG_TYPE_KPROBE},
74 {"tracepoint", BPF_PROG_TYPE_TRACEPOINT},
75 {"skfilter", BPF_PROG_TYPE_SOCKET_FILTER},
76 {"cgroupskb", BPF_PROG_TYPE_CGROUP_SKB},
77 {"schedcls", BPF_PROG_TYPE_SCHED_CLS},
78 {"cgroupsock", BPF_PROG_TYPE_CGROUP_SOCK},
Joel Fernandesd76a2002018-10-16 13:19:58 -070079
80 /* End of table */
Chenbo Feng5aee2f12018-12-26 16:14:05 -080081 {"END", BPF_PROG_TYPE_UNSPEC},
Joel Fernandesd76a2002018-10-16 13:19:58 -070082};
83
84typedef struct {
85 enum bpf_prog_type type;
86 string name;
87 vector<char> data;
88 vector<char> rel_data;
89
Connor O'Brien8d49fc72019-10-24 18:23:49 -070090 unique_fd prog_fd; /* fd after loading */
Joel Fernandesd76a2002018-10-16 13:19:58 -070091} codeSection;
92
Joel Fernandesd76a2002018-10-16 13:19:58 -070093static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
94 elfFile.seekg(0);
95 if (elfFile.fail()) return -1;
96
97 if (!elfFile.read((char*)eh, sizeof(*eh))) return -1;
98
99 return 0;
100}
101
102/* Reads all section header tables into an Shdr array */
103static int readSectionHeadersAll(ifstream& elfFile, vector<Elf64_Shdr>& shTable) {
104 Elf64_Ehdr eh;
105 int ret = 0;
106
107 ret = readElfHeader(elfFile, &eh);
108 if (ret) return ret;
109
110 elfFile.seekg(eh.e_shoff);
111 if (elfFile.fail()) return -1;
112
113 /* Read shdr table entries */
114 shTable.resize(eh.e_shnum);
115
116 if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
117
118 return 0;
119}
120
121/* Read a section by its index - for ex to get sec hdr strtab blob */
122static int readSectionByIdx(ifstream& elfFile, int id, vector<char>& sec) {
123 vector<Elf64_Shdr> shTable;
124 int entries, ret = 0;
125
126 ret = readSectionHeadersAll(elfFile, shTable);
127 if (ret) return ret;
128 entries = shTable.size();
129
130 elfFile.seekg(shTable[id].sh_offset);
131 if (elfFile.fail()) return -1;
132
133 sec.resize(shTable[id].sh_size);
134 if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1;
135
136 return 0;
137}
138
139/* Read whole section header string table */
140static int readSectionHeaderStrtab(ifstream& elfFile, vector<char>& strtab) {
141 Elf64_Ehdr eh;
142 int ret = 0;
143
144 ret = readElfHeader(elfFile, &eh);
145 if (ret) return ret;
146
147 ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab);
148 if (ret) return ret;
149
150 return 0;
151}
152
153/* Get name from offset in strtab */
154static int getSymName(ifstream& elfFile, int nameOff, string& name) {
155 int ret;
156 vector<char> secStrTab;
157
158 ret = readSectionHeaderStrtab(elfFile, secStrTab);
159 if (ret) return ret;
160
161 if (nameOff >= (int)secStrTab.size()) return -1;
162
163 name = string((char*)secStrTab.data() + nameOff);
164 return 0;
165}
166
167/* Reads a full section by name - example to get the GPL license */
168static int readSectionByName(const char* name, ifstream& elfFile, vector<char>& data) {
169 vector<char> secStrTab;
170 vector<Elf64_Shdr> shTable;
171 int ret;
172
173 ret = readSectionHeadersAll(elfFile, shTable);
174 if (ret) return ret;
175
176 ret = readSectionHeaderStrtab(elfFile, secStrTab);
177 if (ret) return ret;
178
179 for (int i = 0; i < (int)shTable.size(); i++) {
180 char* secname = secStrTab.data() + shTable[i].sh_name;
181 if (!secname) continue;
182
183 if (!strcmp(secname, name)) {
184 vector<char> dataTmp;
185 dataTmp.resize(shTable[i].sh_size);
186
187 elfFile.seekg(shTable[i].sh_offset);
188 if (elfFile.fail()) return -1;
189
190 if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
191
192 data = dataTmp;
193 return 0;
194 }
195 }
196 return -2;
197}
198
199static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
200 int ret;
201 vector<Elf64_Shdr> shTable;
202
203 ret = readSectionHeadersAll(elfFile, shTable);
204 if (ret) return ret;
205
206 for (int i = 0; i < (int)shTable.size(); i++) {
207 if ((int)shTable[i].sh_type != type) continue;
208
209 vector<char> dataTmp;
210 dataTmp.resize(shTable[i].sh_size);
211
212 elfFile.seekg(shTable[i].sh_offset);
213 if (elfFile.fail()) return -1;
214
215 if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
216
217 data = dataTmp;
218 return 0;
219 }
220 return -2;
221}
222
223static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
224 return (a.st_value < b.st_value);
225}
226
227static int readSymTab(ifstream& elfFile, int sort, vector<Elf64_Sym>& data) {
228 int ret, numElems;
229 Elf64_Sym* buf;
230 vector<char> secData;
231
232 ret = readSectionByType(elfFile, SHT_SYMTAB, secData);
233 if (ret) return ret;
234
235 buf = (Elf64_Sym*)secData.data();
236 numElems = (secData.size() / sizeof(Elf64_Sym));
237 data.assign(buf, buf + numElems);
238
239 if (sort) std::sort(data.begin(), data.end(), symCompare);
240 return 0;
241}
242
243static enum bpf_prog_type getSectionType(string& name) {
244 for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++)
245 if (StartsWith(name, sectionNameTypes[i].name)) return sectionNameTypes[i].type;
246
247 return BPF_PROG_TYPE_UNSPEC;
248}
249
250/* If ever needed
251static string getSectionName(enum bpf_prog_type type)
252{
253 for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++)
254 if (sectionNameTypes[i].type == type)
255 return std::string(sectionNameTypes[i].name);
256
257 return NULL;
258}
259*/
260
261static bool isRelSection(codeSection& cs, string& name) {
262 for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++) {
263 sectionType st = sectionNameTypes[i];
264
265 if (st.type != cs.type) continue;
266
267 if (StartsWith(name, std::string(".rel") + st.name + "/"))
268 return true;
269 else
270 return false;
271 }
272 return false;
273}
274
275/* Read a section by its index - for ex to get sec hdr strtab blob */
276static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
277 vector<Elf64_Shdr> shTable;
278 int entries, ret = 0;
279
280 ret = readSectionHeadersAll(elfFile, shTable);
281 if (ret) return ret;
282 entries = shTable.size();
283
284 for (int i = 0; i < entries; i++) {
285 string name;
286 codeSection cs_temp;
287 cs_temp.type = BPF_PROG_TYPE_UNSPEC;
288
289 ret = getSymName(elfFile, shTable[i].sh_name, name);
290 if (ret) return ret;
291
292 enum bpf_prog_type ptype = getSectionType(name);
293 if (ptype != BPF_PROG_TYPE_UNSPEC) {
294 deslash(name);
295 cs_temp.type = ptype;
296 cs_temp.name = name;
297
298 ret = readSectionByIdx(elfFile, i, cs_temp.data);
299 if (ret) return ret;
300 ALOGD("Loaded code section %d (%s)\n", i, name.c_str());
301 }
302
303 /* Check for rel section */
304 if (cs_temp.data.size() > 0 && i < entries) {
305 ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
306 if (ret) return ret;
307
308 if (isRelSection(cs_temp, name)) {
309 ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
310 if (ret) return ret;
311 ALOGD("Loaded relo section %d (%s)\n", i, name.c_str());
312 }
313 }
314
315 if (cs_temp.data.size() > 0) {
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700316 cs.push_back(std::move(cs_temp));
Joel Fernandesd76a2002018-10-16 13:19:58 -0700317 ALOGD("Adding section %d to cs list\n", i);
318 }
319 }
320 return 0;
321}
322
323static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
324 vector<Elf64_Sym> symtab;
325 int ret = 0;
326
327 ret = readSymTab(elfFile, 0 /* !sort */, symtab);
328 if (ret) return ret;
329
330 if (index >= (int)symtab.size()) return -1;
331
332 return getSymName(elfFile, symtab[index].st_name, name);
333}
334
335static int getMapNames(ifstream& elfFile, vector<string>& names) {
336 int ret;
337 string mapName;
338 vector<Elf64_Sym> symtab;
339 vector<Elf64_Shdr> shTable;
340
341 ret = readSymTab(elfFile, 1 /* sort */, symtab);
342 if (ret) return ret;
343
344 /* Get index of maps section */
345 ret = readSectionHeadersAll(elfFile, shTable);
346 if (ret) return ret;
347
348 int maps_idx = -1;
349 for (int i = 0; i < (int)shTable.size(); i++) {
350 ret = getSymName(elfFile, shTable[i].sh_name, mapName);
351 if (ret) return ret;
352
353 if (!mapName.compare("maps")) {
354 maps_idx = i;
355 break;
356 }
357 }
358
359 /* No maps found */
360 if (maps_idx == -1) {
361 ALOGE("No maps could be found in elf object\n");
362 return -1;
363 }
364
365 for (int i = 0; i < (int)symtab.size(); i++) {
366 if (symtab[i].st_shndx == maps_idx) {
367 string s;
368 ret = getSymName(elfFile, symtab[i].st_name, s);
369 if (ret) return ret;
370 names.push_back(s);
371 }
372 }
373
374 return 0;
375}
376
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700377static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds) {
378 int ret;
Joel Fernandesd76a2002018-10-16 13:19:58 -0700379 vector<char> mdData;
380 vector<struct bpf_map_def> md;
381 vector<string> mapNames;
382 string fname = pathToFilename(string(elfPath), true);
383
384 ret = readSectionByName("maps", elfFile, mdData);
Steven Morelandc0905b42019-12-12 14:21:20 -0800385 if (ret == -2) return 0; // no maps to read
Joel Fernandesd76a2002018-10-16 13:19:58 -0700386 if (ret) return ret;
387 md.resize(mdData.size() / sizeof(struct bpf_map_def));
388 memcpy(md.data(), mdData.data(), mdData.size());
389
390 ret = getMapNames(elfFile, mapNames);
391 if (ret) return ret;
392
Joel Fernandesd76a2002018-10-16 13:19:58 -0700393 for (int i = 0; i < (int)mapNames.size(); i++) {
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700394 unique_fd fd;
Joel Fernandesd76a2002018-10-16 13:19:58 -0700395 // Format of pin location is /sys/fs/bpf/map_<filename>_<mapname>
396 string mapPinLoc;
397 bool reuse = false;
398
399 mapPinLoc = string(BPF_FS_PATH) + "map_" + fname + "_" + string(mapNames[i]);
400 if (access(mapPinLoc.c_str(), F_OK) == 0) {
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700401 fd.reset(bpf_obj_get(mapPinLoc.c_str()));
402 ALOGD("bpf_create_map reusing map %s, ret: %d\n", mapNames[i].c_str(), fd.get());
Joel Fernandesd76a2002018-10-16 13:19:58 -0700403 reuse = true;
404 } else {
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700405 fd.reset(bpf_create_map(md[i].type, mapNames[i].c_str(), md[i].key_size, md[i].value_size,
406 md[i].max_entries, md[i].map_flags));
407 ALOGD("bpf_create_map name %s, ret: %d\n", mapNames[i].c_str(), fd.get());
Joel Fernandesd76a2002018-10-16 13:19:58 -0700408 }
409
410 if (fd < 0) return fd;
411 if (fd == 0) return -EINVAL;
412
413 if (!reuse) {
414 ret = bpf_obj_pin(fd, mapPinLoc.c_str());
Maciej Żenczykowski83f29772020-01-27 03:11:51 -0800415 if (ret) return -errno;
416 ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
417 if (ret) return -errno;
418 ret = chmod(mapPinLoc.c_str(), md[i].mode);
419 if (ret) return -errno;
Joel Fernandesd76a2002018-10-16 13:19:58 -0700420 }
421
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700422 mapFds.push_back(std::move(fd));
Joel Fernandesd76a2002018-10-16 13:19:58 -0700423 }
424
425 return ret;
426}
427
428/* For debugging, dump all instructions */
429static void dumpIns(char* ins, int size) {
430 for (int row = 0; row < size / 8; row++) {
431 ALOGE("%d: ", row);
432 for (int j = 0; j < 8; j++) {
433 ALOGE("%3x ", ins[(row * 8) + j]);
434 }
435 ALOGE("\n");
436 }
437}
438
439/* For debugging, dump all code sections from cs list */
440static void dumpAllCs(vector<codeSection>& cs) {
441 for (int i = 0; i < (int)cs.size(); i++) {
442 ALOGE("Dumping cs %d, name %s\n", int(i), cs[i].name.c_str());
443 dumpIns((char*)cs[i].data.data(), cs[i].data.size());
444 ALOGE("-----------\n");
445 }
446}
447
448static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
449 int insnIndex;
450 struct bpf_insn *insn, *insns;
451
452 insns = (struct bpf_insn*)(insnsPtr);
453
454 insnIndex = offset / sizeof(struct bpf_insn);
455 insn = &insns[insnIndex];
456
457 ALOGD(
458 "applying relo to instruction at byte offset: %d, \
459 insn offset %d , insn %lx\n",
460 (int)offset, (int)insnIndex, *(unsigned long*)insn);
461
462 if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
463 ALOGE("Dumping all instructions till ins %d\n", insnIndex);
464 ALOGE("invalid relo for insn %d: code 0x%x\n", insnIndex, insn->code);
465 dumpIns((char*)insnsPtr, (insnIndex + 3) * 8);
466 return;
467 }
468
469 insn->imm = fd;
470 insn->src_reg = BPF_PSEUDO_MAP_FD;
471}
472
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700473static void applyMapRelo(ifstream& elfFile, vector<unique_fd> &mapFds, vector<codeSection>& cs) {
Joel Fernandesd76a2002018-10-16 13:19:58 -0700474 vector<string> mapNames;
475
476 int ret = getMapNames(elfFile, mapNames);
477 if (ret) return;
478
479 for (int k = 0; k != (int)cs.size(); k++) {
480 Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
481 int n_rel = cs[k].rel_data.size() / sizeof(*rel);
482
483 for (int i = 0; i < n_rel; i++) {
484 int symIndex = ELF64_R_SYM(rel[i].r_info);
485 string symName;
486
487 ret = getSymNameByIdx(elfFile, symIndex, symName);
488 if (ret) return;
489
490 /* Find the map fd and apply relo */
491 for (int j = 0; j < (int)mapNames.size(); j++) {
492 if (!mapNames[j].compare(symName)) {
493 applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]);
494 break;
495 }
496 }
497 }
498 }
499}
500
Christopher Ferrisc151c672019-02-01 15:31:26 -0800501static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license) {
Joel Fernandesd76a2002018-10-16 13:19:58 -0700502 int ret, fd, kvers;
503
504 if ((kvers = getMachineKvers()) < 0) return -1;
505
506 string fname = pathToFilename(string(elfPath), true);
507
508 for (int i = 0; i < (int)cs.size(); i++) {
509 string progPinLoc;
510 bool reuse = false;
511
512 // Format of pin location is
513 // /sys/fs/bpf/prog_<filename>_<mapname>
514 progPinLoc = string(BPF_FS_PATH) + "prog_" + fname + "_" + cs[i].name;
515 if (access(progPinLoc.c_str(), F_OK) == 0) {
516 fd = bpf_obj_get(progPinLoc.c_str());
517 ALOGD("New bpf prog load reusing prog %s, ret: %d\n", cs[i].name.c_str(), fd);
518 reuse = true;
519 } else {
520 vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
521
522 fd = bpf_prog_load(cs[i].type, cs[i].name.c_str(), (struct bpf_insn*)cs[i].data.data(),
523 cs[i].data.size(), license.c_str(), kvers, 0,
524 log_buf.data(), log_buf.size());
Steven Moreland804bca02019-12-12 17:21:23 -0800525 ALOGD("bpf_prog_load lib call for %s (%s) returned fd: %d (%s)\n", elfPath,
526 cs[i].name.c_str(), fd, (fd < 0 ? std::strerror(errno) : "no error"));
Joel Fernandesd76a2002018-10-16 13:19:58 -0700527
Maciej Żenczykowski524deef2020-02-11 11:12:37 -0800528 if (fd < 0) {
529 std::vector<std::string> lines = android::base::Split(log_buf.data(), "\n");
530
531 ALOGE("bpf_prog_load - BEGIN log_buf contents:");
532 for (const auto& line : lines) ALOGE("%s", line.c_str());
533 ALOGE("bpf_prog_load - END log_buf contents.");
534 }
Joel Fernandesd76a2002018-10-16 13:19:58 -0700535 }
536
537 if (fd < 0) return fd;
538 if (fd == 0) return -EINVAL;
539
540 if (!reuse) {
541 ret = bpf_obj_pin(fd, progPinLoc.c_str());
542 if (ret < 0) return ret;
543 }
544
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700545 cs[i].prog_fd.reset(fd);
Joel Fernandesd76a2002018-10-16 13:19:58 -0700546 }
547
548 return 0;
549}
550
551int loadProg(const char* elfPath) {
552 vector<char> license;
553 vector<codeSection> cs;
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700554 vector<unique_fd> mapFds;
Joel Fernandesd76a2002018-10-16 13:19:58 -0700555 int ret;
556
557 ifstream elfFile(elfPath, ios::in | ios::binary);
558 if (!elfFile.is_open()) return -1;
559
560 ret = readSectionByName("license", elfFile, license);
561 if (ret) {
562 ALOGE("Couldn't find license in %s\n", elfPath);
563 return ret;
564 } else {
565 ALOGD("Loading ELF object %s with license %s\n", elfPath, (char*)license.data());
566 }
567
568 ret = readCodeSections(elfFile, cs);
569 if (ret) {
570 ALOGE("Couldn't read all code sections in %s\n", elfPath);
571 return ret;
572 }
573
574 /* Just for future debugging */
575 if (0) dumpAllCs(cs);
576
577 ret = createMaps(elfPath, elfFile, mapFds);
578 if (ret) {
579 ALOGE("Failed to create maps: (ret=%d) in %s\n", ret, elfPath);
580 return ret;
581 }
582
583 for (int i = 0; i < (int)mapFds.size(); i++)
Connor O'Brien8d49fc72019-10-24 18:23:49 -0700584 ALOGD("map_fd found at %d is %d in %s\n", i, mapFds[i].get(), elfPath);
Joel Fernandesd76a2002018-10-16 13:19:58 -0700585
586 applyMapRelo(elfFile, mapFds, cs);
587
588 ret = loadCodeSections(elfPath, cs, string(license.data()));
589 if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d\n", ret);
590
591 return ret;
592}
593
Steven Moreland4891e612020-01-10 15:35:52 -0800594void waitForProgsLoaded() {
595 while (!android::base::WaitForProperty("bpf.progs_loaded", "1", std::chrono::seconds(5))) {
596 ALOGW("Waited 5s for bpf.progs_loaded, still waiting...");
597 }
598}
599
Joel Fernandesd76a2002018-10-16 13:19:58 -0700600} // namespace bpf
601} // namespace android