1 /**
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 
25 static const char *sw = "ice stunsrv v" VERSION " (" ARCH "/" OS ")";
26 
27 
triggered_check(struct icem * icem,struct ice_cand * lcand,struct ice_cand * rcand)28 static 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  */
handle_stun_full(struct icem * icem,struct icem_comp * comp,const struct sa * src,uint32_t prio,bool use_cand,bool tunnel)95 static 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  */
handle_stun_lite(struct icem * icem,struct icem_comp * comp,const struct sa * src,bool use_cand)176 static 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 
stunsrv_ereply(struct icem_comp * comp,const struct sa * src,size_t presz,const struct stun_msg * req,uint16_t scode,const char * reason)217 static 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 
icem_stund_recv(struct icem_comp * comp,const struct sa * src,struct stun_msg * req,size_t presz)230 int 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 }
325