xref: /freebsd/sys/netinet/libalias/alias_smedia.c (revision 271b8f0f090836393d835219c37ae7e41ac39357)
1 /*-
2  * alias_smedia.c
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Copyright (c) 2000 Whistle Communications, Inc.
7  * All rights reserved.
8  *
9  * Subject to the following obligations and disclaimer of warranty, use and
10  * redistribution of this software, in source or object code forms, with or
11  * without modifications are expressly permitted by Whistle Communications;
12  * provided, however, that:
13  * 1. Any and all reproductions of the source or object code must include the
14  *    copyright notice above and the following disclaimer of warranties; and
15  * 2. No rights are granted, in any manner or form, to use Whistle
16  *    Communications, Inc. trademarks, including the mark "WHISTLE
17  *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
18  *    such appears in the above copyright notice or in the software.
19  *
20  * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
21  * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
22  * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
23  * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
24  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
25  * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
26  * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
27  * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
28  * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
29  * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
30  * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
31  * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
32  * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
33  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35  * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
36  * OF SUCH DAMAGE.
37  *
38  * Copyright (c) 2000  Junichi SATOH <junichi@astec.co.jp>
39  *                                   <junichi@junichi.org>
40  * All rights reserved.
41  *
42  * Redistribution and use in source and binary forms, with or without
43  * modification, are permitted provided that the following conditions
44  * are met:
45  * 1. Redistributions of source code must retain the above copyright
46  *    notice, this list of conditions and the following disclaimer.
47  * 2. Redistributions in binary form must reproduce the above copyright
48  *    notice, this list of conditions and the following disclaimer in the
49  *    documentation and/or other materials provided with the distribution.
50  *
51  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
52  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
53  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
54  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
55  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
56  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
57  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
58  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
59  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
60  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61  * SUCH DAMAGE.
62  *
63  * Authors: Erik Salander <erik@whistle.com>
64  *          Junichi SATOH <junichi@astec.co.jp>
65  *                        <junichi@junichi.org>
66  */
67 
68 #include <sys/cdefs.h>
69 /*
70    Alias_smedia.c is meant to contain the aliasing code for streaming media
71    protocols.  It performs special processing for RSTP sessions under TCP.
72    Specifically, when a SETUP request is sent by a client, or a 200 reply
73    is sent by a server, it is intercepted and modified.  The address is
74    changed to the gateway machine and an aliasing port is used.
75 
76    More specifically, the "client_port" configuration parameter is
77    parsed for SETUP requests.  The "server_port" configuration parameter is
78    parsed for 200 replies eminating from a server.  This is intended to handle
79    the unicast case.
80 
81    RTSP also allows a redirection of a stream to another client by using the
82    "destination" configuration parameter.  The destination config parm would
83    indicate a different IP address.  This function is NOT supported by the
84    RTSP translation code below.
85 
86    The RTSP multicast functions without any address translation intervention.
87 
88    For this routine to work, the SETUP/200 must fit entirely
89    into a single TCP packet.  This is typically the case, but exceptions
90    can easily be envisioned under the actual specifications.
91 
92    Probably the most troubling aspect of the approach taken here is
93    that the new SETUP/200 will typically be a different length, and
94    this causes a certain amount of bookkeeping to keep track of the
95    changes of sequence and acknowledgment numbers, since the client
96    machine is totally unaware of the modification to the TCP stream.
97 
98    Initial version:  May, 2000 (eds)
99 */
100 
101 #ifdef _KERNEL
102 #include <sys/param.h>
103 #include <sys/systm.h>
104 #include <sys/kernel.h>
105 #include <sys/module.h>
106 #else
107 #include <sys/types.h>
108 #include <errno.h>
109 #include <stdbool.h>
110 #include <stdio.h>
111 #include <string.h>
112 #endif
113 
114 #include <netinet/in_systm.h>
115 #include <netinet/in.h>
116 #include <netinet/ip.h>
117 #include <netinet/tcp.h>
118 
119 #ifdef _KERNEL
120 #include <netinet/libalias/alias.h>
121 #include <netinet/libalias/alias_local.h>
122 #include <netinet/libalias/alias_mod.h>
123 #else
124 #include "alias_local.h"
125 #include "alias_mod.h"
126 #endif
127 
128 #define RTSP_CONTROL_PORT_NUMBER_1 554
129 #define RTSP_CONTROL_PORT_NUMBER_2 7070
130 #define TFTP_PORT_NUMBER 69
131 
132 static void AliasHandleRtspOut(struct libalias *, struct ip *,
133     struct alias_link *, size_t);
134 
135 static int
fingerprint(struct libalias * la,struct alias_data * ah)136 fingerprint(struct libalias *la, struct alias_data *ah)
137 {
138 	if (ah->dport != NULL && ah->aport != NULL && ah->sport != NULL &&
139 	    ntohs(*ah->dport) == TFTP_PORT_NUMBER)
140 		return (0);
141 	if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL ||
142 	    ah->maxpktsize == 0)
143 		return (-1);
144 	if (ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_1
145 	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_1
146 	    || ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_2
147 	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_2)
148 		return (0);
149 	return (-1);
150 }
151 
152 static int
protohandler(struct libalias * la,struct ip * pip,struct alias_data * ah)153 protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
154 {
155 	if (ntohs(*ah->dport) == TFTP_PORT_NUMBER)
156 		FindRtspOut(la, pip->ip_src, pip->ip_dst,
157 		    *ah->sport, *ah->aport, IPPROTO_UDP);
158 	else
159 		AliasHandleRtspOut(la, pip, ah->lnk, ah->maxpktsize);
160 	return (0);
161 }
162 
163 struct proto_handler handlers[] = {
164 	{
165 	  .pri = 100,
166 	  .dir = OUT,
167 	  .proto = TCP|UDP,
168 	  .fingerprint = &fingerprint,
169 	  .protohandler = &protohandler
170 	},
171 	{ EOH }
172 };
173 
174 static int
mod_handler(module_t mod,int type,void * data)175 mod_handler(module_t mod, int type, void *data)
176 {
177 	int error;
178 
179 	switch (type) {
180 	case MOD_LOAD:
181 		error = 0;
182 		LibAliasAttachHandlers(handlers);
183 		break;
184 	case MOD_UNLOAD:
185 		error = 0;
186 		LibAliasDetachHandlers(handlers);
187 		break;
188 	default:
189 		error = EINVAL;
190 	}
191 	return (error);
192 }
193 
194 #ifdef _KERNEL
195 static
196 #endif
197 moduledata_t alias_mod = {
198        "alias_smedia", mod_handler, NULL
199 };
200 
201 #ifdef _KERNEL
202 DECLARE_MODULE(alias_smedia, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
203 MODULE_VERSION(alias_smedia, 1);
204 MODULE_DEPEND(alias_smedia, libalias, 1, 1, 1);
205 #endif
206 
207 #define RTSP_CONTROL_PORT_NUMBER_1    554
208 #define RTSP_CONTROL_PORT_NUMBER_2   7070
209 #define RTSP_PORT_GROUP			2
210 
211 #define ISDIGIT(a) (((a) >= '0') && ((a) <= '9'))
212 
213 static ssize_t
search_string(char * data,size_t dlen,const char * search_str)214 search_string(char *data, size_t dlen, const char *search_str)
215 {
216 	size_t i, j, k;
217 	size_t search_str_len;
218 
219 	search_str_len = strlen(search_str);
220 	if (search_str_len > dlen)
221 		return (-1);
222 	for (i = 0; i < dlen - search_str_len; i++) {
223 		for (j = i, k = 0; j < dlen - search_str_len; j++, k++) {
224 			if (data[j] != search_str[k] &&
225 			    data[j] != search_str[k] - ('a' - 'A'))
226 				break;
227 			if (k == search_str_len - 1)
228 				return (j + 1);
229 		}
230 	}
231 	return (-1);
232 }
233 
234 static int
alias_rtsp_out(struct libalias * la,struct ip * pip,struct alias_link * lnk,char * data,size_t maxlen,const char * port_str)235 alias_rtsp_out(struct libalias *la, struct ip *pip,
236     struct alias_link *lnk,
237     char *data, size_t maxlen,
238     const char *port_str)
239 {
240 	size_t hlen, tlen, dlen;
241 	size_t i, j;
242 	struct tcphdr *tc;
243 	int delta, state;
244 	ssize_t pos, slen;
245 	size_t new_dlen, port_dlen, port_slen;
246 	u_short p[2], new_len;
247 	u_short sport, eport, base_port;
248 	u_short salias = 0, ealias = 0, base_alias = 0;
249 	const char *transport_str = "transport:";
250 	char *newdata, *port_data;
251 	bool links_created = false, pkt_updated = false;
252 	struct alias_link *rtsp_lnk = NULL;
253 	struct in_addr null_addr;
254 
255 	/* Calculate data length of TCP packet */
256 	tc = (struct tcphdr *)ip_next(pip);
257 	hlen = (pip->ip_hl + tc->th_off) << 2;
258 	tlen = ntohs(pip->ip_len);
259 	dlen = tlen - hlen;
260 	if (hlen > tlen || tlen > maxlen)
261 		return (-1);
262 	port_slen = strlen(port_str);
263 
264 	/* Find keyword, "Transport: " */
265 	pos = search_string(data, dlen, transport_str);
266 	if (pos < 0)
267 		return (-1);
268 
269 	port_data = data + pos;
270 	port_dlen = dlen - pos;
271 
272 	/* Allocate temporary buffer */
273 	maxlen -= hlen;
274 	if ((newdata = malloc(maxlen)) == NULL)
275 		return (-1);
276 	memcpy(newdata, data, pos);
277 	new_dlen = pos;
278 
279 	while (port_dlen > port_slen) {
280 		/* Find keyword, appropriate port string */
281 		pos = search_string(port_data, port_dlen, port_str);
282 		if (pos < 0 || (size_t)pos + 1 > maxlen - new_dlen)
283 			break;
284 
285 		memcpy(newdata + new_dlen, port_data, pos + 1);
286 		new_dlen += pos + 1;
287 
288 		p[0] = p[1] = 0;
289 		sport = eport = 0;
290 		state = 0;
291 		for (i = pos; i < port_dlen; i++) {
292 			switch (state) {
293 			case 0:
294 				if (port_data[i] == '=')
295 					state++;
296 				break;
297 			case 1:
298 				if (ISDIGIT(port_data[i]))
299 					p[0] = p[0] * 10 + port_data[i] - '0';
300 				else if (port_data[i] == ';')
301 					state = 3;
302 				else if (port_data[i] == '-')
303 					state++;
304 				break;
305 			case 2:
306 				if (ISDIGIT(port_data[i]))
307 					p[1] = p[1] * 10 + port_data[i] - '0';
308 				else
309 					state++;
310 				break;
311 			case 3:
312 				base_port = p[0];
313 				sport = htons(p[0]);
314 				eport = htons(p[1]);
315 
316 				if (!links_created) {
317 					links_created = true;
318 					/*
319 					 * Find an even numbered port
320 					 * number base that satisfies the
321 					 * contiguous number of ports we
322 					 * need
323 					 */
324 					null_addr.s_addr = 0;
325 					if (0 == (salias = FindNewPortGroup(la, null_addr,
326 					    FindAliasAddress(la, pip->ip_src),
327 					    sport, 0,
328 					    RTSP_PORT_GROUP,
329 					    IPPROTO_UDP, 1))) {
330 #ifdef LIBALIAS_DEBUG
331 						fprintf(stderr,
332 						    "PacketAlias/RTSP: Cannot find contiguous RTSP data ports\n");
333 #endif
334 					} else {
335 						base_alias = ntohs(salias);
336 						for (j = 0; j < RTSP_PORT_GROUP; j++) {
337 							/*
338 							 * Establish link
339 							 * to port found in
340 							 * RTSP packet
341 							 */
342 							rtsp_lnk = FindRtspOut(la, GetOriginalAddress(lnk), null_addr,
343 							    htons(base_port + j), htons(base_alias + j),
344 							    IPPROTO_UDP);
345 							if (rtsp_lnk != NULL) {
346 #ifndef NO_FW_PUNCH
347 								/*
348 								 * Punch
349 								 * hole in
350 								 * firewall
351 								 */
352 								PunchFWHole(rtsp_lnk);
353 #endif
354 							} else {
355 #ifdef LIBALIAS_DEBUG
356 								fprintf(stderr,
357 								    "PacketAlias/RTSP: Cannot allocate RTSP data ports\n");
358 #endif
359 								break;
360 							}
361 						}
362 					}
363 					ealias = htons(base_alias + (RTSP_PORT_GROUP - 1));
364 				}
365 				if (salias && rtsp_lnk) {
366 					pkt_updated = true;
367 
368 					/* Copy into IP packet */
369 					slen = snprintf(newdata + new_dlen,
370 					    maxlen - new_dlen, "%d", ntohs(salias));
371 					if (slen < 0 || slen >= maxlen - new_dlen)
372 						goto fail;
373 					new_dlen += slen;
374 
375 					if (eport != 0) {
376 						if (new_dlen == maxlen)
377 							goto fail;
378 						newdata[new_dlen++] = '-';
379 
380 						/* Copy into IP packet */
381 						slen = snprintf(newdata + new_dlen,
382 						    maxlen - new_dlen, "%d", ntohs(ealias));
383 						if (slen < 0 || slen >= maxlen - new_dlen)
384 							goto fail;
385 						new_dlen += slen;
386 					}
387 					if (new_dlen == maxlen)
388 						goto fail;
389 					newdata[new_dlen++] = ';';
390 				}
391 				state++;
392 				break;
393 			}
394 			if (state > 3) {
395 				break;
396 			}
397 		}
398 		port_data += i;
399 		port_dlen -= i;
400 	}
401 
402 	if (!pkt_updated)
403 		goto fail;
404 
405 	/* Create new packet */
406 	if (new_dlen + port_dlen > maxlen)
407 		goto fail;
408 	memmove(data + new_dlen, port_data, port_dlen);
409 	memcpy(data, newdata, new_dlen);
410 	new_dlen += port_dlen;
411 	free(newdata);
412 
413 	SetAckModified(lnk);
414 	tc = (struct tcphdr *)ip_next(pip);
415 	delta = GetDeltaSeqOut(tc->th_seq, lnk);
416 	AddSeq(lnk, delta + (int)(new_dlen - dlen), pip->ip_hl, pip->ip_len,
417 	    tc->th_seq, tc->th_off);
418 
419 	new_len = htons(hlen + new_dlen);
420 	DifferentialChecksum(&pip->ip_sum, &new_len, &pip->ip_len, 1);
421 	pip->ip_len = new_len;
422 
423 	tc->th_sum = 0;
424 #ifdef _KERNEL
425 	tcp_set_flags(tc, tcp_get_flags(tc) | TH_RES1);
426 #else
427 	tc->th_sum = TcpChecksum(pip);
428 #endif
429 	return (0);
430 fail:
431 	free(newdata);
432 	return (-1);
433 }
434 
435 /* Support the protocol used by early versions of RealPlayer */
436 
437 static int
alias_pna_out(struct libalias * la,struct ip * pip,struct alias_link * lnk,char * data,size_t dlen)438 alias_pna_out(struct libalias *la, struct ip *pip,
439     struct alias_link *lnk,
440     char *data,
441     size_t dlen)
442 {
443 	struct alias_link *pna_links;
444 	char *work;
445 	struct tcphdr *tc;
446 	u_short msg_id, msg_len;
447 	u_short alias_port, port;
448 
449 	work = data;
450 	work += 5;
451 	while (work + 4 < data + dlen) {
452 		memcpy(&msg_id, work, 2);
453 		work += 2;
454 		memcpy(&msg_len, work, 2);
455 		work += 2;
456 		if (ntohs(msg_id) == 0) /* end of options */
457 			return (0);
458 
459 		if (ntohs(msg_id) == 1 || ntohs(msg_id) == 7) {
460 			memcpy(&port, work, 2);
461 			(void)FindUdpTcpOut(la, pip->ip_src, GetDestAddress(lnk),
462 			    port, 0, IPPROTO_UDP, 1, &pna_links);
463 			if (pna_links != NULL) {
464 #ifndef NO_FW_PUNCH
465 				/* Punch hole in firewall */
466 				PunchFWHole(pna_links);
467 #endif
468 				tc = (struct tcphdr *)ip_next(pip);
469 				alias_port = GetAliasPort(pna_links);
470 				memcpy(work, &alias_port, 2);
471 
472 				/* Compute TCP checksum for revised packet */
473 				tc->th_sum = 0;
474 #ifdef _KERNEL
475 				tcp_set_flags(tc, tcp_get_flags(tc) | TH_RES1);
476 #else
477 				tc->th_sum = TcpChecksum(pip);
478 #endif
479 			}
480 		}
481 		work += ntohs(msg_len);
482 	}
483 
484 	return (0);
485 }
486 
487 static void
AliasHandleRtspOut(struct libalias * la,struct ip * pip,struct alias_link * lnk,size_t maxlen)488 AliasHandleRtspOut(struct libalias *la, struct ip *pip, struct alias_link *lnk,
489     size_t maxlen)
490 {
491 	struct tcphdr *tc;
492 	char *data;
493 	const char *setup = "SETUP", *pna = "PNA", *str200 = "200";
494 	const char *okstr = "OK", *client_port_str = "client_port";
495 	const char *server_port_str = "server_port";
496 	size_t hlen, tlen, dlen;
497 	size_t i;
498 
499 	tc = (struct tcphdr *)ip_next(pip);
500 	hlen = (pip->ip_hl + tc->th_off) << 2;
501 	tlen = ntohs(pip->ip_len);
502 	dlen = tlen - hlen;
503 	if (hlen > tlen || tlen > maxlen)
504 		return;
505 
506 	data = (char *)pip;
507 	data += hlen;
508 
509 	/* When aliasing a client, check for the SETUP request */
510 	if ((ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_1) ||
511 	    (ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_2)) {
512 		if (dlen >= strlen(setup) &&
513 		    memcmp(data, setup, strlen(setup)) == 0) {
514 			alias_rtsp_out(la, pip, lnk, data, maxlen,
515 			    client_port_str);
516 			return;
517 		}
518 
519 		if (dlen >= strlen(pna) &&
520 		    memcmp(data, pna, strlen(pna)) == 0)
521 			alias_pna_out(la, pip, lnk, data, dlen);
522 	} else {
523 		/*
524 		 * When aliasing a server, check for the 200 reply
525 		 * Accommodate varying number of blanks between 200 & OK
526 		 */
527 		if (dlen < strlen(str200) + 1 + strlen(okstr))
528 			return;
529 		for (i = 0; i <= dlen - strlen(str200) - 1; i++) {
530 			if (memcmp(&data[i], str200, strlen(str200)) == 0 &&
531 			    data[i + strlen(str200)] == ' ') {
532 				i += strlen(str200);	/* skip 200 */
533 				while (i < dlen && data[i] == ' ')	/* skip blank(s) */
534 					i++;
535 				if (dlen - i >= strlen(okstr)) {
536 					if (memcmp(&data[i], okstr, strlen(okstr)) == 0) {
537 						alias_rtsp_out(la, pip, lnk, data,
538 						    maxlen, server_port_str);
539 						break;
540 					}
541 				}
542 			}
543 		}
544 	}
545 }
546