freebsd-skq/usr.sbin/ppp/nat_cmd.c
marcus e19a1e64d2 Add Cisco Skinny Station protocol support to libalias, natd, and ppp.
Skinny is the protocol used by Cisco IP phones to talk to Cisco Call
Managers.  With this code, one can use a Cisco IP phone behind a FreeBSD
NAT gateway.

Currently, having the Call Manager behind the NAT gateway is not supported.
More information on enabling Skinny support in libalias, natd, and ppp
can be found in those applications' manpages.

PR:		55843
Reviewed by:	ru
Approved by:	ru
MFC after:	30 days
2003-09-23 07:41:55 +00:00

595 lines
15 KiB
C

/*-
* Copyright (c) 2001 Charles Mott <cm@linktel.net>
* Brian Somers <brian@Awfulhak.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#ifdef LOCALNAT
#include "alias.h"
#else
#include <alias.h>
#endif
#include "layer.h"
#include "proto.h"
#include "defs.h"
#include "command.h"
#include "log.h"
#include "nat_cmd.h"
#include "descriptor.h"
#include "prompt.h"
#include "timer.h"
#include "fsm.h"
#include "slcompress.h"
#include "throughput.h"
#include "iplist.h"
#include "mbuf.h"
#include "lqr.h"
#include "hdlc.h"
#include "ncpaddr.h"
#include "ip.h"
#include "ipcp.h"
#include "ipv6cp.h"
#include "lcp.h"
#include "ccp.h"
#include "link.h"
#include "mp.h"
#include "filter.h"
#ifndef NORADIUS
#include "radius.h"
#endif
#include "ncp.h"
#include "bundle.h"
#define NAT_EXTRABUF (13)
static int StrToAddr(const char *, struct in_addr *);
static int StrToPortRange(const char *, u_short *, u_short *, const char *);
static int StrToAddrAndPort(const char *, struct in_addr *, u_short *,
u_short *, const char *);
static void
lowhigh(u_short *a, u_short *b)
{
if (a > b) {
u_short c;
c = *b;
*b = *a;
*a = c;
}
}
int
nat_RedirectPort(struct cmdargs const *arg)
{
if (!arg->bundle->NatEnabled) {
prompt_Printf(arg->prompt, "Alias not enabled\n");
return 1;
} else if (arg->argc == arg->argn + 3 || arg->argc == arg->argn + 4) {
char proto_constant;
const char *proto;
struct in_addr localaddr;
u_short hlocalport, llocalport;
struct in_addr aliasaddr;
u_short haliasport, laliasport;
struct in_addr remoteaddr;
u_short hremoteport, lremoteport;
struct alias_link *link;
int error;
proto = arg->argv[arg->argn];
if (strcmp(proto, "tcp") == 0) {
proto_constant = IPPROTO_TCP;
} else if (strcmp(proto, "udp") == 0) {
proto_constant = IPPROTO_UDP;
} else {
prompt_Printf(arg->prompt, "port redirect: protocol must be"
" tcp or udp\n");
return -1;
}
error = StrToAddrAndPort(arg->argv[arg->argn+1], &localaddr, &llocalport,
&hlocalport, proto);
if (error) {
prompt_Printf(arg->prompt, "nat port: error reading localaddr:port\n");
return -1;
}
error = StrToPortRange(arg->argv[arg->argn+2], &laliasport, &haliasport,
proto);
if (error) {
prompt_Printf(arg->prompt, "nat port: error reading alias port\n");
return -1;
}
aliasaddr.s_addr = INADDR_ANY;
if (arg->argc == arg->argn + 4) {
error = StrToAddrAndPort(arg->argv[arg->argn+3], &remoteaddr,
&lremoteport, &hremoteport, proto);
if (error) {
prompt_Printf(arg->prompt, "nat port: error reading "
"remoteaddr:port\n");
return -1;
}
} else {
remoteaddr.s_addr = INADDR_ANY;
lremoteport = hremoteport = 0;
}
lowhigh(&llocalport, &hlocalport);
lowhigh(&laliasport, &haliasport);
lowhigh(&lremoteport, &hremoteport);
if (haliasport - laliasport != hlocalport - llocalport) {
prompt_Printf(arg->prompt, "nat port: local & alias port ranges "
"are not equal\n");
return -1;
}
if (hremoteport && hremoteport - lremoteport != hlocalport - llocalport) {
prompt_Printf(arg->prompt, "nat port: local & remote port ranges "
"are not equal\n");
return -1;
}
while (laliasport <= haliasport) {
link = PacketAliasRedirectPort(localaddr, htons(llocalport),
remoteaddr, htons(lremoteport),
aliasaddr, htons(laliasport),
proto_constant);
if (link == NULL) {
prompt_Printf(arg->prompt, "nat port: %d: error %d\n", laliasport,
error);
return 1;
}
llocalport++;
laliasport++;
if (hremoteport)
lremoteport++;
}
return 0;
}
return -1;
}
int
nat_RedirectAddr(struct cmdargs const *arg)
{
if (!arg->bundle->NatEnabled) {
prompt_Printf(arg->prompt, "nat not enabled\n");
return 1;
} else if (arg->argc == arg->argn+2) {
int error;
struct in_addr localaddr, aliasaddr;
struct alias_link *link;
error = StrToAddr(arg->argv[arg->argn], &localaddr);
if (error) {
prompt_Printf(arg->prompt, "address redirect: invalid local address\n");
return 1;
}
error = StrToAddr(arg->argv[arg->argn+1], &aliasaddr);
if (error) {
prompt_Printf(arg->prompt, "address redirect: invalid alias address\n");
prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
arg->cmd->syntax);
return 1;
}
link = PacketAliasRedirectAddr(localaddr, aliasaddr);
if (link == NULL) {
prompt_Printf(arg->prompt, "address redirect: packet aliasing"
" engine error\n");
prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
arg->cmd->syntax);
}
} else
return -1;
return 0;
}
int
nat_RedirectProto(struct cmdargs const *arg)
{
if (!arg->bundle->NatEnabled) {
prompt_Printf(arg->prompt, "nat not enabled\n");
return 1;
} else if (arg->argc >= arg->argn + 2 && arg->argc <= arg->argn + 4) {
struct in_addr localIP, publicIP, remoteIP;
struct alias_link *link;
struct protoent *pe;
int error, len;
len = strlen(arg->argv[arg->argn]);
if (len == 0) {
prompt_Printf(arg->prompt, "proto redirect: invalid protocol\n");
return 1;
}
if (strspn(arg->argv[arg->argn], "01234567") == len)
pe = getprotobynumber(atoi(arg->argv[arg->argn]));
else
pe = getprotobyname(arg->argv[arg->argn]);
if (pe == NULL) {
prompt_Printf(arg->prompt, "proto redirect: invalid protocol\n");
return 1;
}
error = StrToAddr(arg->argv[arg->argn + 1], &localIP);
if (error) {
prompt_Printf(arg->prompt, "proto redirect: invalid src address\n");
return 1;
}
if (arg->argc >= arg->argn + 3) {
error = StrToAddr(arg->argv[arg->argn + 2], &publicIP);
if (error) {
prompt_Printf(arg->prompt, "proto redirect: invalid alias address\n");
prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
arg->cmd->syntax);
return 1;
}
} else
publicIP.s_addr = INADDR_ANY;
if (arg->argc == arg->argn + 4) {
error = StrToAddr(arg->argv[arg->argn + 2], &remoteIP);
if (error) {
prompt_Printf(arg->prompt, "proto redirect: invalid dst address\n");
prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
arg->cmd->syntax);
return 1;
}
} else
remoteIP.s_addr = INADDR_ANY;
link = PacketAliasRedirectProto(localIP, remoteIP, publicIP, pe->p_proto);
if (link == NULL) {
prompt_Printf(arg->prompt, "proto redirect: packet aliasing"
" engine error\n");
prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
arg->cmd->syntax);
}
} else
return -1;
return 0;
}
static int
StrToAddr(const char *str, struct in_addr *addr)
{
struct hostent *hp;
if (inet_aton(str, addr))
return 0;
hp = gethostbyname(str);
if (!hp) {
log_Printf(LogWARN, "StrToAddr: Unknown host %s.\n", str);
return -1;
}
*addr = *((struct in_addr *) hp->h_addr);
return 0;
}
static int
StrToPort(const char *str, u_short *port, const char *proto)
{
struct servent *sp;
char *end;
*port = strtol(str, &end, 10);
if (*end != '\0') {
sp = getservbyname(str, proto);
if (sp == NULL) {
log_Printf(LogWARN, "StrToAddr: Unknown port or service %s/%s.\n",
str, proto);
return -1;
}
*port = ntohs(sp->s_port);
}
return 0;
}
static int
StrToPortRange(const char *str, u_short *low, u_short *high, const char *proto)
{
char *minus;
int res;
minus = strchr(str, '-');
if (minus)
*minus = '\0'; /* Cheat the const-ness ! */
res = StrToPort(str, low, proto);
if (minus)
*minus = '-'; /* Cheat the const-ness ! */
if (res == 0) {
if (minus)
res = StrToPort(minus + 1, high, proto);
else
*high = *low;
}
return res;
}
static int
StrToAddrAndPort(const char *str, struct in_addr *addr, u_short *low,
u_short *high, const char *proto)
{
char *colon;
int res;
colon = strchr(str, ':');
if (!colon) {
log_Printf(LogWARN, "StrToAddrAndPort: %s is missing port number.\n", str);
return -1;
}
*colon = '\0'; /* Cheat the const-ness ! */
res = StrToAddr(str, addr);
*colon = ':'; /* Cheat the const-ness ! */
if (res != 0)
return -1;
return StrToPortRange(colon + 1, low, high, proto);
}
int
nat_ProxyRule(struct cmdargs const *arg)
{
char cmd[LINE_LEN];
int f, pos;
size_t len;
if (arg->argn >= arg->argc)
return -1;
for (f = arg->argn, pos = 0; f < arg->argc; f++) {
len = strlen(arg->argv[f]);
if (sizeof cmd - pos < len + (len ? 1 : 0))
break;
if (len)
cmd[pos++] = ' ';
strcpy(cmd + pos, arg->argv[f]);
pos += len;
}
return PacketAliasProxyRule(cmd);
}
int
nat_SetTarget(struct cmdargs const *arg)
{
struct in_addr addr;
if (arg->argc == arg->argn) {
addr.s_addr = INADDR_ANY;
PacketAliasSetTarget(addr);
return 0;
}
if (arg->argc != arg->argn + 1)
return -1;
if (!strcasecmp(arg->argv[arg->argn], "MYADDR")) {
addr.s_addr = INADDR_ANY;
PacketAliasSetTarget(addr);
return 0;
}
addr = GetIpAddr(arg->argv[arg->argn]);
if (addr.s_addr == INADDR_NONE) {
log_Printf(LogWARN, "%s: invalid address\n", arg->argv[arg->argn]);
return 1;
}
PacketAliasSetTarget(addr);
return 0;
}
#ifndef NO_FW_PUNCH
int
nat_PunchFW(struct cmdargs const *arg)
{
char *end;
long base, count;
if (arg->argc == arg->argn) {
PacketAliasSetMode(0, PKT_ALIAS_PUNCH_FW);
return 0;
}
if (arg->argc != arg->argn + 2)
return -1;
base = strtol(arg->argv[arg->argn], &end, 10);
if (*end != '\0' || base < 0)
return -1;
count = strtol(arg->argv[arg->argn + 1], &end, 10);
if (*end != '\0' || count < 0)
return -1;
PacketAliasSetFWBase(base, count);
PacketAliasSetMode(PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW);
return 0;
}
#endif
int
nat_SkinnyPort(struct cmdargs const *arg)
{
char *end;
long port;
if (arg->argc == arg->argn) {
PacketAliasSetSkinnyPort(0);
return 0;
}
if (arg->argc != arg->argn + 1)
return -1;
port = strtol(arg->argv[arg->argn], &end, 10);
if (*end != '\0' || port < 0)
return -1;
PacketAliasSetSkinnyPort(port);
return 0;
}
static struct mbuf *
nat_LayerPush(struct bundle *bundle, struct link *l, struct mbuf *bp,
int pri, u_short *proto)
{
if (!bundle->NatEnabled || *proto != PROTO_IP)
return bp;
log_Printf(LogDEBUG, "nat_LayerPush: PROTO_IP -> PROTO_IP\n");
m_settype(bp, MB_NATOUT);
/* Ensure there's a bit of extra buffer for the NAT code... */
bp = m_pullup(m_append(bp, NULL, NAT_EXTRABUF));
PacketAliasOut(MBUF_CTOP(bp), bp->m_len);
bp->m_len = ntohs(((struct ip *)MBUF_CTOP(bp))->ip_len);
return bp;
}
static struct mbuf *
nat_LayerPull(struct bundle *bundle, struct link *l, struct mbuf *bp,
u_short *proto)
{
static int gfrags;
int ret, len, nfrags;
struct mbuf **last;
char *fptr;
if (!bundle->NatEnabled || *proto != PROTO_IP)
return bp;
log_Printf(LogDEBUG, "nat_LayerPull: PROTO_IP -> PROTO_IP\n");
m_settype(bp, MB_NATIN);
/* Ensure there's a bit of extra buffer for the NAT code... */
bp = m_pullup(m_append(bp, NULL, NAT_EXTRABUF));
ret = PacketAliasIn(MBUF_CTOP(bp), bp->m_len);
bp->m_len = ntohs(((struct ip *)MBUF_CTOP(bp))->ip_len);
if (bp->m_len > MAX_MRU) {
log_Printf(LogWARN, "nat_LayerPull: Problem with IP header length (%lu)\n",
(unsigned long)bp->m_len);
m_freem(bp);
return NULL;
}
switch (ret) {
case PKT_ALIAS_OK:
break;
case PKT_ALIAS_UNRESOLVED_FRAGMENT:
/* Save the data for later */
fptr = malloc(bp->m_len);
bp = mbuf_Read(bp, fptr, bp->m_len);
PacketAliasSaveFragment(fptr);
log_Printf(LogDEBUG, "Store another frag (%lu) - now %d\n",
(unsigned long)((struct ip *)fptr)->ip_id, ++gfrags);
break;
case PKT_ALIAS_FOUND_HEADER_FRAGMENT:
/* Fetch all the saved fragments and chain them on the end of `bp' */
last = &bp->m_nextpkt;
nfrags = 0;
while ((fptr = PacketAliasGetFragment(MBUF_CTOP(bp))) != NULL) {
nfrags++;
PacketAliasFragmentIn(MBUF_CTOP(bp), fptr);
len = ntohs(((struct ip *)fptr)->ip_len);
*last = m_get(len, MB_NATIN);
memcpy(MBUF_CTOP(*last), fptr, len);
free(fptr);
last = &(*last)->m_nextpkt;
}
gfrags -= nfrags;
log_Printf(LogDEBUG, "Found a frag header (%lu) - plus %d more frags (no"
"w %d)\n", (unsigned long)((struct ip *)MBUF_CTOP(bp))->ip_id,
nfrags, gfrags);
break;
case PKT_ALIAS_IGNORED:
if (PacketAliasSetMode(0, 0) & PKT_ALIAS_DENY_INCOMING) {
log_Printf(LogTCPIP, "NAT engine denied data:\n");
m_freem(bp);
bp = NULL;
} else if (log_IsKept(LogTCPIP)) {
log_Printf(LogTCPIP, "NAT engine ignored data:\n");
PacketCheck(bundle, AF_INET, MBUF_CTOP(bp), bp->m_len, NULL,
NULL, NULL);
}
break;
default:
log_Printf(LogWARN, "nat_LayerPull: Dropped a packet (%d)....\n", ret);
m_freem(bp);
bp = NULL;
break;
}
return bp;
}
struct layer natlayer =
{ LAYER_NAT, "nat", nat_LayerPush, nat_LayerPull };