// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2021 Felix Fietkau */ #include #include "qosify.h" static struct blob_buf b; static int qosify_ubus_add_array(struct blob_attr *attr, uint8_t val, enum qosify_map_id id) { struct blob_attr *cur; int rem; if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0) return UBUS_STATUS_INVALID_ARGUMENT; blobmsg_for_each_attr(cur, attr, rem) qosify_map_set_entry(id, false, blobmsg_get_string(cur), val); return 0; } static int qosify_ubus_set_files(struct blob_attr *attr) { struct blob_attr *cur; int rem; if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0) return UBUS_STATUS_INVALID_ARGUMENT; qosify_map_clear_files(); blobmsg_for_each_attr(cur, attr, rem) qosify_map_load_file(blobmsg_get_string(cur)); qosify_map_gc(); return 0; } enum { CL_ADD_DSCP, CL_ADD_TIMEOUT, CL_ADD_IPV4, CL_ADD_IPV6, CL_ADD_TCP_PORT, CL_ADD_UDP_PORT, CL_ADD_DNS, __CL_ADD_MAX }; static const struct blobmsg_policy qosify_add_policy[__CL_ADD_MAX] = { [CL_ADD_DSCP] = { "dscp", BLOBMSG_TYPE_STRING }, [CL_ADD_TIMEOUT] = { "timeout", BLOBMSG_TYPE_INT32 }, [CL_ADD_IPV4] = { "ipv4", BLOBMSG_TYPE_ARRAY }, [CL_ADD_IPV6] = { "ipv6", BLOBMSG_TYPE_ARRAY }, [CL_ADD_TCP_PORT] = { "tcp_port", BLOBMSG_TYPE_ARRAY }, [CL_ADD_UDP_PORT] = { "udp_port", BLOBMSG_TYPE_ARRAY }, [CL_ADD_DNS] = { "dns", BLOBMSG_TYPE_ARRAY }, }; static int qosify_ubus_reload(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { qosify_map_reload(); return 0; } static int qosify_ubus_add(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { int prev_timemout = qosify_map_timeout; struct blob_attr *tb[__CL_ADD_MAX]; struct blob_attr *cur; uint8_t dscp = 0xff; int ret; blobmsg_parse(qosify_add_policy, __CL_ADD_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); if (!strcmp(method, "add")) { if ((cur = tb[CL_ADD_DSCP]) == NULL || qosify_map_dscp_value(blobmsg_get_string(cur), &dscp)) return UBUS_STATUS_INVALID_ARGUMENT; if ((cur = tb[CL_ADD_TIMEOUT]) != NULL) qosify_map_timeout = blobmsg_get_u32(cur); } if ((cur = tb[CL_ADD_IPV4]) != NULL && (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_IPV4_ADDR) != 0)) return ret; if ((cur = tb[CL_ADD_IPV6]) != NULL && (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_IPV6_ADDR) != 0)) return ret; if ((cur = tb[CL_ADD_TCP_PORT]) != NULL && (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_TCP_PORTS) != 0)) return ret; if ((cur = tb[CL_ADD_UDP_PORT]) != NULL && (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_UDP_PORTS) != 0)) return ret; if ((cur = tb[CL_ADD_DNS]) != NULL && (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_DNS) != 0)) return ret; qosify_map_timeout = prev_timemout; return 0; } enum { CL_CONFIG_RESET, CL_CONFIG_FILES, CL_CONFIG_TIMEOUT, CL_CONFIG_DSCP_UDP, CL_CONFIG_DSCP_TCP, CL_CONFIG_DSCP_ICMP, CL_CONFIG_INTERFACES, CL_CONFIG_DEVICES, CL_CONFIG_CLASSES, __CL_CONFIG_MAX }; static const struct blobmsg_policy qosify_config_policy[__CL_CONFIG_MAX] = { [CL_CONFIG_RESET] = { "reset", BLOBMSG_TYPE_BOOL }, [CL_CONFIG_FILES] = { "files", BLOBMSG_TYPE_ARRAY }, [CL_CONFIG_TIMEOUT] = { "timeout", BLOBMSG_TYPE_INT32 }, [CL_CONFIG_DSCP_UDP] = { "dscp_default_udp", BLOBMSG_TYPE_STRING }, [CL_CONFIG_DSCP_TCP] = { "dscp_default_tcp", BLOBMSG_TYPE_STRING }, [CL_CONFIG_DSCP_ICMP] = { "dscp_icmp", BLOBMSG_TYPE_STRING }, [CL_CONFIG_INTERFACES] = { "interfaces", BLOBMSG_TYPE_TABLE }, [CL_CONFIG_DEVICES] = { "devices", BLOBMSG_TYPE_TABLE }, [CL_CONFIG_CLASSES] = { "classes", BLOBMSG_TYPE_TABLE }, }; static int qosify_ubus_config(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__CL_CONFIG_MAX]; struct blob_attr *cur; uint8_t dscp; bool reset = false; int ret; blobmsg_parse(qosify_config_policy, __CL_CONFIG_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); if ((cur = tb[CL_CONFIG_RESET]) != NULL) reset = blobmsg_get_bool(cur); if (reset) qosify_map_reset_config(); if ((cur = tb[CL_CONFIG_CLASSES]) != NULL || reset) qosify_map_set_classes(cur); if ((cur = tb[CL_CONFIG_TIMEOUT]) != NULL) qosify_map_timeout = blobmsg_get_u32(cur); if ((cur = tb[CL_CONFIG_FILES]) != NULL && (ret = qosify_ubus_set_files(cur) != 0)) return ret; if (map_parse_flow_config(&flow_config, msg, reset) || map_fill_dscp_value(&config.dscp_icmp, tb[CL_CONFIG_DSCP_ICMP], reset)) return UBUS_STATUS_INVALID_ARGUMENT; map_fill_dscp_value(&dscp, tb[CL_CONFIG_DSCP_UDP], true); if (dscp != 0xff) qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, dscp); map_fill_dscp_value(&dscp, tb[CL_CONFIG_DSCP_TCP], true); if (dscp != 0xff) qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, dscp); qosify_map_update_config(); qosify_iface_config_update(tb[CL_CONFIG_INTERFACES], tb[CL_CONFIG_DEVICES]); qosify_iface_check(); return 0; } static int qosify_ubus_dump(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { blob_buf_init(&b, 0); qosify_map_dump(&b); ubus_send_reply(ctx, req, b.head); blob_buf_free(&b); return 0; } static int qosify_ubus_status(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { blob_buf_init(&b, 0); qosify_iface_status(&b); ubus_send_reply(ctx, req, b.head); blob_buf_free(&b); return 0; } static int qosify_ubus_get_stats(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { static const struct blobmsg_policy policy = { "reset", BLOBMSG_TYPE_BOOL }; struct blob_attr *tb; bool reset = false; blobmsg_parse(&policy, 1, &tb, blobmsg_data(msg), blobmsg_len(msg)); reset = tb && blobmsg_get_u8(tb); blob_buf_init(&b, 0); qosify_map_stats(&b, reset); ubus_send_reply(ctx, req, b.head); blob_buf_free(&b); return 0; } static int qosify_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { qosify_iface_check(); return 0; } enum { CL_DNS_HOST_NAME, CL_DNS_HOST_TYPE, CL_DNS_HOST_ADDR, CL_DNS_HOST_TTL, __CL_DNS_HOST_MAX }; static const struct blobmsg_policy qosify_dns_policy[__CL_DNS_HOST_MAX] = { [CL_DNS_HOST_NAME] = { "name", BLOBMSG_TYPE_STRING }, [CL_DNS_HOST_TYPE] = { "type", BLOBMSG_TYPE_STRING }, [CL_DNS_HOST_ADDR] = { "address", BLOBMSG_TYPE_STRING }, [CL_DNS_HOST_TTL] = { "ttl", BLOBMSG_TYPE_INT32 }, }; static int __qosify_ubus_add_dns_host(struct blob_attr *msg) { struct blob_attr *tb[__CL_DNS_HOST_MAX]; struct blob_attr *cur; uint32_t ttl = 0; blobmsg_parse(qosify_dns_policy, __CL_DNS_HOST_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); if (!tb[CL_DNS_HOST_NAME] || !tb[CL_DNS_HOST_TYPE] || !tb[CL_DNS_HOST_ADDR]) return UBUS_STATUS_INVALID_ARGUMENT; if ((cur = tb[CL_DNS_HOST_TTL]) != NULL) ttl = blobmsg_get_u32(cur); if (qosify_map_add_dns_host(blobmsg_get_string(tb[CL_DNS_HOST_NAME]), blobmsg_get_string(tb[CL_DNS_HOST_ADDR]), blobmsg_get_string(tb[CL_DNS_HOST_TYPE]), ttl)) return UBUS_STATUS_INVALID_ARGUMENT; return 0; } static int qosify_ubus_add_dns_host(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { return __qosify_ubus_add_dns_host(msg); } static const struct ubus_method qosify_methods[] = { UBUS_METHOD_NOARG("reload", qosify_ubus_reload), UBUS_METHOD("add", qosify_ubus_add, qosify_add_policy), UBUS_METHOD_MASK("remove", qosify_ubus_add, qosify_add_policy, ((1 << __CL_ADD_MAX) - 1) & ~(1 << CL_ADD_DSCP)), UBUS_METHOD("config", qosify_ubus_config, qosify_config_policy), UBUS_METHOD_NOARG("dump", qosify_ubus_dump), UBUS_METHOD_NOARG("status", qosify_ubus_status), UBUS_METHOD_NOARG("get_stats", qosify_ubus_get_stats), UBUS_METHOD("add_dns_host", qosify_ubus_add_dns_host, qosify_dns_policy), UBUS_METHOD_NOARG("check_devices", qosify_ubus_check_devices), }; static struct ubus_object_type qosify_object_type = UBUS_OBJECT_TYPE("qosify", qosify_methods); static struct ubus_object qosify_object = { .name = "qosify", .type = &qosify_object_type, .methods = qosify_methods, .n_methods = ARRAY_SIZE(qosify_methods), }; static void qosify_subscribe_dnsmasq(struct ubus_context *ctx) { static struct ubus_subscriber sub = { .cb = qosify_ubus_add_dns_host, }; uint32_t id; if (!sub.obj.id && ubus_register_subscriber(ctx, &sub)) return; if (ubus_lookup_id(ctx, "dnsmasq.dns", &id)) return; ubus_subscribe(ctx, &sub, id); } static void qosify_ubus_event_cb(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *type, struct blob_attr *msg) { static const struct blobmsg_policy policy = { "path", BLOBMSG_TYPE_STRING }; struct blob_attr *attr; const char *path; blobmsg_parse(&policy, 1, &attr, blobmsg_data(msg), blobmsg_len(msg)); if (!attr) return; path = blobmsg_get_string(attr); if (!strcmp(path, "dnsmasq.dns")) qosify_subscribe_dnsmasq(ctx); else if (!strcmp(path, "bridger")) qosify_ubus_update_bridger(false); } static void ubus_connect_handler(struct ubus_context *ctx) { static struct ubus_event_handler ev = { .cb = qosify_ubus_event_cb }; ubus_add_object(ctx, &qosify_object); ubus_register_event_handler(ctx, &ev, "ubus.object.add"); qosify_subscribe_dnsmasq(ctx); } static struct ubus_auto_conn conn; void qosify_ubus_update_bridger(bool shutdown) { struct ubus_request req; uint32_t id; void *c; if (ubus_lookup_id(&conn.ctx, "bridger", &id)) return; blob_buf_init(&b, 0); blobmsg_add_string(&b, "name", "qosify"); c = blobmsg_open_array(&b, "devices"); if (!shutdown) qosify_iface_get_devices(&b); blobmsg_close_array(&b, c); ubus_invoke_async(&conn.ctx, id, "set_blacklist", b.head, &req); } int qosify_ubus_init(void) { conn.cb = ubus_connect_handler; ubus_auto_connect(&conn); return 0; } void qosify_ubus_stop(void) { qosify_ubus_update_bridger(true); ubus_auto_shutdown(&conn); } struct iface_req { char *name; int len; }; static void netifd_if_cb(struct ubus_request *req, int type, struct blob_attr *msg) { struct iface_req *ifr = req->priv; enum { IFS_ATTR_UP, IFS_ATTR_DEV, __IFS_ATTR_MAX }; static const struct blobmsg_policy policy[__IFS_ATTR_MAX] = { [IFS_ATTR_UP] = { "up", BLOBMSG_TYPE_BOOL }, [IFS_ATTR_DEV] = { "l3_device", BLOBMSG_TYPE_STRING }, }; struct blob_attr *tb[__IFS_ATTR_MAX]; blobmsg_parse(policy, __IFS_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); if (!tb[IFS_ATTR_UP] || !tb[IFS_ATTR_DEV]) return; if (!blobmsg_get_bool(tb[IFS_ATTR_UP])) return; snprintf(ifr->name, ifr->len, "%s", blobmsg_get_string(tb[IFS_ATTR_DEV])); } int qosify_ubus_check_interface(const char *name, char *ifname, int ifname_len) { struct iface_req req = { ifname, ifname_len }; char *obj_name = "network.interface."; uint32_t id; #define PREFIX "network.interface." obj_name = alloca(sizeof(PREFIX) + strlen(name) + 1); sprintf(obj_name, PREFIX "%s", name); #undef PREFIX ifname[0] = 0; if (ubus_lookup_id(&conn.ctx, obj_name, &id)) return -1; blob_buf_init(&b, 0); ubus_invoke(&conn.ctx, id, "status", b.head, netifd_if_cb, &req, 1000); if (!ifname[0]) return -1; return 0; }