xref: /freebsd/sys/netinet/libalias/alias_smedia.c (revision e0c4386e)
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 <errno.h>
108 #include <sys/types.h>
109 #include <stdio.h>
110 #include <string.h>
111 #endif
112 
113 #include <netinet/in_systm.h>
114 #include <netinet/in.h>
115 #include <netinet/ip.h>
116 #include <netinet/tcp.h>
117 
118 #ifdef _KERNEL
119 #include <netinet/libalias/alias.h>
120 #include <netinet/libalias/alias_local.h>
121 #include <netinet/libalias/alias_mod.h>
122 #else
123 #include "alias_local.h"
124 #include "alias_mod.h"
125 #endif
126 
127 #define RTSP_CONTROL_PORT_NUMBER_1 554
128 #define RTSP_CONTROL_PORT_NUMBER_2 7070
129 #define TFTP_PORT_NUMBER 69
130 
131 static void
132 AliasHandleRtspOut(struct libalias *, struct ip *, struct alias_link *,
133     int maxpacketsize);
134 static int
135 fingerprint(struct libalias *la, struct alias_data *ah)
136 {
137 	if (ah->dport != NULL && ah->aport != NULL && ah->sport != NULL &&
138 	    ntohs(*ah->dport) == TFTP_PORT_NUMBER)
139 		return (0);
140 	if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL ||
141 	    ah->maxpktsize == 0)
142 		return (-1);
143 	if (ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_1
144 	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_1
145 	    || ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_2
146 	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_2)
147 		return (0);
148 	return (-1);
149 }
150 
151 static int
152 protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
153 {
154 	if (ntohs(*ah->dport) == TFTP_PORT_NUMBER)
155 		FindRtspOut(la, pip->ip_src, pip->ip_dst,
156 		    *ah->sport, *ah->aport, IPPROTO_UDP);
157 	else AliasHandleRtspOut(la, pip, ah->lnk, ah->maxpktsize);
158 	return (0);
159 }
160 
161 struct proto_handler handlers[] = {
162 	{
163 	  .pri = 100,
164 	  .dir = OUT,
165 	  .proto = TCP|UDP,
166 	  .fingerprint = &fingerprint,
167 	  .protohandler = &protohandler
168 	},
169 	{ EOH }
170 };
171 
172 static int
173 mod_handler(module_t mod, int type, void *data)
174 {
175 	int error;
176 
177 	switch (type) {
178 	case MOD_LOAD:
179 		error = 0;
180 		LibAliasAttachHandlers(handlers);
181 		break;
182 	case MOD_UNLOAD:
183 		error = 0;
184 		LibAliasDetachHandlers(handlers);
185 		break;
186 	default:
187 		error = EINVAL;
188 	}
189 	return (error);
190 }
191 
192 #ifdef _KERNEL
193 static
194 #endif
195 moduledata_t alias_mod = {
196        "alias_smedia", mod_handler, NULL
197 };
198 
199 #ifdef _KERNEL
200 DECLARE_MODULE(alias_smedia, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
201 MODULE_VERSION(alias_smedia, 1);
202 MODULE_DEPEND(alias_smedia, libalias, 1, 1, 1);
203 #endif
204 
205 #define RTSP_CONTROL_PORT_NUMBER_1    554
206 #define RTSP_CONTROL_PORT_NUMBER_2   7070
207 #define RTSP_PORT_GROUP			2
208 
209 #define ISDIGIT(a) (((a) >= '0') && ((a) <= '9'))
210 
211 static int
212 search_string(char *data, int dlen, const char *search_str)
213 {
214 	int i, j, k;
215 	int search_str_len;
216 
217 	search_str_len = strlen(search_str);
218 	for (i = 0; i < dlen - search_str_len; i++) {
219 		for (j = i, k = 0; j < dlen - search_str_len; j++, k++) {
220 			if (data[j] != search_str[k] &&
221 			    data[j] != search_str[k] - ('a' - 'A'))
222 				break;
223 			if (k == search_str_len - 1)
224 				return (j + 1);
225 		}
226 	}
227 	return (-1);
228 }
229 
230 static int
231 alias_rtsp_out(struct libalias *la, struct ip *pip,
232     struct alias_link *lnk,
233     char *data,
234     const char *port_str)
235 {
236 	int hlen, tlen, dlen;
237 	struct tcphdr *tc;
238 	int i, j, pos, state, port_dlen, new_dlen, delta;
239 	u_short p[2], new_len;
240 	u_short sport, eport, base_port;
241 	u_short salias = 0, ealias = 0, base_alias = 0;
242 	const char *transport_str = "transport:";
243 	char newdata[2048], *port_data, *port_newdata, stemp[80];
244 	int links_created = 0, pkt_updated = 0;
245 	struct alias_link *rtsp_lnk = NULL;
246 	struct in_addr null_addr;
247 
248 	/* Calculate data length of TCP packet */
249 	tc = (struct tcphdr *)ip_next(pip);
250 	hlen = (pip->ip_hl + tc->th_off) << 2;
251 	tlen = ntohs(pip->ip_len);
252 	dlen = tlen - hlen;
253 
254 	/* Find keyword, "Transport: " */
255 	pos = search_string(data, dlen, transport_str);
256 	if (pos < 0)
257 		return (-1);
258 
259 	port_data = data + pos;
260 	port_dlen = dlen - pos;
261 
262 	memcpy(newdata, data, pos);
263 	port_newdata = newdata + pos;
264 
265 	while (port_dlen > (int)strlen(port_str)) {
266 		/* Find keyword, appropriate port string */
267 		pos = search_string(port_data, port_dlen, port_str);
268 		if (pos < 0)
269 			break;
270 
271 		memcpy(port_newdata, port_data, pos + 1);
272 		port_newdata += (pos + 1);
273 
274 		p[0] = p[1] = 0;
275 		sport = eport = 0;
276 		state = 0;
277 		for (i = pos; i < port_dlen; i++) {
278 			switch (state) {
279 			case 0:
280 				if (port_data[i] == '=')
281 					state++;
282 				break;
283 			case 1:
284 				if (ISDIGIT(port_data[i]))
285 					p[0] = p[0] * 10 + port_data[i] - '0';
286 				else if (port_data[i] == ';')
287 					state = 3;
288 				else if (port_data[i] == '-')
289 					state++;
290 				break;
291 			case 2:
292 				if (ISDIGIT(port_data[i]))
293 					p[1] = p[1] * 10 + port_data[i] - '0';
294 				else
295 					state++;
296 				break;
297 			case 3:
298 				base_port = p[0];
299 				sport = htons(p[0]);
300 				eport = htons(p[1]);
301 
302 				if (!links_created) {
303 					links_created = 1;
304 					/*
305 					 * Find an even numbered port
306 					 * number base that satisfies the
307 					 * contiguous number of ports we
308 					 * need
309 					 */
310 					null_addr.s_addr = 0;
311 					if (0 == (salias = FindNewPortGroup(la, null_addr,
312 					    FindAliasAddress(la, pip->ip_src),
313 					    sport, 0,
314 					    RTSP_PORT_GROUP,
315 					    IPPROTO_UDP, 1))) {
316 #ifdef LIBALIAS_DEBUG
317 						fprintf(stderr,
318 						    "PacketAlias/RTSP: Cannot find contiguous RTSP data ports\n");
319 #endif
320 					} else {
321 						base_alias = ntohs(salias);
322 						for (j = 0; j < RTSP_PORT_GROUP; j++) {
323 							/*
324 							 * Establish link
325 							 * to port found in
326 							 * RTSP packet
327 							 */
328 							rtsp_lnk = FindRtspOut(la, GetOriginalAddress(lnk), null_addr,
329 							    htons(base_port + j), htons(base_alias + j),
330 							    IPPROTO_UDP);
331 							if (rtsp_lnk != NULL) {
332 #ifndef NO_FW_PUNCH
333 								/*
334 								 * Punch
335 								 * hole in
336 								 * firewall
337 								 */
338 								PunchFWHole(rtsp_lnk);
339 #endif
340 							} else {
341 #ifdef LIBALIAS_DEBUG
342 								fprintf(stderr,
343 								    "PacketAlias/RTSP: Cannot allocate RTSP data ports\n");
344 #endif
345 								break;
346 							}
347 						}
348 					}
349 					ealias = htons(base_alias + (RTSP_PORT_GROUP - 1));
350 				}
351 				if (salias && rtsp_lnk) {
352 					pkt_updated = 1;
353 
354 					/* Copy into IP packet */
355 					sprintf(stemp, "%d", ntohs(salias));
356 					memcpy(port_newdata, stemp, strlen(stemp));
357 					port_newdata += strlen(stemp);
358 
359 					if (eport != 0) {
360 						*port_newdata = '-';
361 						port_newdata++;
362 
363 						/* Copy into IP packet */
364 						sprintf(stemp, "%d", ntohs(ealias));
365 						memcpy(port_newdata, stemp, strlen(stemp));
366 						port_newdata += strlen(stemp);
367 					}
368 					*port_newdata = ';';
369 					port_newdata++;
370 				}
371 				state++;
372 				break;
373 			}
374 			if (state > 3) {
375 				break;
376 			}
377 		}
378 		port_data += i;
379 		port_dlen -= i;
380 	}
381 
382 	if (!pkt_updated)
383 		return (-1);
384 
385 	memcpy(port_newdata, port_data, port_dlen);
386 	port_newdata += port_dlen;
387 	*port_newdata = '\0';
388 
389 	/* Create new packet */
390 	new_dlen = port_newdata - newdata;
391 	memcpy(data, newdata, new_dlen);
392 
393 	SetAckModified(lnk);
394 	tc = (struct tcphdr *)ip_next(pip);
395 	delta = GetDeltaSeqOut(tc->th_seq, lnk);
396 	AddSeq(lnk, delta + new_dlen - dlen, pip->ip_hl, pip->ip_len,
397 	    tc->th_seq, tc->th_off);
398 
399 	new_len = htons(hlen + new_dlen);
400 	DifferentialChecksum(&pip->ip_sum, &new_len, &pip->ip_len, 1);
401 	pip->ip_len = new_len;
402 
403 	tc->th_sum = 0;
404 #ifdef _KERNEL
405 	tc->th_x2 = (TH_RES1 >> 8);
406 #else
407 	tc->th_sum = TcpChecksum(pip);
408 #endif
409 	return (0);
410 }
411 
412 /* Support the protocol used by early versions of RealPlayer */
413 
414 static int
415 alias_pna_out(struct libalias *la, struct ip *pip,
416     struct alias_link *lnk,
417     char *data,
418     int dlen)
419 {
420 	struct alias_link *pna_links;
421 	u_short msg_id, msg_len;
422 	char *work;
423 	u_short alias_port, port;
424 	struct tcphdr *tc;
425 
426 	work = data;
427 	work += 5;
428 	while (work + 4 < data + dlen) {
429 		memcpy(&msg_id, work, 2);
430 		work += 2;
431 		memcpy(&msg_len, work, 2);
432 		work += 2;
433 		if (ntohs(msg_id) == 0) /* end of options */
434 			return (0);
435 
436 		if ((ntohs(msg_id) == 1) || (ntohs(msg_id) == 7)) {
437 			memcpy(&port, work, 2);
438 			pna_links = FindUdpTcpOut(la, pip->ip_src, GetDestAddress(lnk),
439 			    port, 0, IPPROTO_UDP, 1);
440 			if (pna_links != NULL) {
441 #ifndef NO_FW_PUNCH
442 				/* Punch hole in firewall */
443 				PunchFWHole(pna_links);
444 #endif
445 				tc = (struct tcphdr *)ip_next(pip);
446 				alias_port = GetAliasPort(pna_links);
447 				memcpy(work, &alias_port, 2);
448 
449 				/* Compute TCP checksum for revised packet */
450 				tc->th_sum = 0;
451 #ifdef _KERNEL
452 				tc->th_x2 = (TH_RES1 >> 8);
453 #else
454 				tc->th_sum = TcpChecksum(pip);
455 #endif
456 			}
457 		}
458 		work += ntohs(msg_len);
459 	}
460 
461 	return (0);
462 }
463 
464 static void
465 AliasHandleRtspOut(struct libalias *la, struct ip *pip, struct alias_link *lnk, int maxpacketsize)
466 {
467 	int hlen, tlen, dlen;
468 	struct tcphdr *tc;
469 	char *data;
470 	const char *setup = "SETUP", *pna = "PNA", *str200 = "200";
471 	const char *okstr = "OK", *client_port_str = "client_port";
472 	const char *server_port_str = "server_port";
473 	int i, parseOk;
474 
475 	(void)maxpacketsize;
476 
477 	tc = (struct tcphdr *)ip_next(pip);
478 	hlen = (pip->ip_hl + tc->th_off) << 2;
479 	tlen = ntohs(pip->ip_len);
480 	dlen = tlen - hlen;
481 
482 	data = (char *)pip;
483 	data += hlen;
484 
485 	/* When aliasing a client, check for the SETUP request */
486 	if ((ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_1) ||
487 	    (ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_2)) {
488 		if (dlen >= (int)strlen(setup) &&
489 		    memcmp(data, setup, strlen(setup)) == 0) {
490 			alias_rtsp_out(la, pip, lnk, data, client_port_str);
491 			return;
492 		}
493 
494 		if (dlen >= (int)strlen(pna) &&
495 		    memcmp(data, pna, strlen(pna)) == 0)
496 			alias_pna_out(la, pip, lnk, data, dlen);
497 	} else {
498 		/*
499 		 * When aliasing a server, check for the 200 reply
500 		 * Accommodate varying number of blanks between 200 & OK
501 		 */
502 
503 		if (dlen >= (int)strlen(str200)) {
504 			for (parseOk = 0, i = 0;
505 			    i <= dlen - (int)strlen(str200);
506 			    i++)
507 				if (memcmp(&data[i], str200, strlen(str200)) == 0) {
508 					parseOk = 1;
509 					break;
510 				}
511 
512 			if (parseOk) {
513 				i += strlen(str200);	/* skip string found */
514 				while (data[i] == ' ')	/* skip blank(s) */
515 					i++;
516 
517 				if ((dlen - i) >= (int)strlen(okstr))
518 					if (memcmp(&data[i], okstr, strlen(okstr)) == 0)
519 						alias_rtsp_out(la, pip, lnk, data, server_port_str);
520 			}
521 		}
522 	}
523 }
524