alias_skinny.c revision 133121
1/*-
2 * alias_skinny.c
3 *
4 * Copyright (c) 2002, 2003 MarcusCom, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 * Author: Joe Marcus Clarke <marcus@FreeBSD.org>
29 *
30 * $FreeBSD: head/sys/netinet/libalias/alias_skinny.c 133121 2004-08-04 15:17:08Z marcus $
31 */
32
33#include <stdio.h>
34#include <string.h>
35#include <sys/types.h>
36#include <sys/socket.h>
37#include <netinet/in_systm.h>
38#include <netinet/in.h>
39#include <arpa/inet.h>
40#include <netinet/ip.h>
41#include <netinet/tcp.h>
42#include <netinet/udp.h>
43#include <unistd.h>
44
45#include "alias_local.h"
46
47/*
48 * alias_skinny.c handles the translation for the Cisco Skinny Station
49 * protocol.  Skinny typically uses TCP port 2000 to set up calls between
50 * a Cisco Call Manager and a Cisco IP phone.  When a phone comes on line,
51 * it first needs to register with the Call Manager.  To do this it sends
52 * a registration message.  This message contains the IP address of the
53 * IP phone.  This message must then be translated to reflect our global
54 * IP address.  Along with the registration message (and usually in the
55 * same packet), the phone sends an IP port message.  This message indicates
56 * the TCP port over which it will communicate.
57 *
58 * When a call is placed from the phone, the Call Manager will send an
59 * Open Receive Channel message to the phone to let the caller know someone
60 * has answered.  The phone then sends back an Open Receive Channel
61 * Acknowledgement.  In this packet, the phone sends its IP address again,
62 * and the UDP port over which the voice traffic should flow.  These values
63 * need translation.  Right after the Open Receive Channel Acknowledgement,
64 * the Call Manager sends a Start Media Transmission message indicating the
65 * call is connected.  This message contains the IP address and UDP port
66 * number of the remote (called) party.  Once this message is translated, the
67 * call can commence.  The called part sends the first UDP packet to the
68 * calling phone at the pre-arranged UDP port in the Open Receive Channel
69 * Acknowledgement.
70 *
71 * Skinny is a Cisco-proprietary protocol and is a trademark of Cisco Systems,
72 * Inc.  All rights reserved.
73*/
74
75/* #define DEBUG 1 */
76
77/* Message types that need translating */
78#define REG_MSG         0x00000001
79#define IP_PORT_MSG     0x00000002
80#define OPNRCVCH_ACK    0x00000022
81#define START_MEDIATX   0x0000008a
82
83struct skinny_header {
84	u_int32_t	len;
85	u_int32_t	reserved;
86	u_int32_t	msgId;
87};
88
89struct RegisterMessage {
90	u_int32_t	msgId;
91	char		devName   [16];
92	u_int32_t	uid;
93	u_int32_t	instance;
94	u_int32_t	ipAddr;
95	u_char		devType;
96	u_int32_t	maxStreams;
97};
98
99struct IpPortMessage {
100	u_int32_t	msgId;
101	u_int32_t	stationIpPort;	/* Note: Skinny uses 32-bit port
102					 * numbers */
103};
104
105struct OpenReceiveChannelAck {
106	u_int32_t	msgId;
107	u_int32_t	status;
108	u_int32_t	ipAddr;
109	u_int32_t	port;
110	u_int32_t	passThruPartyID;
111};
112
113struct StartMediaTransmission {
114	u_int32_t	msgId;
115	u_int32_t	conferenceID;
116	u_int32_t	passThruPartyID;
117	u_int32_t	remoteIpAddr;
118	u_int32_t	remotePort;
119	u_int32_t	MSPacket;
120	u_int32_t	payloadCap;
121	u_int32_t	precedence;
122	u_int32_t	silenceSuppression;
123	u_short		maxFramesPerPacket;
124	u_int32_t	G723BitRate;
125};
126
127typedef enum {
128	ClientToServer = 0,
129	ServerToClient = 1
130} ConvDirection;
131
132
133static int
134alias_skinny_reg_msg(struct RegisterMessage *reg_msg, struct ip *pip,
135    struct tcphdr *tc, struct alias_link *lnk,
136    ConvDirection direction)
137{
138	(void)direction;
139
140	reg_msg->ipAddr = (u_int32_t) GetAliasAddress(lnk).s_addr;
141
142	tc->th_sum = 0;
143	tc->th_sum = TcpChecksum(pip);
144
145	return (0);
146}
147
148static int
149alias_skinny_startmedia(struct StartMediaTransmission *start_media,
150    struct ip *pip, struct tcphdr *tc,
151    struct alias_link *lnk, u_int32_t localIpAddr,
152    ConvDirection direction)
153{
154	struct in_addr dst, src;
155
156	(void)pip;
157	(void)tc;
158	(void)lnk;
159	(void)direction;
160
161	dst.s_addr = start_media->remoteIpAddr;
162	src.s_addr = localIpAddr;
163
164	/*
165	 * XXX I should probably handle in bound global translations as
166	 * well.
167	 */
168
169	return (0);
170}
171
172static int
173alias_skinny_port_msg(struct IpPortMessage *port_msg, struct ip *pip,
174    struct tcphdr *tc, struct alias_link *lnk,
175    ConvDirection direction)
176{
177	(void)direction;
178
179	port_msg->stationIpPort = (u_int32_t) ntohs(GetAliasPort(lnk));
180
181	tc->th_sum = 0;
182	tc->th_sum = TcpChecksum(pip);
183
184	return (0);
185}
186
187static int
188alias_skinny_opnrcvch_ack(struct libalias *la, struct OpenReceiveChannelAck *opnrcvch_ack,
189    struct ip *pip, struct tcphdr *tc,
190    struct alias_link *lnk, u_int32_t * localIpAddr,
191    ConvDirection direction)
192{
193	struct in_addr null_addr;
194	struct alias_link *opnrcv_lnk;
195	u_int32_t localPort;
196
197	(void)lnk;
198	(void)direction;
199
200	*localIpAddr = (u_int32_t) opnrcvch_ack->ipAddr;
201	localPort = opnrcvch_ack->port;
202
203	null_addr.s_addr = INADDR_ANY;
204	opnrcv_lnk = FindUdpTcpOut(la, pip->ip_src, null_addr,
205	    htons((u_short) opnrcvch_ack->port), 0,
206	    IPPROTO_UDP, 1);
207	opnrcvch_ack->ipAddr = (u_int32_t) GetAliasAddress(opnrcv_lnk).s_addr;
208	opnrcvch_ack->port = (u_int32_t) ntohs(GetAliasPort(opnrcv_lnk));
209
210	tc->th_sum = 0;
211	tc->th_sum = TcpChecksum(pip);
212
213	return (0);
214}
215
216void
217AliasHandleSkinny(struct libalias *la, struct ip *pip, struct alias_link *lnk)
218{
219	int hlen, tlen, dlen;
220	struct tcphdr *tc;
221	int32_t msgId, len, t, lip;
222	struct skinny_header *sd;
223	int orig_len, skinny_hdr_len = sizeof(struct skinny_header);
224	ConvDirection direction;
225
226	tc = (struct tcphdr *)ip_next(pip);
227	hlen = (pip->ip_hl + tc->th_off) << 2;
228	tlen = ntohs(pip->ip_len);
229	dlen = tlen - hlen;
230
231	sd = (struct skinny_header *)tcp_next(tc);
232
233	/*
234	 * XXX This direction is reserved for future use.  I still need to
235	 * handle the scenario where the call manager is on the inside, and
236	 * the calling phone is on the global outside.
237	 */
238	if (ntohs(tc->th_dport) == la->skinnyPort) {
239		direction = ClientToServer;
240	} else if (ntohs(tc->th_sport) == la->skinnyPort) {
241		direction = ServerToClient;
242	} else {
243#ifdef DEBUG
244		fprintf(stderr,
245		    "PacketAlias/Skinny: Invalid port number, not a Skinny packet\n");
246#endif
247		return;
248	}
249
250	orig_len = dlen;
251	/*
252	 * Skinny packets can contain many messages.  We need to loop
253	 * through the packet using len to determine message boundaries.
254	 * This comes into play big time with port messages being in the
255	 * same packet as register messages.  Also, open receive channel
256	 * acks are usually buried in a pakcet some 400 bytes long.
257	 */
258	while (dlen >= skinny_hdr_len) {
259		len = (sd->len);
260		msgId = (sd->msgId);
261		t = len;
262
263		if (t > orig_len || t > dlen) {
264#ifdef DEBUG
265			fprintf(stderr,
266			    "PacketAlias/Skinny: Not a skinny packet, invalid length \n");
267#endif
268			return;
269		}
270		switch (msgId) {
271		case REG_MSG: {
272			struct RegisterMessage *reg_mesg;
273
274			if (len < (int)sizeof(struct RegisterMessage)) {
275#ifdef DEBUG
276				fprintf(stderr,
277				    "PacketAlias/Skinny: Not a skinny packet, bad registration message\n");
278#endif
279				return;
280			}
281			reg_mesg = (struct RegisterMessage *)&sd->msgId;
282#ifdef DEBUG
283			fprintf(stderr,
284			    "PacketAlias/Skinny: Received a register message");
285#endif
286			alias_skinny_reg_msg(reg_mesg, pip, tc, lnk, direction);
287			break;
288		}
289		case IP_PORT_MSG: {
290			struct IpPortMessage *port_mesg;
291
292			if (len < (int)sizeof(struct IpPortMessage)) {
293#ifdef DEBUG
294				fprintf(stderr,
295				    "PacketAlias/Skinny: Not a skinny packet, port message\n");
296#endif
297				return;
298			}
299#ifdef DEBUG
300			fprintf(stderr
301			    "PacketAlias/Skinny: Received ipport message\n");
302#endif
303			port_mesg = (struct IpPortMessage *)&sd->msgId;
304			alias_skinny_port_msg(port_mesg, pip, tc, lnk, direction);
305			break;
306		}
307		case OPNRCVCH_ACK: {
308			struct OpenReceiveChannelAck *opnrcvchn_ack;
309
310			if (len < (int)sizeof(struct OpenReceiveChannelAck)) {
311#ifdef DEBUG
312				fprintf(stderr,
313				    "PacketAlias/Skinny: Not a skinny packet, packet,OpnRcvChnAckMsg\n");
314#endif
315				return;
316			}
317#ifdef DEBUG
318			fprintf(stderr,
319			    "PacketAlias/Skinny: Received open rcv channel msg\n");
320#endif
321			opnrcvchn_ack = (struct OpenReceiveChannelAck *)&sd->msgId;
322			alias_skinny_opnrcvch_ack(la, opnrcvchn_ack, pip, tc, lnk, &lip, direction);
323			break;
324		}
325		case START_MEDIATX: {
326			struct StartMediaTransmission *startmedia_tx;
327
328			if (len < (int)sizeof(struct StartMediaTransmission)) {
329#ifdef DEBUG
330				fprintf(stderr,
331				    "PacketAlias/Skinny: Not a skinny packet,StartMediaTx Message\n");
332#endif
333				return;
334			}
335#ifdef DEBUG
336			fprintf(stderr,
337			    "PacketAlias/Skinny: Received start media trans msg\n");
338#endif
339			startmedia_tx = (struct StartMediaTransmission *)&sd->msgId;
340			alias_skinny_startmedia(startmedia_tx, pip, tc, lnk, lip, direction);
341			break;
342		}
343		default:
344			break;
345		}
346		/* Place the pointer at the next message in the packet. */
347		dlen -= len + (skinny_hdr_len - sizeof(msgId));
348		sd = (struct skinny_header *)(((char *)&sd->msgId) + len);
349	}
350}
351