1 // SPDX-License-Identifier: GPL-2.0+ 2 // Copyright (C) 2018 Facebook 3 4 #define _GNU_SOURCE 5 #include <stdlib.h> 6 #include <string.h> 7 #include <unistd.h> 8 #include <libbpf.h> 9 #include <net/if.h> 10 #include <linux/if.h> 11 #include <linux/rtnetlink.h> 12 #include <linux/tc_act/tc_bpf.h> 13 #include <sys/socket.h> 14 15 #include <bpf.h> 16 #include <nlattr.h> 17 #include "main.h" 18 #include "netlink_dumper.h" 19 20 struct bpf_netdev_t { 21 int *ifindex_array; 22 int used_len; 23 int array_len; 24 int filter_idx; 25 }; 26 27 struct tc_kind_handle { 28 char kind[64]; 29 int handle; 30 }; 31 32 struct bpf_tcinfo_t { 33 struct tc_kind_handle *handle_array; 34 int used_len; 35 int array_len; 36 bool is_qdisc; 37 }; 38 39 static int dump_link_nlmsg(void *cookie, void *msg, struct nlattr **tb) 40 { 41 struct bpf_netdev_t *netinfo = cookie; 42 struct ifinfomsg *ifinfo = msg; 43 44 if (netinfo->filter_idx > 0 && netinfo->filter_idx != ifinfo->ifi_index) 45 return 0; 46 47 if (netinfo->used_len == netinfo->array_len) { 48 netinfo->ifindex_array = realloc(netinfo->ifindex_array, 49 (netinfo->array_len + 16) * sizeof(int)); 50 netinfo->array_len += 16; 51 } 52 netinfo->ifindex_array[netinfo->used_len++] = ifinfo->ifi_index; 53 54 return do_xdp_dump(ifinfo, tb); 55 } 56 57 static int dump_class_qdisc_nlmsg(void *cookie, void *msg, struct nlattr **tb) 58 { 59 struct bpf_tcinfo_t *tcinfo = cookie; 60 struct tcmsg *info = msg; 61 62 if (tcinfo->is_qdisc) { 63 /* skip clsact qdisc */ 64 if (tb[TCA_KIND] && 65 strcmp(nla_data(tb[TCA_KIND]), "clsact") == 0) 66 return 0; 67 if (info->tcm_handle == 0) 68 return 0; 69 } 70 71 if (tcinfo->used_len == tcinfo->array_len) { 72 tcinfo->handle_array = realloc(tcinfo->handle_array, 73 (tcinfo->array_len + 16) * sizeof(struct tc_kind_handle)); 74 tcinfo->array_len += 16; 75 } 76 tcinfo->handle_array[tcinfo->used_len].handle = info->tcm_handle; 77 snprintf(tcinfo->handle_array[tcinfo->used_len].kind, 78 sizeof(tcinfo->handle_array[tcinfo->used_len].kind), 79 "%s_%s", 80 tcinfo->is_qdisc ? "qdisc" : "class", 81 tb[TCA_KIND] ? nla_getattr_str(tb[TCA_KIND]) : "unknown"); 82 tcinfo->used_len++; 83 84 return 0; 85 } 86 87 static int dump_filter_nlmsg(void *cookie, void *msg, struct nlattr **tb) 88 { 89 const char *kind = cookie; 90 91 return do_filter_dump((struct tcmsg *)msg, tb, kind); 92 } 93 94 static int show_dev_tc_bpf(int sock, unsigned int nl_pid, int ifindex) 95 { 96 struct bpf_tcinfo_t tcinfo; 97 int i, handle, ret; 98 99 tcinfo.handle_array = NULL; 100 tcinfo.used_len = 0; 101 tcinfo.array_len = 0; 102 103 tcinfo.is_qdisc = false; 104 ret = nl_get_class(sock, nl_pid, ifindex, dump_class_qdisc_nlmsg, 105 &tcinfo); 106 if (ret) 107 return ret; 108 109 tcinfo.is_qdisc = true; 110 ret = nl_get_qdisc(sock, nl_pid, ifindex, dump_class_qdisc_nlmsg, 111 &tcinfo); 112 if (ret) 113 return ret; 114 115 for (i = 0; i < tcinfo.used_len; i++) { 116 ret = nl_get_filter(sock, nl_pid, ifindex, 117 tcinfo.handle_array[i].handle, 118 dump_filter_nlmsg, 119 tcinfo.handle_array[i].kind); 120 if (ret) 121 return ret; 122 } 123 124 /* root, ingress and egress handle */ 125 handle = TC_H_ROOT; 126 ret = nl_get_filter(sock, nl_pid, ifindex, handle, dump_filter_nlmsg, 127 "root"); 128 if (ret) 129 return ret; 130 131 handle = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); 132 ret = nl_get_filter(sock, nl_pid, ifindex, handle, dump_filter_nlmsg, 133 "qdisc_clsact_ingress"); 134 if (ret) 135 return ret; 136 137 handle = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS); 138 ret = nl_get_filter(sock, nl_pid, ifindex, handle, dump_filter_nlmsg, 139 "qdisc_clsact_egress"); 140 if (ret) 141 return ret; 142 143 return 0; 144 } 145 146 static int do_show(int argc, char **argv) 147 { 148 int i, sock, ret, filter_idx = -1; 149 struct bpf_netdev_t dev_array; 150 unsigned int nl_pid; 151 char err_buf[256]; 152 153 if (argc == 2) { 154 if (strcmp(argv[0], "dev") != 0) 155 usage(); 156 filter_idx = if_nametoindex(argv[1]); 157 if (filter_idx == 0) { 158 fprintf(stderr, "invalid dev name %s\n", argv[1]); 159 return -1; 160 } 161 } else if (argc != 0) { 162 usage(); 163 } 164 165 sock = bpf_netlink_open(&nl_pid); 166 if (sock < 0) { 167 fprintf(stderr, "failed to open netlink sock\n"); 168 return -1; 169 } 170 171 dev_array.ifindex_array = NULL; 172 dev_array.used_len = 0; 173 dev_array.array_len = 0; 174 dev_array.filter_idx = filter_idx; 175 176 if (json_output) 177 jsonw_start_array(json_wtr); 178 NET_START_OBJECT; 179 NET_START_ARRAY("xdp", "\n"); 180 ret = nl_get_link(sock, nl_pid, dump_link_nlmsg, &dev_array); 181 NET_END_ARRAY("\n"); 182 183 if (!ret) { 184 NET_START_ARRAY("tc_filters", "\n"); 185 for (i = 0; i < dev_array.used_len; i++) { 186 ret = show_dev_tc_bpf(sock, nl_pid, 187 dev_array.ifindex_array[i]); 188 if (ret) 189 break; 190 } 191 NET_END_ARRAY("\n"); 192 } 193 NET_END_OBJECT; 194 if (json_output) 195 jsonw_end_array(json_wtr); 196 197 if (ret) { 198 if (json_output) 199 jsonw_null(json_wtr); 200 libbpf_strerror(ret, err_buf, sizeof(err_buf)); 201 fprintf(stderr, "Error: %s\n", err_buf); 202 } 203 free(dev_array.ifindex_array); 204 close(sock); 205 return ret; 206 } 207 208 static int do_help(int argc, char **argv) 209 { 210 if (json_output) { 211 jsonw_null(json_wtr); 212 return 0; 213 } 214 215 fprintf(stderr, 216 "Usage: %s %s { show | list } [dev <devname>]\n" 217 " %s %s help\n", 218 bin_name, argv[-2], bin_name, argv[-2]); 219 220 return 0; 221 } 222 223 static const struct cmd cmds[] = { 224 { "show", do_show }, 225 { "list", do_show }, 226 { "help", do_help }, 227 { 0 } 228 }; 229 230 int do_net(int argc, char **argv) 231 { 232 return cmd_select(cmds, argc, argv, do_help); 233 } 234