| /* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> |
| * |
| * All Rights Reserved |
| * |
| * This program is free software; you can redistribute 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <netdb.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <osmocom/core/select.h> |
| #include <osmocom/gsm/gsm_utils.h> |
| #include <osmocom/hlr/logging.h> |
| #include <osmocom/mslookup/mdns.h> |
| #include <osmocom/mslookup/mdns_sock.h> |
| #include <osmocom/mslookup/mslookup_client.h> |
| #include <osmocom/mslookup/mslookup_client_mdns.h> |
| |
| struct osmo_mdns_method_state { |
| /* Parameters passed by _add_method_dns() */ |
| struct osmo_sockaddr_str bind_addr; |
| const char *domain_suffix; |
| |
| struct osmo_mdns_sock *mc; |
| |
| struct osmo_mslookup_client *client; |
| struct llist_head requests; |
| uint16_t next_packet_id; |
| }; |
| |
| struct osmo_mdns_method_request { |
| struct llist_head entry; |
| uint32_t request_handle; |
| struct osmo_mslookup_query query; |
| uint16_t packet_id; |
| }; |
| |
| static int request_handle_by_query(uint32_t *request_handle, struct osmo_mdns_method_state *state, |
| struct osmo_mslookup_query *query, uint16_t packet_id) |
| { |
| struct osmo_mdns_method_request *request; |
| |
| llist_for_each_entry(request, &state->requests, entry) { |
| if (strcmp(request->query.service, query->service) != 0) |
| continue; |
| if (osmo_mslookup_id_cmp(&request->query.id, &query->id) != 0) |
| continue; |
| |
| /* Match! */ |
| *request_handle = request->request_handle; |
| return 0; |
| } |
| return -1; |
| } |
| |
| static int mdns_method_recv(struct osmo_fd *osmo_fd, unsigned int what) |
| { |
| struct osmo_mdns_method_state *state = osmo_fd->data; |
| struct osmo_mslookup_result result; |
| struct osmo_mslookup_query query; |
| uint16_t packet_id; |
| int n; |
| uint8_t buffer[1024]; |
| uint32_t request_handle = 0; |
| void *ctx = state; |
| |
| n = read(osmo_fd->fd, buffer, sizeof(buffer)); |
| if (n < 0) { |
| LOGP(DMSLOOKUP, LOGL_ERROR, "failed to read from socket\n"); |
| return n; |
| } |
| |
| if (osmo_mdns_result_decode(ctx, buffer, n, &packet_id, &query, &result, state->domain_suffix) < 0) |
| return -EINVAL; |
| |
| if (request_handle_by_query(&request_handle, state, &query, packet_id) != 0) |
| return -EINVAL; |
| |
| osmo_mslookup_client_rx_result(state->client, request_handle, &result); |
| return n; |
| } |
| |
| static void mdns_method_request(struct osmo_mslookup_client_method *method, const struct osmo_mslookup_query *query, |
| uint32_t request_handle) |
| { |
| char buf[256]; |
| struct osmo_mdns_method_state *state = method->priv; |
| struct msgb *msg; |
| struct osmo_mdns_method_request *r = talloc_zero(method->client, struct osmo_mdns_method_request); |
| |
| *r = (struct osmo_mdns_method_request){ |
| .request_handle = request_handle, |
| .query = *query, |
| .packet_id = state->next_packet_id, |
| }; |
| llist_add(&r->entry, &state->requests); |
| state->next_packet_id++; |
| |
| msg = osmo_mdns_query_encode(method->client, r->packet_id, query, state->domain_suffix); |
| if (!msg) { |
| LOGP(DMSLOOKUP, LOGL_ERROR, "Cannot encode request: %s\n", |
| osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL)); |
| return; |
| } |
| |
| /* Send over the wire */ |
| LOGP(DMSLOOKUP, LOGL_DEBUG, "sending mDNS query: %s.%s\n", query->service, |
| osmo_mslookup_id_name_b(buf, sizeof(buf), &query->id)); |
| if (osmo_mdns_sock_send(state->mc, msg) == -1) |
| LOGP(DMSLOOKUP, LOGL_ERROR, "sending mDNS query failed\n"); |
| } |
| |
| static void mdns_method_request_cleanup(struct osmo_mslookup_client_method *method, uint32_t request_handle) |
| { |
| struct osmo_mdns_method_state *state = method->priv; |
| |
| /* Tear down any state associated with this handle. */ |
| struct osmo_mdns_method_request *r; |
| llist_for_each_entry(r, &state->requests, entry) { |
| if (r->request_handle != request_handle) |
| continue; |
| llist_del(&r->entry); |
| talloc_free(r); |
| return; |
| } |
| } |
| |
| static void mdns_method_destruct(struct osmo_mslookup_client_method *method) |
| { |
| struct osmo_mdns_method_state *state = method->priv; |
| struct osmo_mdns_method_request *e, *n; |
| if (!state) |
| return; |
| |
| /* Drop all DNS lookup request state. Triggering a timeout event and cleanup for mslookup client users will |
| * happen in the mslookup_client.c, we will simply stop responding from this lookup method. */ |
| llist_for_each_entry_safe(e, n, &state->requests, entry) { |
| llist_del(&e->entry); |
| } |
| |
| osmo_mdns_sock_cleanup(state->mc); |
| } |
| |
| /*! Initialize the mDNS lookup method. |
| * \param[in] client the client to attach the method to. |
| * \param[in] ip IPv4 or IPv6 address string. |
| * \param[in] port The port to bind to. |
| * \param[in] initial_packet_id Used in the first mslookup query, then increased by one in each following query. All |
| * servers answer to each query with the same packet ID. Set to -1 to use a random |
| * initial ID (recommended unless you need deterministic output). This ID is for visually |
| * distinguishing the packets in packet sniffers, the mslookup client uses not just the |
| * ID, but all query parameters (service type, ID, ID type), to determine if a reply is |
| * relevant. |
| * \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains |
| * administrated by IANA. Example: "mdns.osmocom.org" */ |
| struct osmo_mslookup_client_method *osmo_mslookup_client_add_mdns(struct osmo_mslookup_client *client, const char *ip, |
| uint16_t port, int initial_packet_id, |
| const char *domain_suffix) |
| { |
| struct osmo_mdns_method_state *state; |
| struct osmo_mslookup_client_method *m; |
| |
| m = talloc_zero(client, struct osmo_mslookup_client_method); |
| OSMO_ASSERT(m); |
| |
| state = talloc_zero(m, struct osmo_mdns_method_state); |
| OSMO_ASSERT(state); |
| INIT_LLIST_HEAD(&state->requests); |
| if (osmo_sockaddr_str_from_str(&state->bind_addr, ip, port)) { |
| LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: invalid address/port: %s %u\n", |
| ip, port); |
| goto error_cleanup; |
| } |
| |
| if (initial_packet_id == -1) { |
| if (osmo_get_rand_id((uint8_t *)&state->next_packet_id, 2) < 0) { |
| LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: failed to generate random initial packet ID\n"); |
| goto error_cleanup; |
| } |
| } else |
| state->next_packet_id = initial_packet_id; |
| |
| state->client = client; |
| state->domain_suffix = domain_suffix; |
| |
| state->mc = osmo_mdns_sock_init(state, ip, port, mdns_method_recv, state, 0); |
| if (!state->mc) |
| goto error_cleanup; |
| |
| *m = (struct osmo_mslookup_client_method){ |
| .name = "mDNS", |
| .priv = state, |
| .request = mdns_method_request, |
| .request_cleanup = mdns_method_request_cleanup, |
| .destruct = mdns_method_destruct, |
| }; |
| |
| osmo_mslookup_client_method_add(client, m); |
| return m; |
| |
| error_cleanup: |
| talloc_free(m); |
| return NULL; |
| } |
| |
| const struct osmo_sockaddr_str *osmo_mslookup_client_method_mdns_get_bind_addr(struct osmo_mslookup_client_method *dns_method) |
| { |
| struct osmo_mdns_method_state *state; |
| if (!dns_method || !dns_method->priv) |
| return NULL; |
| state = dns_method->priv; |
| return &state->bind_addr; |
| } |
| |
| const char *osmo_mslookup_client_method_mdns_get_domain_suffix(struct osmo_mslookup_client_method *dns_method) |
| { |
| struct osmo_mdns_method_state *state; |
| if (!dns_method || !dns_method->priv) |
| return NULL; |
| state = dns_method->priv; |
| return state->domain_suffix; |
| } |