alias_skinny.c revision 124621
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 124621 2004-01-17 10:52:21Z phk $
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 *link,
136                     ConvDirection direction)
137{
138  reg_msg->ipAddr = (u_int32_t) GetAliasAddress(link).s_addr;
139
140  tc->th_sum = 0;
141  tc->th_sum = TcpChecksum(pip);
142
143  return 0;
144}
145
146static int
147alias_skinny_startmedia(struct StartMediaTransmission *start_media,
148                        struct ip *pip, struct tcphdr *tc,
149                        struct alias_link *link, u_int32_t localIpAddr,
150                        ConvDirection direction)
151{
152  struct in_addr  dst, src;
153
154  dst.s_addr = start_media->remoteIpAddr;
155  src.s_addr = localIpAddr;
156
157  /* XXX I should probably handle in bound global translations as well. */
158
159  return 0;
160}
161
162static int
163alias_skinny_port_msg(struct IpPortMessage *port_msg, struct ip *pip,
164                      struct tcphdr *tc, struct alias_link *link,
165                      ConvDirection direction)
166{
167  port_msg->stationIpPort = (u_int32_t) ntohs(GetAliasPort(link));
168
169  tc->th_sum = 0;
170  tc->th_sum = TcpChecksum(pip);
171
172  return 0;
173}
174
175static int
176alias_skinny_opnrcvch_ack(struct libalias *la, struct OpenReceiveChannelAck *opnrcvch_ack,
177                          struct ip * pip, struct tcphdr *tc,
178                          struct alias_link *link, u_int32_t *localIpAddr,
179                          ConvDirection direction)
180{
181  struct in_addr  null_addr;
182  struct alias_link *opnrcv_link;
183  u_int32_t localPort;
184
185  *localIpAddr = (u_int32_t) opnrcvch_ack->ipAddr;
186  localPort = opnrcvch_ack->port;
187
188  null_addr.s_addr = INADDR_ANY;
189  opnrcv_link = FindUdpTcpOut(la, pip->ip_src, null_addr,
190                              htons((u_short) opnrcvch_ack->port), 0,
191                              IPPROTO_UDP, 1);
192  opnrcvch_ack->ipAddr = (u_int32_t) GetAliasAddress(opnrcv_link).s_addr;
193  opnrcvch_ack->port = (u_int32_t) ntohs(GetAliasPort(opnrcv_link));
194
195  tc->th_sum = 0;
196  tc->th_sum = TcpChecksum(pip);
197
198  return 0;
199}
200
201void
202AliasHandleSkinny(struct libalias *la, struct ip *pip, struct alias_link *link)
203{
204  int             hlen, tlen, dlen;
205  struct tcphdr  *tc;
206  u_int32_t       msgId, len, t, lip;
207  struct skinny_header *sd;
208  int             orig_len, skinny_hdr_len = sizeof(struct skinny_header);
209  ConvDirection   direction;
210
211  tc = (struct tcphdr *) ((char *)pip + (pip->ip_hl << 2));
212  hlen = (pip->ip_hl + tc->th_off) << 2;
213  tlen = ntohs(pip->ip_len);
214  dlen = tlen - hlen;
215
216  sd = (struct skinny_header *) ((char *)pip + hlen);
217
218  /*
219   * XXX This direction is reserved for future use.  I still need to
220   * handle the scenario where the call manager is on the inside, and
221   * the calling phone is on the global outside.
222   */
223  if (ntohs(tc->th_dport) == la->skinnyPort) {
224    direction = ClientToServer;
225  } else if (ntohs(tc->th_sport) == la->skinnyPort) {
226    direction = ServerToClient;
227  } else {
228#ifdef DEBUG
229    fprintf(stderr,
230            "PacketAlias/Skinny: Invalid port number, not a Skinny packet\n");
231#endif
232    return;
233  }
234
235  orig_len = dlen;
236  /*
237   * Skinny packets can contain many messages.  We need to loop through
238   * the packet using len to determine message boundaries.  This comes
239   * into play big time with port messages being in the same packet as
240   * register messages.  Also, open receive channel acks are
241   * usually buried in a pakcet some 400 bytes long.
242   */
243  while (dlen >= skinny_hdr_len) {
244    len = (sd->len);
245    msgId = (sd->msgId);
246    t = len;
247
248    if (t < 0 || t > orig_len || t > dlen) {
249#ifdef DEBUG
250      fprintf(stderr,
251              "PacketAlias/Skinny: Not a skinny packet, invalid length \n");
252#endif
253      return;
254    }
255    switch (msgId) {
256      case REG_MSG:
257        {
258          struct RegisterMessage *reg_mesg;
259
260          if (len < sizeof(struct RegisterMessage)) {
261#ifdef DEBUG
262            fprintf(stderr,
263                    "PacketAlias/Skinny: Not a skinny packet, bad registration message\n");
264#endif
265            return;
266          }
267          reg_mesg = (struct RegisterMessage *) & sd->msgId;
268#ifdef DEBUG
269          fprintf(stderr,
270                  "PacketAlias/Skinny: Received a register message");
271#endif
272	  alias_skinny_reg_msg(reg_mesg, pip, tc, link, direction);
273        }
274        break;
275      case IP_PORT_MSG:
276        {
277          struct IpPortMessage *port_mesg;
278          if (len < sizeof(struct IpPortMessage)) {
279#ifdef DEBUG
280            fprintf(stderr,
281                    "PacketAlias/Skinny: Not a skinny packet, port message\n");
282#endif
283            return;
284          }
285#ifdef DEBUG
286          fprintf(stderr
287                  "PacketAlias/Skinny: Received ipport message\n");
288#endif
289          port_mesg = (struct IpPortMessage *) & sd->msgId;
290          alias_skinny_port_msg(port_mesg, pip, tc, link, direction);
291        }
292        break;
293      case OPNRCVCH_ACK:
294        {
295          struct OpenReceiveChannelAck *opnrcvchn_ack;
296
297          if (len < sizeof(struct OpenReceiveChannelAck)) {
298#ifdef DEBUG
299            fprintf(stderr,
300                    "PacketAlias/Skinny: Not a skinny packet, packet,OpnRcvChnAckMsg\n");
301#endif
302            return;
303          }
304#ifdef DEBUG
305          fprintf(stderr,
306                  "PacketAlias/Skinny: Received open rcv channel msg\n");
307#endif
308          opnrcvchn_ack = (struct OpenReceiveChannelAck *) & sd->msgId;
309          alias_skinny_opnrcvch_ack(la, opnrcvchn_ack, pip, tc, link, &lip, direction);
310        }
311        break;
312      case START_MEDIATX:
313        {
314          struct StartMediaTransmission *startmedia_tx;
315
316          if (len < sizeof(struct StartMediaTransmission)) {
317#ifdef DEBUG
318            fprintf(stderr,
319                    "PacketAlias/Skinny: Not a skinny packet,StartMediaTx Message\n");
320#endif
321            return;
322          }
323#ifdef DEBUG
324          fprintf(stderr,
325                  "PacketAlias/Skinny: Received start media trans msg\n");
326#endif
327           startmedia_tx = (struct StartMediaTransmission *) & sd->msgId;
328           alias_skinny_startmedia(startmedia_tx, pip, tc, link, lip, direction);
329         }
330         break;
331       default:
332         break;
333     }
334     /* Place the pointer at the next message in the packet. */
335     dlen -= len + (skinny_hdr_len - sizeof(msgId));
336     sd = (struct skinny_header *) (((char *)&sd->msgId) + len);
337   }
338}
339