blob: 5f73f1dc1d6723cfd43be651227c90e7c6874ac2 [file] [log] [blame]
James Kuszmaul82f6c042021-01-17 11:30:16 -08001/**
2 * @file stunsrv.c Basic STUN Server for Connectivity checks
3 *
4 * Copyright (C) 2010 Creytiv.com
5 */
6#include <string.h>
7#include <re_types.h>
8#include <re_fmt.h>
9#include <re_mem.h>
10#include <re_mbuf.h>
11#include <re_list.h>
12#include <re_tmr.h>
13#include <re_sa.h>
14#include <re_stun.h>
15#include <re_ice.h>
16#include <re_sys.h>
17#include "ice.h"
18
19
20#define DEBUG_MODULE "stunsrv"
21#define DEBUG_LEVEL 5
22#include <re_dbg.h>
23
24
25static const char *sw = "ice stunsrv v" VERSION " (" ARCH "/" OS ")";
26
27
28static void triggered_check(struct icem *icem, struct ice_cand *lcand,
29 struct ice_cand *rcand)
30{
31 struct ice_candpair *cp = NULL;
32 int err;
33
34 if (lcand && rcand)
35 cp = icem_candpair_find(&icem->checkl, lcand, rcand);
36
37 if (cp) {
38
39 switch (cp->state) {
40
41#if 0
42 /* TODO: I am not sure why we should cancel the
43 * pending Connectivity check here. this
44 * can lead to a deadlock situation where
45 * both agents are stuck on sending
46 * triggered checks on the same candidate pair
47 */
48 case ICE_CANDPAIR_INPROGRESS:
49 icem_candpair_cancel(cp);
50 /*@fallthrough@*/
51#endif
52
53 case ICE_CANDPAIR_FAILED:
54 icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING);
55 /*@fallthrough@*/
56
57 case ICE_CANDPAIR_FROZEN:
58 case ICE_CANDPAIR_WAITING:
59 err = icem_conncheck_send(cp, false, true);
60 if (err) {
61 DEBUG_WARNING("triggered check failed\n");
62 }
63 break;
64
65 case ICE_CANDPAIR_SUCCEEDED:
66 default:
67 break;
68 }
69 }
70 else {
71
72#if 0
73 err = icem_candpair_alloc(&cp, icem, lcand, rcand);
74 if (err) {
75 DEBUG_WARNING("failed to allocate candpair:"
76 " lcand=%p rcand=%p (%m)\n",
77 lcand, rcand, err);
78 return;
79 }
80
81 icem_candpair_prio_order(&icem->checkl);
82
83 icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING);
84
85 (void)icem_conncheck_send(cp, false, true);
86#endif
87
88 }
89}
90
91
92/*
93 * 7.2.1. Additional Procedures for Full Implementations
94 */
95static int handle_stun_full(struct icem *icem,
96 struct icem_comp *comp, const struct sa *src,
97 uint32_t prio, bool use_cand, bool tunnel)
98{
99 struct ice_cand *lcand = NULL, *rcand;
100 struct ice_candpair *cp = NULL;
101 int err;
102
103 rcand = icem_cand_find(&icem->rcandl, comp->id, src);
104 if (!rcand) {
105 err = icem_rcand_add_prflx(&rcand, icem, comp->id, prio, src);
106 if (err)
107 return err;
108 }
109
110 cp = icem_candpair_find_rcand(icem, rcand);
111 if (cp)
112 lcand = cp->lcand;
113 else
114 lcand = icem_lcand_find_checklist(icem, comp->id);
115
116 if (!lcand) {
117 DEBUG_WARNING("{%s.%u} local candidate not found"
118 " (checklist=%u) (src=%J)\n",
119 icem->name, comp->id,
120 list_count(&icem->checkl), src);
121 return 0;
122 }
123
124 triggered_check(icem, lcand, rcand);
125
126 if (!cp) {
127 cp = icem_candpair_find_rcand(icem, rcand);
128 if (!cp) {
129 DEBUG_WARNING("{%s.%u} candidate pair not found:"
130 " source=%J\n",
131 icem->name, comp->id, src);
132 return 0;
133 }
134 }
135
136#if ICE_TRACE
137 icecomp_printf(comp, "Rx Binding Request from %J via %s"
138 " (candpair=%s) %s\n",
139 src, tunnel ? "Tunnel" : "Socket",
140 cp ? ice_candpair_state2name(cp->state) : "n/a",
141 use_cand ? "[USE]" : "");
142#else
143 (void)tunnel;
144#endif
145
146 /* 7.2.1.5. Updating the Nominated Flag */
147 if (use_cand) {
148 if (icem->lrole == ICE_ROLE_CONTROLLED &&
149 cp->state == ICE_CANDPAIR_SUCCEEDED) {
150
151 if (!cp->nominated) {
152 icecomp_printf(comp, "setting NOMINATED"
153 " flag on candpair [%H]\n",
154 icem_candpair_debug, cp);
155 }
156
157 cp->nominated = true;
158 }
159
160 /* Cancel conncheck. Choose Selected Pair */
161 icem_candpair_make_valid(cp);
162
163 if (icem->conf.nom == ICE_NOMINATION_REGULAR) {
164 icem_candpair_cancel(cp);
165 icem_comp_set_selected(comp, cp);
166 }
167 }
168
169 return 0;
170}
171
172
173/*
174 * 7.2.2. Additional Procedures for Lite Implementations
175 */
176static int handle_stun_lite(struct icem *icem,
177 struct icem_comp *comp, const struct sa *src,
178 bool use_cand)
179{
180 struct ice_cand *lcand, *rcand;
181 struct ice_candpair *cp;
182 int err;
183
184 if (!use_cand)
185 return 0;
186
187 rcand = icem_cand_find(&icem->rcandl, comp->id, src);
188 if (!rcand) {
189 DEBUG_WARNING("lite: could not find remote candidate\n");
190 return 0;
191 }
192
193 /* find the local host candidate with the same component */
194 lcand = icem_cand_find(&icem->lcandl, comp->id, NULL);
195 if (!lcand) {
196 DEBUG_WARNING("lite: could not find local candidate\n");
197 return 0;
198 }
199
200 /* search validlist for existing candpair's */
201 if (icem_candpair_find(&icem->validl, lcand, rcand))
202 return 0;
203
204 err = icem_candpair_alloc(&cp, icem, lcand, rcand);
205 if (err) {
206 DEBUG_WARNING("lite: failed to created candidate pair\n");
207 return err;
208 }
209
210 icem_candpair_make_valid(cp);
211 cp->nominated = true;
212
213 return 0;
214}
215
216
217static int stunsrv_ereply(struct icem_comp *comp, const struct sa *src,
218 size_t presz, const struct stun_msg *req,
219 uint16_t scode, const char *reason)
220{
221 struct icem *icem = comp->icem;
222
223 return stun_ereply(icem->proto, comp->sock, src, presz, req,
224 scode, reason,
225 (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 1,
226 STUN_ATTR_SOFTWARE, sw);
227}
228
229
230int icem_stund_recv(struct icem_comp *comp, const struct sa *src,
231 struct stun_msg *req, size_t presz)
232{
233 struct icem *icem = comp->icem;
234 struct stun_attr *attr;
235 struct pl lu, ru;
236 enum ice_role rrole = ICE_ROLE_UNKNOWN;
237 uint64_t tiebrk = 0;
238 uint32_t prio_prflx;
239 bool use_cand = false;
240 int err;
241
242 /* RFC 5389: Fingerprint errors are silently discarded */
243 err = stun_msg_chk_fingerprint(req);
244 if (err)
245 return err;
246
247 err = stun_msg_chk_mi(req, (uint8_t *)icem->lpwd, strlen(icem->lpwd));
248 if (err) {
249 if (err == EBADMSG)
250 goto unauth;
251 else
252 goto badmsg;
253 }
254
255 attr = stun_msg_attr(req, STUN_ATTR_USERNAME);
256 if (!attr)
257 goto badmsg;
258
259 err = re_regex(attr->v.username, strlen(attr->v.username),
260 "[^:]+:[^]+", &lu, &ru);
261 if (err) {
262 DEBUG_WARNING("could not parse USERNAME attribute (%s)\n",
263 attr->v.username);
264 goto unauth;
265 }
266 if (pl_strcmp(&lu, icem->lufrag))
267 goto unauth;
268 if (str_isset(icem->rufrag) && pl_strcmp(&ru, icem->rufrag))
269 goto unauth;
270
271 attr = stun_msg_attr(req, STUN_ATTR_CONTROLLED);
272 if (attr) {
273 rrole = ICE_ROLE_CONTROLLED;
274 tiebrk = attr->v.uint64;
275 }
276
277 attr = stun_msg_attr(req, STUN_ATTR_CONTROLLING);
278 if (attr) {
279 rrole = ICE_ROLE_CONTROLLING;
280 tiebrk = attr->v.uint64;
281 }
282
283 if (rrole == icem->lrole) {
284 if (icem->tiebrk >= tiebrk)
285 ice_switch_local_role(icem);
286 else
287 goto conflict;
288 }
289
290 attr = stun_msg_attr(req, STUN_ATTR_PRIORITY);
291 if (attr)
292 prio_prflx = attr->v.uint32;
293 else
294 goto badmsg;
295
296 attr = stun_msg_attr(req, STUN_ATTR_USE_CAND);
297 if (attr)
298 use_cand = true;
299
300 if (icem->lmode == ICE_MODE_FULL) {
301 err = handle_stun_full(icem, comp, src, prio_prflx,
302 use_cand, presz > 0);
303 }
304 else {
305 err = handle_stun_lite(icem, comp, src, use_cand);
306 }
307
308 if (err)
309 goto badmsg;
310
311 return stun_reply(icem->proto, comp->sock, src, presz, req,
312 (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 2,
313 STUN_ATTR_XOR_MAPPED_ADDR, src,
314 STUN_ATTR_SOFTWARE, sw);
315
316 badmsg:
317 return stunsrv_ereply(comp, src, presz, req, 400, "Bad Request");
318
319 unauth:
320 return stunsrv_ereply(comp, src, presz, req, 401, "Unauthorized");
321
322 conflict:
323 return stunsrv_ereply(comp, src, presz, req, 487, "Role Conflict");
324}