Yonghong Song | f6f3bac | 2018-09-05 16:58:06 -0700 | [diff] [blame] | 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 | } |