| /* |
| * em_meta.c Metadata Ematch |
| * |
| * This program is free software; you can distribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 2 of the License, or (at your option) any later version. |
| * |
| * Authors: Thomas Graf <tgraf@suug.ch> |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <syslog.h> |
| #include <fcntl.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #include "m_ematch.h" |
| #include <linux/tc_ematch/tc_em_meta.h> |
| |
| extern struct ematch_util meta_ematch_util; |
| |
| static void meta_print_usage(FILE *fd) |
| { |
| fprintf(fd, |
| "Usage: meta(OBJECT { eq | lt | gt } OBJECT)\n" \ |
| "where: OBJECT := { META_ID | VALUE }\n" \ |
| " META_ID := id [ shift SHIFT ] [ mask MASK ]\n" \ |
| "\n" \ |
| "Example: meta(nf_mark gt 24)\n" \ |
| " meta(indev shift 1 eq \"ppp\")\n" \ |
| " meta(tcindex mask 0xf0 eq 0xf0)\n" \ |
| "\n" \ |
| "For a list of meta identifiers, use meta(list).\n"); |
| } |
| |
| struct meta_entry { |
| int id; |
| char *kind; |
| char *mask; |
| char *desc; |
| } meta_table[] = { |
| #define TCF_META_ID_SECTION 0 |
| #define __A(id, name, mask, desc) { TCF_META_ID_##id, name, mask, desc } |
| __A(SECTION, "Generic", "", ""), |
| __A(RANDOM, "random", "i", |
| "Random value (32 bit)"), |
| __A(LOADAVG_0, "loadavg_1", "i", |
| "Load average in last minute"), |
| __A(LOADAVG_1, "loadavg_5", "i", |
| "Load average in last 5 minutes"), |
| __A(LOADAVG_2, "loadavg_15", "i", |
| "Load average in last 15 minutes"), |
| |
| __A(SECTION, "Interfaces", "", ""), |
| __A(DEV, "dev", "iv", |
| "Device the packet is on"), |
| __A(SECTION, "Packet attributes", "", ""), |
| __A(PRIORITY, "priority", "i", |
| "Priority of packet"), |
| __A(PROTOCOL, "protocol", "i", |
| "Link layer protocol"), |
| __A(PKTTYPE, "pkt_type", "i", |
| "Packet type (uni|multi|broad|...)cast"), |
| __A(PKTLEN, "pkt_len", "i", |
| "Length of packet"), |
| __A(DATALEN, "data_len", "i", |
| "Length of data in packet"), |
| __A(MACLEN, "mac_len", "i", |
| "Length of link layer header"), |
| |
| __A(SECTION, "Netfilter", "", ""), |
| __A(NFMARK, "nf_mark", "i", |
| "Netfilter mark"), |
| __A(NFMARK, "fwmark", "i", |
| "Alias for nf_mark"), |
| |
| __A(SECTION, "Traffic Control", "", ""), |
| __A(TCINDEX, "tc_index", "i", "TC Index"), |
| __A(SECTION, "Routing", "", ""), |
| __A(RTCLASSID, "rt_classid", "i", |
| "Routing ClassID (cls_route)"), |
| __A(RTIIF, "rt_iif", "i", |
| "Incoming interface index"), |
| __A(VLAN_TAG, "vlan", "i", "Vlan tag"), |
| |
| __A(SECTION, "Sockets", "", ""), |
| __A(SK_FAMILY, "sk_family", "i", "Address family"), |
| __A(SK_STATE, "sk_state", "i", "State"), |
| __A(SK_REUSE, "sk_reuse", "i", "Reuse Flag"), |
| __A(SK_BOUND_IF, "sk_bind_if", "iv", "Bound interface"), |
| __A(SK_REFCNT, "sk_refcnt", "i", "Reference counter"), |
| __A(SK_SHUTDOWN, "sk_shutdown", "i", "Shutdown mask"), |
| __A(SK_PROTO, "sk_proto", "i", "Protocol"), |
| __A(SK_TYPE, "sk_type", "i", "Type"), |
| __A(SK_RCVBUF, "sk_rcvbuf", "i", "Receive buffer size"), |
| __A(SK_RMEM_ALLOC, "sk_rmem", "i", "RMEM"), |
| __A(SK_WMEM_ALLOC, "sk_wmem", "i", "WMEM"), |
| __A(SK_OMEM_ALLOC, "sk_omem", "i", "OMEM"), |
| __A(SK_WMEM_QUEUED, "sk_wmem_queue", "i", "WMEM queue"), |
| __A(SK_SND_QLEN, "sk_snd_queue", "i", "Send queue length"), |
| __A(SK_RCV_QLEN, "sk_rcv_queue", "i", "Receive queue length"), |
| __A(SK_ERR_QLEN, "sk_err_queue", "i", "Error queue length"), |
| __A(SK_FORWARD_ALLOCS, "sk_fwd_alloc", "i", "Forward allocations"), |
| __A(SK_SNDBUF, "sk_sndbuf", "i", "Send buffer size"), |
| #undef __A |
| }; |
| |
| static inline int map_type(char k) |
| { |
| switch (k) { |
| case 'i': return TCF_META_TYPE_INT; |
| case 'v': return TCF_META_TYPE_VAR; |
| } |
| |
| fprintf(stderr, "BUG: Unknown map character '%c'\n", k); |
| return INT_MAX; |
| } |
| |
| static struct meta_entry *lookup_meta_entry(struct bstr *kind) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(meta_table); i++) |
| if (!bstrcmp(kind, meta_table[i].kind) && |
| meta_table[i].id != 0) |
| return &meta_table[i]; |
| |
| return NULL; |
| } |
| |
| static struct meta_entry *lookup_meta_entry_byid(int id) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(meta_table); i++) |
| if (meta_table[i].id == id) |
| return &meta_table[i]; |
| |
| return NULL; |
| } |
| |
| static inline void dump_value(struct nlmsghdr *n, int tlv, unsigned long val, |
| struct tcf_meta_val *hdr) |
| { |
| __u32 t; |
| |
| switch (TCF_META_TYPE(hdr->kind)) { |
| case TCF_META_TYPE_INT: |
| t = val; |
| addattr_l(n, MAX_MSG, tlv, &t, sizeof(t)); |
| break; |
| |
| case TCF_META_TYPE_VAR: |
| if (TCF_META_ID(hdr->kind) == TCF_META_ID_VALUE) { |
| struct bstr *a = (struct bstr *) val; |
| |
| addattr_l(n, MAX_MSG, tlv, a->data, a->len); |
| } |
| break; |
| } |
| } |
| |
| static inline int is_compatible(struct tcf_meta_val *what, |
| struct tcf_meta_val *needed) |
| { |
| char *p; |
| struct meta_entry *entry; |
| |
| entry = lookup_meta_entry_byid(TCF_META_ID(what->kind)); |
| |
| if (entry == NULL) |
| return 0; |
| |
| for (p = entry->mask; p; p++) |
| if (map_type(*p) == TCF_META_TYPE(needed->kind)) |
| return 1; |
| |
| return 0; |
| } |
| |
| static void list_meta_ids(FILE *fd) |
| { |
| int i; |
| |
| fprintf(fd, |
| "--------------------------------------------------------\n" \ |
| " ID Type Description\n" \ |
| "--------------------------------------------------------"); |
| |
| for (i = 0; i < ARRAY_SIZE(meta_table); i++) { |
| if (meta_table[i].id == TCF_META_ID_SECTION) { |
| fprintf(fd, "\n%s:\n", meta_table[i].kind); |
| } else { |
| char *p = meta_table[i].mask; |
| char buf[64] = {0}; |
| |
| fprintf(fd, " %-16s ", meta_table[i].kind); |
| |
| while (*p) { |
| int type = map_type(*p); |
| |
| switch (type) { |
| case TCF_META_TYPE_INT: |
| strcat(buf, "INT"); |
| break; |
| |
| case TCF_META_TYPE_VAR: |
| strcat(buf, "VAR"); |
| break; |
| } |
| |
| if (*(++p)) |
| strcat(buf, ","); |
| } |
| |
| fprintf(fd, "%-10s %s\n", buf, meta_table[i].desc); |
| } |
| } |
| |
| fprintf(fd, |
| "--------------------------------------------------------\n"); |
| } |
| |
| #undef TCF_META_ID_SECTION |
| |
| #define PARSE_FAILURE ((void *) (-1)) |
| |
| #define PARSE_ERR(CARG, FMT, ARGS...) \ |
| em_parse_error(EINVAL, args, CARG, &meta_ematch_util, FMT, ##ARGS) |
| |
| static inline int can_adopt(struct tcf_meta_val *val) |
| { |
| return !!TCF_META_ID(val->kind); |
| } |
| |
| static inline int overwrite_type(struct tcf_meta_val *src, |
| struct tcf_meta_val *dst) |
| { |
| return (TCF_META_TYPE(dst->kind) << 12) | TCF_META_ID(src->kind); |
| } |
| |
| |
| static inline struct bstr * |
| parse_object(struct bstr *args, struct bstr *arg, struct tcf_meta_val *obj, |
| unsigned long *dst, struct tcf_meta_val *left) |
| { |
| struct meta_entry *entry; |
| unsigned long num; |
| struct bstr *a; |
| |
| if (arg->quoted) { |
| obj->kind = TCF_META_TYPE_VAR << 12; |
| obj->kind |= TCF_META_ID_VALUE; |
| *dst = (unsigned long) arg; |
| return bstr_next(arg); |
| } |
| |
| num = bstrtoul(arg); |
| if (num != ULONG_MAX) { |
| obj->kind = TCF_META_TYPE_INT << 12; |
| obj->kind |= TCF_META_ID_VALUE; |
| *dst = (unsigned long) num; |
| return bstr_next(arg); |
| } |
| |
| entry = lookup_meta_entry(arg); |
| |
| if (entry == NULL) { |
| PARSE_ERR(arg, "meta: unknown meta id\n"); |
| return PARSE_FAILURE; |
| } |
| |
| obj->kind = entry->id | (map_type(entry->mask[0]) << 12); |
| |
| if (left) { |
| struct tcf_meta_val *right = obj; |
| |
| if (TCF_META_TYPE(right->kind) == TCF_META_TYPE(left->kind)) |
| goto compatible; |
| |
| if (can_adopt(left) && !can_adopt(right)) { |
| if (is_compatible(left, right)) |
| left->kind = overwrite_type(left, right); |
| else |
| goto not_compatible; |
| } else if (can_adopt(right) && !can_adopt(left)) { |
| if (is_compatible(right, left)) |
| right->kind = overwrite_type(right, left); |
| else |
| goto not_compatible; |
| } else if (can_adopt(left) && can_adopt(right)) { |
| if (is_compatible(left, right)) |
| left->kind = overwrite_type(left, right); |
| else if (is_compatible(right, left)) |
| right->kind = overwrite_type(right, left); |
| else |
| goto not_compatible; |
| } else |
| goto not_compatible; |
| } |
| |
| compatible: |
| |
| a = bstr_next(arg); |
| |
| while (a) { |
| if (!bstrcmp(a, "shift")) { |
| unsigned long shift; |
| |
| if (a->next == NULL) { |
| PARSE_ERR(a, "meta: missing argument"); |
| return PARSE_FAILURE; |
| } |
| a = bstr_next(a); |
| |
| shift = bstrtoul(a); |
| if (shift == ULONG_MAX) { |
| PARSE_ERR(a, "meta: invalid shift, must " \ |
| "be numeric"); |
| return PARSE_FAILURE; |
| } |
| |
| obj->shift = (__u8) shift; |
| a = bstr_next(a); |
| } else if (!bstrcmp(a, "mask")) { |
| unsigned long mask; |
| |
| if (a->next == NULL) { |
| PARSE_ERR(a, "meta: missing argument"); |
| return PARSE_FAILURE; |
| } |
| a = bstr_next(a); |
| |
| mask = bstrtoul(a); |
| if (mask == ULONG_MAX) { |
| PARSE_ERR(a, "meta: invalid mask, must be " \ |
| "numeric"); |
| return PARSE_FAILURE; |
| } |
| *dst = (unsigned long) mask; |
| a = bstr_next(a); |
| } else |
| break; |
| } |
| |
| return a; |
| |
| not_compatible: |
| PARSE_ERR(arg, "lvalue and rvalue are not compatible."); |
| return PARSE_FAILURE; |
| } |
| |
| static int meta_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, |
| struct bstr *args) |
| { |
| int opnd; |
| struct bstr *a; |
| struct tcf_meta_hdr meta_hdr = {}; |
| unsigned long lvalue = 0, rvalue = 0; |
| |
| if (args == NULL) |
| return PARSE_ERR(args, "meta: missing arguments"); |
| |
| if (!bstrcmp(args, "list")) { |
| list_meta_ids(stderr); |
| return -1; |
| } |
| |
| a = parse_object(args, args, &meta_hdr.left, &lvalue, NULL); |
| if (a == PARSE_FAILURE) |
| return -1; |
| else if (a == NULL) |
| return PARSE_ERR(args, "meta: missing operand"); |
| |
| if (!bstrcmp(a, "eq")) |
| opnd = TCF_EM_OPND_EQ; |
| else if (!bstrcmp(a, "gt")) |
| opnd = TCF_EM_OPND_GT; |
| else if (!bstrcmp(a, "lt")) |
| opnd = TCF_EM_OPND_LT; |
| else |
| return PARSE_ERR(a, "meta: invalid operand"); |
| |
| meta_hdr.left.op = (__u8) opnd; |
| |
| if (a->next == NULL) |
| return PARSE_ERR(args, "meta: missing rvalue"); |
| a = bstr_next(a); |
| |
| a = parse_object(args, a, &meta_hdr.right, &rvalue, &meta_hdr.left); |
| if (a == PARSE_FAILURE) |
| return -1; |
| else if (a != NULL) |
| return PARSE_ERR(a, "meta: unexpected trailer"); |
| |
| |
| addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); |
| |
| addattr_l(n, MAX_MSG, TCA_EM_META_HDR, &meta_hdr, sizeof(meta_hdr)); |
| |
| dump_value(n, TCA_EM_META_LVALUE, lvalue, &meta_hdr.left); |
| dump_value(n, TCA_EM_META_RVALUE, rvalue, &meta_hdr.right); |
| |
| return 0; |
| } |
| #undef PARSE_ERR |
| |
| static inline void print_binary(FILE *fd, unsigned char *str, int len) |
| { |
| int i; |
| |
| for (i = 0; i < len; i++) |
| if (!isprint(str[i])) |
| goto binary; |
| |
| for (i = 0; i < len; i++) |
| fprintf(fd, "%c", str[i]); |
| return; |
| |
| binary: |
| for (i = 0; i < len; i++) |
| fprintf(fd, "%02x ", str[i]); |
| |
| fprintf(fd, "\""); |
| for (i = 0; i < len; i++) |
| fprintf(fd, "%c", isprint(str[i]) ? str[i] : '.'); |
| fprintf(fd, "\""); |
| } |
| |
| static inline int print_value(FILE *fd, int type, struct rtattr *rta) |
| { |
| if (rta == NULL) { |
| fprintf(stderr, "Missing value TLV\n"); |
| return -1; |
| } |
| |
| switch (type) { |
| case TCF_META_TYPE_INT: |
| if (RTA_PAYLOAD(rta) < sizeof(__u32)) { |
| fprintf(stderr, "meta int type value TLV " \ |
| "size mismatch.\n"); |
| return -1; |
| } |
| fprintf(fd, "%d", rta_getattr_u32(rta)); |
| break; |
| |
| case TCF_META_TYPE_VAR: |
| print_binary(fd, RTA_DATA(rta), RTA_PAYLOAD(rta)); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int print_object(FILE *fd, struct tcf_meta_val *obj, struct rtattr *rta) |
| { |
| int id = TCF_META_ID(obj->kind); |
| int type = TCF_META_TYPE(obj->kind); |
| struct meta_entry *entry; |
| |
| if (id == TCF_META_ID_VALUE) |
| return print_value(fd, type, rta); |
| |
| entry = lookup_meta_entry_byid(id); |
| |
| if (entry == NULL) |
| fprintf(fd, "[unknown meta id %d]", id); |
| else |
| fprintf(fd, "%s", entry->kind); |
| |
| if (obj->shift) |
| fprintf(fd, " shift %d", obj->shift); |
| |
| switch (type) { |
| case TCF_META_TYPE_INT: |
| if (rta) { |
| if (RTA_PAYLOAD(rta) < sizeof(__u32)) |
| goto size_mismatch; |
| |
| if (rta_getattr_u32(rta)) |
| fprintf(fd, " mask 0x%08x", |
| rta_getattr_u32(rta)); |
| } |
| break; |
| } |
| |
| return 0; |
| |
| size_mismatch: |
| fprintf(stderr, "meta int type mask TLV size mismatch\n"); |
| return -1; |
| } |
| |
| |
| static int meta_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, |
| int data_len) |
| { |
| struct rtattr *tb[TCA_EM_META_MAX+1]; |
| struct tcf_meta_hdr *meta_hdr; |
| |
| if (parse_rtattr(tb, TCA_EM_META_MAX, data, data_len) < 0) |
| return -1; |
| |
| if (tb[TCA_EM_META_HDR] == NULL) { |
| fprintf(stderr, "Missing meta header\n"); |
| return -1; |
| } |
| |
| if (RTA_PAYLOAD(tb[TCA_EM_META_HDR]) < sizeof(*meta_hdr)) { |
| fprintf(stderr, "Meta header size mismatch\n"); |
| return -1; |
| } |
| |
| meta_hdr = RTA_DATA(tb[TCA_EM_META_HDR]); |
| |
| if (print_object(fd, &meta_hdr->left, tb[TCA_EM_META_LVALUE]) < 0) |
| return -1; |
| |
| switch (meta_hdr->left.op) { |
| case TCF_EM_OPND_EQ: |
| fprintf(fd, " eq "); |
| break; |
| case TCF_EM_OPND_LT: |
| fprintf(fd, " lt "); |
| break; |
| case TCF_EM_OPND_GT: |
| fprintf(fd, " gt "); |
| break; |
| } |
| |
| return print_object(fd, &meta_hdr->right, tb[TCA_EM_META_RVALUE]); |
| } |
| |
| struct ematch_util meta_ematch_util = { |
| .kind = "meta", |
| .kind_num = TCF_EM_META, |
| .parse_eopt = meta_parse_eopt, |
| .print_eopt = meta_print_eopt, |
| .print_usage = meta_print_usage |
| }; |