blob: 255430b8cc535f8c54dc0e4f3d3b00352f3eae4e [file] [log] [blame]
Oliver Smith3a9f2672019-11-20 10:56:35 +01001/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
2 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <string.h>
20#include <sys/types.h>
21#include <sys/socket.h>
22#include <netdb.h>
23#include <unistd.h>
24#include <errno.h>
25#include <osmocom/core/select.h>
26#include <osmocom/gsm/gsm_utils.h>
27#include <osmocom/hlr/logging.h>
28#include <osmocom/mslookup/mdns.h>
29#include <osmocom/mslookup/mdns_sock.h>
30#include <osmocom/mslookup/mslookup_client.h>
31#include <osmocom/mslookup/mslookup_client_mdns.h>
32
33struct osmo_mdns_method_state {
34 /* Parameters passed by _add_method_dns() */
35 struct osmo_sockaddr_str bind_addr;
36 const char *domain_suffix;
37
38 struct osmo_mdns_sock *mc;
39
40 struct osmo_mslookup_client *client;
41 struct llist_head requests;
42 uint16_t next_packet_id;
43};
44
45struct osmo_mdns_method_request {
46 struct llist_head entry;
47 uint32_t request_handle;
48 struct osmo_mslookup_query query;
49 uint16_t packet_id;
50};
51
52static int request_handle_by_query(uint32_t *request_handle, struct osmo_mdns_method_state *state,
53 struct osmo_mslookup_query *query, uint16_t packet_id)
54{
55 struct osmo_mdns_method_request *request;
56
57 llist_for_each_entry(request, &state->requests, entry) {
58 if (strcmp(request->query.service, query->service) != 0)
59 continue;
60 if (osmo_mslookup_id_cmp(&request->query.id, &query->id) != 0)
61 continue;
62
63 /* Match! */
64 *request_handle = request->request_handle;
65 return 0;
66 }
67 return -1;
68}
69
70static int mdns_method_recv(struct osmo_fd *osmo_fd, unsigned int what)
71{
72 struct osmo_mdns_method_state *state = osmo_fd->data;
73 struct osmo_mslookup_result result;
74 struct osmo_mslookup_query query;
75 uint16_t packet_id;
76 int n;
77 uint8_t buffer[1024];
78 uint32_t request_handle = 0;
79 void *ctx = state;
80
81 n = read(osmo_fd->fd, buffer, sizeof(buffer));
82 if (n < 0) {
83 LOGP(DMSLOOKUP, LOGL_ERROR, "failed to read from socket\n");
84 return n;
85 }
86
87 if (osmo_mdns_result_decode(ctx, buffer, n, &packet_id, &query, &result, state->domain_suffix) < 0)
88 return -EINVAL;
89
90 if (request_handle_by_query(&request_handle, state, &query, packet_id) != 0)
91 return -EINVAL;
92
93 osmo_mslookup_client_rx_result(state->client, request_handle, &result);
94 return n;
95}
96
97static void mdns_method_request(struct osmo_mslookup_client_method *method, const struct osmo_mslookup_query *query,
98 uint32_t request_handle)
99{
100 char buf[256];
101 struct osmo_mdns_method_state *state = method->priv;
102 struct msgb *msg;
103 struct osmo_mdns_method_request *r = talloc_zero(method->client, struct osmo_mdns_method_request);
104
105 *r = (struct osmo_mdns_method_request){
106 .request_handle = request_handle,
107 .query = *query,
108 .packet_id = state->next_packet_id,
109 };
110 llist_add(&r->entry, &state->requests);
111 state->next_packet_id++;
112
113 msg = osmo_mdns_query_encode(method->client, r->packet_id, query, state->domain_suffix);
114 if (!msg) {
115 LOGP(DMSLOOKUP, LOGL_ERROR, "Cannot encode request: %s\n",
116 osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
Oliver Smith89afb7f2020-01-13 15:00:13 +0100117 return;
Oliver Smith3a9f2672019-11-20 10:56:35 +0100118 }
119
120 /* Send over the wire */
121 LOGP(DMSLOOKUP, LOGL_DEBUG, "sending mDNS query: %s.%s\n", query->service,
122 osmo_mslookup_id_name_b(buf, sizeof(buf), &query->id));
123 if (osmo_mdns_sock_send(state->mc, msg) == -1)
124 LOGP(DMSLOOKUP, LOGL_ERROR, "sending mDNS query failed\n");
125}
126
127static void mdns_method_request_cleanup(struct osmo_mslookup_client_method *method, uint32_t request_handle)
128{
129 struct osmo_mdns_method_state *state = method->priv;
130
131 /* Tear down any state associated with this handle. */
132 struct osmo_mdns_method_request *r;
133 llist_for_each_entry(r, &state->requests, entry) {
134 if (r->request_handle != request_handle)
135 continue;
136 llist_del(&r->entry);
137 talloc_free(r);
138 return;
139 }
140}
141
142static void mdns_method_destruct(struct osmo_mslookup_client_method *method)
143{
144 struct osmo_mdns_method_state *state = method->priv;
145 struct osmo_mdns_method_request *e, *n;
146 if (!state)
147 return;
148
149 /* Drop all DNS lookup request state. Triggering a timeout event and cleanup for mslookup client users will
150 * happen in the mslookup_client.c, we will simply stop responding from this lookup method. */
151 llist_for_each_entry_safe(e, n, &state->requests, entry) {
152 llist_del(&e->entry);
153 }
154
155 osmo_mdns_sock_cleanup(state->mc);
156}
157
158/*! Initialize the mDNS lookup method.
159 * \param[in] client the client to attach the method to.
160 * \param[in] ip IPv4 or IPv6 address string.
161 * \param[in] port The port to bind to.
162 * \param[in] initial_packet_id Used in the first mslookup query, then increased by one in each following query. All
163 * servers answer to each query with the same packet ID. Set to -1 to use a random
164 * initial ID (recommended unless you need deterministic output). This ID is for visually
165 * distinguishing the packets in packet sniffers, the mslookup client uses not just the
166 * ID, but all query parameters (service type, ID, ID type), to determine if a reply is
167 * relevant.
168 * \param[in] domain_suffix is appended to each domain in the queries to avoid colliding with the top-level domains
169 * administrated by IANA. Example: "mdns.osmocom.org" */
170struct osmo_mslookup_client_method *osmo_mslookup_client_add_mdns(struct osmo_mslookup_client *client, const char *ip,
171 uint16_t port, int initial_packet_id,
172 const char *domain_suffix)
173{
174 struct osmo_mdns_method_state *state;
175 struct osmo_mslookup_client_method *m;
176
177 m = talloc_zero(client, struct osmo_mslookup_client_method);
178 OSMO_ASSERT(m);
179
180 state = talloc_zero(m, struct osmo_mdns_method_state);
181 OSMO_ASSERT(state);
182 INIT_LLIST_HEAD(&state->requests);
183 if (osmo_sockaddr_str_from_str(&state->bind_addr, ip, port)) {
184 LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: invalid address/port: %s %u\n",
185 ip, port);
186 goto error_cleanup;
187 }
188
189 if (initial_packet_id == -1) {
190 if (osmo_get_rand_id((uint8_t *)&state->next_packet_id, 2) < 0) {
191 LOGP(DMSLOOKUP, LOGL_ERROR, "mslookup mDNS: failed to generate random initial packet ID\n");
192 goto error_cleanup;
193 }
194 } else
195 state->next_packet_id = initial_packet_id;
196
197 state->client = client;
198 state->domain_suffix = domain_suffix;
199
200 state->mc = osmo_mdns_sock_init(state, ip, port, mdns_method_recv, state, 0);
201 if (!state->mc)
202 goto error_cleanup;
203
204 *m = (struct osmo_mslookup_client_method){
205 .name = "mDNS",
206 .priv = state,
207 .request = mdns_method_request,
208 .request_cleanup = mdns_method_request_cleanup,
209 .destruct = mdns_method_destruct,
210 };
211
212 osmo_mslookup_client_method_add(client, m);
213 return m;
214
215error_cleanup:
216 talloc_free(m);
217 return NULL;
218}
219
220const struct osmo_sockaddr_str *osmo_mslookup_client_method_mdns_get_bind_addr(struct osmo_mslookup_client_method *dns_method)
221{
222 struct osmo_mdns_method_state *state;
223 if (!dns_method || !dns_method->priv)
224 return NULL;
225 state = dns_method->priv;
226 return &state->bind_addr;
227}
228
229const char *osmo_mslookup_client_method_mdns_get_domain_suffix(struct osmo_mslookup_client_method *dns_method)
230{
231 struct osmo_mdns_method_state *state;
232 if (!dns_method || !dns_method->priv)
233 return NULL;
234 state = dns_method->priv;
235 return state->domain_suffix;
236}