/* -*- mode: c; c-file-style: "openbsd" -*- */ /* * Copyright (c) 2008 Vincent Bernat * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "lldpd.h" #include "trace.h" #include static ssize_t client_handle_none(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { log_info("rpc", "received noop request from client"); *type = NONE; return 0; } /* Return the global configuration */ static ssize_t client_handle_get_configuration(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { ssize_t output_len; log_debug("rpc", "client requested configuration"); output_len = lldpd_config_serialize(&cfg->g_config, output); if (output_len <= 0) { output_len = 0; *type = NONE; } return output_len; } static char * xstrdup(const char *str) { if (!str) return NULL; return strdup(str); } /* Change the global configuration */ static ssize_t client_handle_set_configuration(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { struct lldpd_config *config; log_debug("rpc", "client request a change in configuration"); /* Get the proposed configuration. */ if (lldpd_config_unserialize(input, input_len, &config) <= 0) { *type = NONE; return 0; } #define CHANGED(w) (config->w != cfg->g_config.w) #define CHANGED_STR(w) \ (!(config->w == cfg->g_config.w || \ (config->w && cfg->g_config.w && !strcmp(config->w, cfg->g_config.w)))) /* What needs to be done? Transmit delay? */ if (CHANGED(c_tx_interval) && config->c_tx_interval != 0) { if (config->c_tx_interval < 0) { log_debug("rpc", "client asked for immediate retransmission"); } else { log_debug("rpc", "client change transmit interval to %d ms", config->c_tx_interval); cfg->g_config.c_tx_interval = config->c_tx_interval; cfg->g_config.c_ttl = cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold; cfg->g_config.c_ttl = (cfg->g_config.c_ttl + 999) / 1000; } levent_send_now(cfg); } if (CHANGED(c_tx_hold) && config->c_tx_hold > 0) { log_debug("rpc", "client change transmit hold to %d", config->c_tx_hold); cfg->g_config.c_tx_hold = config->c_tx_hold; cfg->g_config.c_ttl = cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold; cfg->g_config.c_ttl = (cfg->g_config.c_ttl + 999) / 1000; } if (CHANGED(c_max_neighbors) && config->c_max_neighbors > 0) { log_debug("rpc", "client change maximum neighbors to %d", config->c_max_neighbors); cfg->g_config.c_max_neighbors = config->c_max_neighbors; } if (CHANGED(c_lldp_portid_type) && config->c_lldp_portid_type > LLDP_PORTID_SUBTYPE_UNKNOWN && config->c_lldp_portid_type <= LLDP_PORTID_SUBTYPE_MAX) { log_debug("rpc", "change lldp portid tlv subtype to %d", config->c_lldp_portid_type); cfg->g_config.c_lldp_portid_type = config->c_lldp_portid_type; levent_update_now(cfg); } if (CHANGED(c_lldp_agent_type) && config->c_lldp_agent_type > LLDP_AGENT_TYPE_UNKNOWN && config->c_lldp_agent_type <= LLDP_AGENT_TYPE_MAX) { log_debug("rpc", "change lldp agent type to %d", config->c_lldp_agent_type); cfg->g_config.c_lldp_agent_type = config->c_lldp_agent_type; levent_update_now(cfg); } /* Pause/resume */ if (CHANGED(c_paused)) { log_debug("rpc", "client asked to %s lldpd", config->c_paused ? "pause" : "resume"); cfg->g_config.c_paused = config->c_paused; levent_send_now(cfg); } #ifdef ENABLE_LLDPMED if (CHANGED(c_enable_fast_start)) { cfg->g_config.c_enable_fast_start = config->c_enable_fast_start; log_debug("rpc", "client asked to %s fast start", cfg->g_config.c_enable_fast_start ? "enable" : "disable"); } if (CHANGED(c_tx_fast_interval) && config->c_tx_fast_interval > 0) { log_debug("rpc", "change fast interval to %d", config->c_tx_fast_interval); cfg->g_config.c_tx_fast_interval = config->c_tx_fast_interval; } #endif if (CHANGED_STR(c_iface_pattern)) { log_debug("rpc", "change interface pattern to %s", config->c_iface_pattern ? config->c_iface_pattern : "(NULL)"); free(cfg->g_config.c_iface_pattern); cfg->g_config.c_iface_pattern = xstrdup(config->c_iface_pattern); levent_update_now(cfg); } if (CHANGED_STR(c_perm_ifaces)) { log_debug("rpc", "change permanent interface pattern to %s", config->c_perm_ifaces ? config->c_perm_ifaces : "(NULL)"); free(cfg->g_config.c_perm_ifaces); cfg->g_config.c_perm_ifaces = xstrdup(config->c_perm_ifaces); levent_update_now(cfg); } if (CHANGED_STR(c_mgmt_pattern)) { log_debug("rpc", "change management pattern to %s", config->c_mgmt_pattern ? config->c_mgmt_pattern : "(NULL)"); free(cfg->g_config.c_mgmt_pattern); cfg->g_config.c_mgmt_pattern = xstrdup(config->c_mgmt_pattern); levent_update_now(cfg); } if (CHANGED_STR(c_cid_string)) { log_debug("rpc", "change chassis ID string to %s", config->c_cid_string ? config->c_cid_string : "(NULL)"); free(cfg->g_config.c_cid_string); cfg->g_config.c_cid_string = xstrdup(config->c_cid_string); free(LOCAL_CHASSIS(cfg)->c_id); LOCAL_CHASSIS(cfg)->c_id = NULL; lldpd_update_localchassis(cfg); levent_update_now(cfg); } if (CHANGED_STR(c_description)) { log_debug("rpc", "change chassis description to %s", config->c_description ? config->c_description : "(NULL)"); free(cfg->g_config.c_description); cfg->g_config.c_description = xstrdup(config->c_description); lldpd_update_localchassis(cfg); levent_update_now(cfg); } if (CHANGED_STR(c_platform)) { log_debug("rpc", "change platform description to %s", config->c_platform ? config->c_platform : "(NULL)"); free(cfg->g_config.c_platform); cfg->g_config.c_platform = xstrdup(config->c_platform); lldpd_update_localchassis(cfg); levent_update_now(cfg); } if (CHANGED_STR(c_hostname)) { log_debug("rpc", "change system name to %s", config->c_hostname ? config->c_hostname : "(NULL)"); free(cfg->g_config.c_hostname); cfg->g_config.c_hostname = xstrdup(config->c_hostname); lldpd_update_localchassis(cfg); levent_update_now(cfg); } if (CHANGED(c_set_ifdescr)) { log_debug("rpc", "%s setting of interface description based on discovered neighbors", config->c_set_ifdescr ? "enable" : "disable"); cfg->g_config.c_set_ifdescr = config->c_set_ifdescr; levent_update_now(cfg); } if (CHANGED(c_promisc)) { log_debug("rpc", "%s promiscuous mode on managed interfaces", config->c_promisc ? "enable" : "disable"); cfg->g_config.c_promisc = config->c_promisc; levent_update_now(cfg); } if (CHANGED(c_cap_advertise)) { log_debug("rpc", "%s chassis capabilities advertisement", config->c_cap_advertise ? "enable" : "disable"); cfg->g_config.c_cap_advertise = config->c_cap_advertise; levent_update_now(cfg); } if (CHANGED(c_cap_override)) { log_debug("rpc", "%s chassis capabilities override", config->c_cap_override ? "enable" : "disable"); cfg->g_config.c_cap_override = config->c_cap_override; levent_update_now(cfg); } if (CHANGED(c_mgmt_advertise)) { log_debug("rpc", "%s management addresses advertisement", config->c_mgmt_advertise ? "enable" : "disable"); cfg->g_config.c_mgmt_advertise = config->c_mgmt_advertise; levent_update_now(cfg); } if (CHANGED(c_bond_slave_src_mac_type)) { if (config->c_bond_slave_src_mac_type > LLDP_BOND_SLAVE_SRC_MAC_TYPE_UNKNOWN && config->c_bond_slave_src_mac_type <= LLDP_BOND_SLAVE_SRC_MAC_TYPE_MAX) { log_debug("rpc", "change bond src mac type to %d", config->c_bond_slave_src_mac_type); cfg->g_config.c_bond_slave_src_mac_type = config->c_bond_slave_src_mac_type; } else { log_info("rpc", "Invalid bond slave src mac type: %d\n", config->c_bond_slave_src_mac_type); } } lldpd_config_cleanup(config); free(config); return 0; } /* Return the list of interfaces. Input: nothing. Output: list of interface names (lldpd_interface_list) */ static ssize_t client_handle_get_interfaces(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { struct lldpd_interface *iff, *iff_next; struct lldpd_hardware *hardware; ssize_t output_len; /* Build the list of interfaces */ struct lldpd_interface_list ifs; log_debug("rpc", "client request the list of interfaces"); TAILQ_INIT(&ifs); TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) { if ((iff = (struct lldpd_interface *)malloc( sizeof(struct lldpd_interface))) == NULL) fatal("rpc", NULL); iff->name = hardware->h_ifname; TAILQ_INSERT_TAIL(&ifs, iff, next); } output_len = lldpd_interface_list_serialize(&ifs, output); if (output_len <= 0) { output_len = 0; *type = NONE; } /* Free the temporary list */ for (iff = TAILQ_FIRST(&ifs); iff != NULL; iff = iff_next) { iff_next = TAILQ_NEXT(iff, next); TAILQ_REMOVE(&ifs, iff, next); free(iff); } return output_len; } /** * Set local chassis info * Input: chassis object * Output: updated chassis object */ static ssize_t client_handle_set_local_chassis(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { struct lldpd_chassis *chassis = NULL; struct lldpd_chassis *local_chassis = NULL; #ifdef ENABLE_LLDPMED struct utsname un; #endif log_debug("rpc", "client request a change in chassis configuration"); if (lldpd_chassis_unserialize(input, input_len, &chassis) <= 0) { *type = NONE; return 0; } local_chassis = LOCAL_CHASSIS(cfg); #ifdef ENABLE_LLDPMED free(local_chassis->c_med_hw); local_chassis->c_med_hw = (!chassis->c_med_hw) ? dmi_hw() : strdup(chassis->c_med_hw); // Follows lldpd.c - only set sw if advertising is enabled if (cfg->g_config.c_advertise_version) { free(local_chassis->c_med_sw); if (!chassis->c_med_sw) { if (uname(&un) < 0) { log_warn("rpc", "Could not get default uname. Will continue anyway."); local_chassis->c_med_sw = NULL; } else { local_chassis->c_med_sw = strdup(un.release); } } else { local_chassis->c_med_sw = strdup(chassis->c_med_sw); } } free(local_chassis->c_med_fw); local_chassis->c_med_fw = (!chassis->c_med_fw) ? dmi_fw() : strdup(chassis->c_med_fw); free(local_chassis->c_med_sn); local_chassis->c_med_sn = (!chassis->c_med_sn) ? dmi_sn() : strdup(chassis->c_med_sn); free(local_chassis->c_med_manuf); local_chassis->c_med_manuf = (!chassis->c_med_manuf) ? dmi_manuf() : strdup(chassis->c_med_manuf); free(local_chassis->c_med_model); local_chassis->c_med_model = (!chassis->c_med_model) ? dmi_model() : strdup(chassis->c_med_model); free(local_chassis->c_med_asset); local_chassis->c_med_asset = (!chassis->c_med_asset) ? dmi_asset() : strdup(chassis->c_med_asset); #endif if (chassis->c_cap_enabled != local_chassis->c_cap_enabled) { local_chassis->c_cap_enabled = chassis->c_cap_enabled; log_debug("rpc", "change capabilities enabled to: %d", local_chassis->c_cap_enabled); } #ifdef ENABLE_LLDPMED log_debug("rpc", "change hardware-revision to: %s", local_chassis->c_med_hw); log_debug("rpc", "change software-revision to: %s", local_chassis->c_med_sw); log_debug("rpc", "change firmware-revision to: %s", local_chassis->c_med_fw); log_debug("rpc", "change serial-number to: %s", local_chassis->c_med_sn); log_debug("rpc", "change manufacturer to: %s", local_chassis->c_med_manuf); log_debug("rpc", "change model to: %s", local_chassis->c_med_model); log_debug("rpc", "change asset to: %s", local_chassis->c_med_asset); #endif lldpd_chassis_cleanup(chassis, 1); ssize_t output_len = lldpd_chassis_serialize(local_chassis, output); if (output_len <= 0) { *type = NONE; return 0; } return output_len; } /* Return the local chassis. Input: nothing. Output: local chassis (lldpd_chassis) */ static ssize_t client_handle_get_local_chassis(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { struct lldpd_chassis *chassis = LOCAL_CHASSIS(cfg); ssize_t output_len; log_debug("rpc", "client request the local chassis"); output_len = lldpd_chassis_serialize(chassis, output); if (output_len <= 0) { output_len = 0; *type = NONE; } return output_len; } /* Return all available information related to an interface Input: name of the interface (serialized) Output: Information about the interface (lldpd_hardware) */ static ssize_t client_handle_get_interface(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { char *name; struct lldpd_hardware *hardware; void *p; /* Get name of the interface */ if (marshal_unserialize(string, input, input_len, &p) <= 0) { *type = NONE; return 0; } name = p; /* Search appropriate hardware */ log_debug("rpc", "client request interface %s", name); TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) if (!strcmp(hardware->h_ifname, name)) { ssize_t output_len = lldpd_hardware_serialize(hardware, output); free(name); if (output_len <= 0) { *type = NONE; return 0; } return output_len; } log_warnx("rpc", "no interface %s found", name); free(name); *type = NONE; return 0; } /* Return all available information related to an interface Input: name of the interface (serialized) Output: Information about the interface (lldpd_hardware) */ static ssize_t client_handle_get_default_port(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { log_debug("rpc", "client request the default local port"); ssize_t output_len = lldpd_port_serialize(cfg->g_default_local_port, output); if (output_len <= 0) { *type = NONE; return 0; } return output_len; } static int _client_handle_set_port(struct lldpd *cfg, struct lldpd_port *port, struct lldpd_port_set *set) { #ifdef ENABLE_LLDPMED struct lldpd_med_loc *loc = NULL; #endif if (set->local_id) { log_debug("rpc", "requested change to Port ID"); free(port->p_id); port->p_id = strdup(set->local_id); port->p_id_len = strlen(set->local_id); port->p_id_subtype = LLDP_PORTID_SUBTYPE_LOCAL; port->p_descr_force = 0; } if (set->local_descr) { log_debug("rpc", "requested change to Port Description"); free(port->p_descr); port->p_descr = strdup(set->local_descr); port->p_descr_force = 1; } switch (set->rxtx) { case LLDPD_RXTX_TXONLY: log_debug("rpc", "requested TX only mode"); port->p_disable_rx = 1; port->p_disable_tx = 0; break; case LLDPD_RXTX_RXONLY: log_debug("rpc", "requested RX only mode"); port->p_disable_rx = 0; port->p_disable_tx = 1; break; case LLDPD_RXTX_BOTH: log_debug("rpc", "requested RX/TX mode"); port->p_disable_rx = port->p_disable_tx = 0; break; case LLDPD_RXTX_DISABLED: log_debug("rpc", "requested disabled mode"); port->p_disable_rx = port->p_disable_tx = 1; break; } if (set->vlan_tx_enabled > -1) { port->p_vlan_tx_enabled = set->vlan_tx_enabled; port->p_vlan_tx_tag = set->vlan_tx_tag; } #ifdef ENABLE_LLDPMED if (set->med_policy && set->med_policy->type > 0) { log_debug("rpc", "requested change to MED policy"); if (set->med_policy->type > LLDP_MED_APPTYPE_LAST) { log_warnx("rpc", "invalid policy provided: %d", set->med_policy->type); return -1; } memcpy(&port->p_med_policy[set->med_policy->type - 1], set->med_policy, sizeof(struct lldpd_med_policy)); port->p_med_cap_enabled |= LLDP_MED_CAP_POLICY; } if (set->med_location && set->med_location->format > 0) { char *newdata = NULL; log_debug("rpc", "requested change to MED location"); if (set->med_location->format > LLDP_MED_LOCFORMAT_LAST) { log_warnx("rpc", "invalid location format provided: %d", set->med_location->format); return -1; } loc = &port->p_med_location[set->med_location->format - 1]; free(loc->data); memcpy(loc, set->med_location, sizeof(struct lldpd_med_loc)); if (!loc->data || !(newdata = malloc(loc->data_len))) loc->data_len = 0; if (newdata) memcpy(newdata, loc->data, loc->data_len); loc->data = newdata; port->p_med_cap_enabled |= LLDP_MED_CAP_LOCATION; } if (set->med_power) { log_debug("rpc", "requested change to MED power"); memcpy(&port->p_med_power, set->med_power, sizeof(struct lldpd_med_power)); switch (set->med_power->devicetype) { case LLDP_MED_POW_TYPE_PD: port->p_med_cap_enabled |= LLDP_MED_CAP_MDI_PD; port->p_med_cap_enabled &= ~LLDP_MED_CAP_MDI_PSE; break; case LLDP_MED_POW_TYPE_PSE: port->p_med_cap_enabled |= LLDP_MED_CAP_MDI_PSE; port->p_med_cap_enabled &= ~LLDP_MED_CAP_MDI_PD; break; } } #endif #ifdef ENABLE_DOT3 if (set->dot3_power) { log_debug("rpc", "requested change to Dot3 power"); memcpy(&port->p_power, set->dot3_power, sizeof(struct lldpd_dot3_power)); } #endif #ifdef ENABLE_CUSTOM if (set->custom_list_clear) { log_debug("rpc", "requested custom TLVs clear"); lldpd_custom_list_cleanup(port); } else { if (set->custom) { log_info("rpc", "custom TLV op %s oui %02x:%02x:%02x subtype %x", (set->custom_tlv_op == CUSTOM_TLV_REMOVE) ? "remove" : (set->custom_tlv_op == CUSTOM_TLV_ADD) ? "add" : "replace", set->custom->oui[0], set->custom->oui[1], set->custom->oui[2], set->custom->subtype); switch (set->custom_tlv_op) { case CUSTOM_TLV_REMOVE: lldpd_custom_tlv_cleanup(port, set->custom); break; case CUSTOM_TLV_ADD: lldpd_custom_tlv_add(port, set->custom); break; case CUSTOM_TLV_REPLACE: default: lldpd_custom_tlv_cleanup(port, set->custom); lldpd_custom_tlv_add(port, set->custom); break; } } } #endif return 0; } /* Set some port related settings (policy, location, power) Input: name of the interface, policy/location/power setting to be modified Output: nothing */ static ssize_t client_handle_set_port(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { int ret = 0; struct lldpd_port_set *set = NULL; struct lldpd_hardware *hardware = NULL; if (lldpd_port_set_unserialize(input, input_len, &set) <= 0) { *type = NONE; return 0; } if (!set->ifname) { log_warnx("rpc", "no interface provided"); goto set_port_finished; } /* Search the appropriate hardware */ if (strlen(set->ifname) == 0) { log_debug("rpc", "client request change to default port"); if (_client_handle_set_port(cfg, cfg->g_default_local_port, set) == -1) goto set_port_finished; ret = 1; } else { log_debug("rpc", "client request change to port %s", set->ifname); TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) { if (!strcmp(hardware->h_ifname, set->ifname)) { struct lldpd_port *port = &hardware->h_lport; if (_client_handle_set_port(cfg, port, set) == -1) goto set_port_finished; ret = 1; break; } } } if (ret == 0) log_warn("rpc", "no interface %s found", set->ifname); else levent_update_now(cfg); set_port_finished: if (!ret) *type = NONE; free(set->ifname); free(set->local_id); free(set->local_descr); #ifdef ENABLE_LLDPMED free(set->med_policy); if (set->med_location) free(set->med_location->data); free(set->med_location); free(set->med_power); #endif #ifdef ENABLE_DOT3 free(set->dot3_power); #endif #ifdef ENABLE_CUSTOM if (set->custom) { free(set->custom->oui_info); free(set->custom); } #endif free(set); return 0; } /* Register subscribtion to neighbor changes */ static ssize_t client_handle_subscribe(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len, void **output, int *subscribed) { log_debug("rpc", "client subscribe to changes"); *subscribed = 1; return 0; } struct client_handle { enum hmsg_type type; const char *name; ssize_t ( *handle)(struct lldpd *, enum hmsg_type *, void *, int, void **, int *); }; static struct client_handle client_handles[] = { { NONE, "None", client_handle_none }, { GET_CONFIG, "Get configuration", client_handle_get_configuration }, { SET_CONFIG, "Set configuration", client_handle_set_configuration }, { GET_INTERFACES, "Get interfaces", client_handle_get_interfaces }, { GET_INTERFACE, "Get interface", client_handle_get_interface }, { GET_DEFAULT_PORT, "Get default port", client_handle_get_default_port }, { SET_CHASSIS, "Set local chassis", client_handle_set_local_chassis }, { GET_CHASSIS, "Get local chassis", client_handle_get_local_chassis }, { SET_PORT, "Set port", client_handle_set_port }, { SUBSCRIBE, "Subscribe", client_handle_subscribe }, { 0, NULL } }; int client_handle_client(struct lldpd *cfg, ssize_t (*send)(void *, int, void *, size_t), void *out, enum hmsg_type type, void *buffer, size_t n, int *subscribed) { struct client_handle *ch; void *answer; ssize_t len, sent; log_debug("rpc", "handle client request"); for (ch = client_handles; ch->handle != NULL; ch++) { if (ch->type == type) { TRACE(LLDPD_CLIENT_REQUEST(ch->name)); answer = NULL; len = ch->handle(cfg, &type, buffer, n, &answer, subscribed); sent = send(out, type, answer, len); free(answer); return sent; } } log_warnx("rpc", "unknown message request (%d) received", type); return -1; }