
/*
 * tcp.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 */

#include "ppp.h"
#include "phys.h"
#include "mbuf.h"
#include "async.h"
#include "tcp.h"

#error NEEDS UPDATING

/*
 * DEFINITIONS
 */

  #define TCP_MTU		2048
  #define TCP_MRU		2048

  #define TCP_REOPEN_PAUSE	10

  struct tcpinfo
  {

  /* Configuration */

    struct in_addr	peer_addr;
    struct in_addr	self_addr;
    u_short		peer_port;
    u_short		self_port;

  /* State */

    int			sock;
    EventRef		connEvent;
    EventRef		readEvent;
    EventRef		writeEvent;
    AsyncInfo		async;
    Mbuf		out;
    u_char		active:1;
  };
  typedef struct tcpinfo	*TcpInfo;

/* Set menu options */

  enum
  {
    SET_MODE,
    SET_PEERADDR,
    SET_SELFADDR,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int	TcpInit(PhysInfo p);
  static void	TcpOpen(PhysInfo p);
  static void	TcpClose(PhysInfo p);
  static Mbuf	TcpOutput(PhysInfo p, Mbuf bp, int proto);
  static void	TcpStat(PhysInfo p);
  static int	TcpOriginated(PhysInfo p);

  static void	TcpDoClose(TcpInfo tcp);
  static void	TcpWrite(int type, void *cookie);
  static void	TcpConEvent(int type, void *cookie);
  static void	TcpRead(int type, void *cookie);

  static int	TcpSetCommand(int ac, char *av[], void *arg);

/*
 * GLOBAL VARIABLES
 */

  const struct phystype gTcpPhysType =
  {
    "tcp",
    FALSE, TCP_REOPEN_PAUSE,
    TCP_MTU, TCP_MRU,
    TcpInit,
    TcpOpen,
    TcpClose,
    NULL,
    TcpShutdown,
    TcpStat,
    TcpOriginated,
  };

  const struct cmdtab TcpSetCmds[] =
  {
    { "mode active|passive",		"Set connect method",
	TcpSetCommand, NULL, (void *) SET_MODE },
    { "self ip [port]",			"Set local IP address",
	TcpSetCommand, NULL, (void *) SET_SELFADDR },
    { "peer ip [port]",			"Set remote IP address",
	TcpSetCommand, NULL, (void *) SET_PEERADDR },
    { NULL },
  };

/*
 * TcpInit()
 */

static int
TcpInit(PhysInfo p)
{
  TcpInfo	tcp;

  tcp = (TcpInfo) (p->info = Malloc(MB_PHYS, sizeof(*tcp)));
  tcp->sock = -1;
  AsyncInit(&tcp->async, NULL, 0);
  return(0);
}

/*
 * TcpOpen()
 */

static void
TcpOpen(PhysInfo p)
{
  TcpInfo	const tcp = (TcpInfo) lnk->phys->info;
  char		errbuf[100];

/* Get socket */

  if ((tcp->sock = GetInetSocket(SOCK_STREAM,
    tcp->self_addr, tcp->self_port, errbuf, sizeof(errbuf))) < 0)
  {
    Log(LG_PHYS, ("[%s] %s", lnk->name, errbuf));
    PhysDown(STR_ERROR, NULL);
    return;
  }

/* Connect to peer, actively or passively */

  if (tcp->active)		/* Initiate connection */
  {
    struct sockaddr_in	peer;

    memset(&peer, 0, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_addr = tcp->peer_addr;
    peer.sin_port = htons(tcp->peer_port);
    if (connect(tcp->sock, (struct sockaddr *) &peer, sizeof(peer)) >= 0)
      TcpConEvent(EVENT_WRITE, lnk);
    else
    {
      if (errno == EINPROGRESS)
      {
	EventRegister(&tcp->connEvent, EVENT_WRITE, tcp->sock,
	  DEV_PRIO, TcpConEvent, lnk);
	Log(LG_PHYS, ("[%s] connecting to %s:%u",
	  lnk->name, inet_ntoa(tcp->peer_addr), tcp->peer_port));
	return;
      }
      Log(LG_PHYS, ("[%s] connect: %s", lnk->name, strerror(errno)));
      close(tcp->sock);
      tcp->sock = -1;
      PhysDown(STR_ERROR, NULL);
      return;
    }
  }
  else				/* Listen for a connection */
  {

  /* Make socket available for connections  */

    if (listen(tcp->sock, 2) < 0) {
      Log(LG_PHYS, ("[%s] listen: %s", lnk->name, strerror(errno)));
      close(tcp->sock);
      tcp->sock = -1;
      PhysDown(STR_ERROR, NULL);
      return;
    }
    Log(LG_PHYS, ("[%s] waiting for connection on %s:%u",
      lnk->name, inet_ntoa(tcp->self_addr), tcp->self_port));
    EventRegister(&tcp->connEvent, EVENT_READ, tcp->sock,
      DEV_PRIO, TcpConEvent, lnk);
  }
}

/*
 * TcpConEvent()
 */

static void
TcpConEvent(int type, void *cookie)
{
  TcpInfo		tcp;
  struct sockaddr_in	peerAddr;

/* Get event */

  lnk = (Link) cookie;
  bund = lnk->bund;
  tcp = (TcpInfo) lnk->phys->info;

/* If passive, accept the incoming connection */

  if (type == EVENT_READ)
  {
    int	sock;

    if ((sock = TcpAcceptConnection(tcp->sock, &peerAddr)) < 0)
      goto failed;
    (void) close(tcp->sock);
    tcp->sock = sock;
    Log(LG_PHYS, ("[%s] incoming connection from %s:%u",
      lnk->name, inet_ntoa(peerAddr.sin_addr), ntohs(peerAddr.sin_port)));

  /* If passive, and peer address specified, only accept from that address */

    if (tcp->peer_addr.s_addr
      && tcp->peer_addr.s_addr != peerAddr.sin_addr.s_addr)
    {
      Log(LG_PHYS, ("[%s] rejected: wrong IP address", lnk->name));
      goto failed;
    }

  /* If passive, and peer port specified, only accept from that port */

    if (tcp->peer_port != 0 && tcp->peer_port != ntohs(peerAddr.sin_port))
    {
      Log(LG_PHYS, ("[%s] rejected: wrong port", lnk->name));
      goto failed;
    }
  }
  else
  {
    int	addrLen = sizeof(peerAddr);

  /* Check whether the connection was successful or not */

    if (getpeername(tcp->sock, (struct sockaddr *) &peerAddr, &addrLen) < 0) {
      Log(LG_PPTP, ("[%s] connection to %s:%d failed",
	lnk->name, inet_ntoa(tcp->peer_addr), tcp->peer_port));
failed:
      PhysDown(STR_ERROR, NULL);
      TcpDoClose(tcp);
      return;
    }
  }

/* Report connected */

  Log(LG_PHYS, ("[%s] connected to %s:%u",
    lnk->name, inet_ntoa(peerAddr.sin_addr), ntohs(peerAddr.sin_port)));
  AsyncInit(&tcp->async, LinkInput, TCP_MRU + MAX_PPP_FRAME_OVERHEAD);
  PhysUp();

/* Wait for input */

  EventRegister(&tcp->readEvent, EVENT_READ,
    tcp->sock, DEV_PRIO, TcpRead, lnk);
}

/*
 * TcpClose()
 */

static void
TcpClose(PhysInfo p)
{
  TcpDoClose((TcpInfo) p->info);
  PhysDown(0, NULL);
}

/*
 * TcpDoClose()
 */

static void
TcpDoClose(TcpInfo tcp)
{
  PFREE(tcp->out);
  EventUnRegister(&tcp->connEvent);
  EventUnRegister(&tcp->readEvent);
  EventUnRegister(&tcp->writeEvent);
  (void) close(tcp->sock);
  tcp->sock = -1;
}

/*
 * TcpRead()
 */

static void
TcpRead(int type, void *cookie)
{
  TcpInfo	tcp;
  u_char	buf[LCP_DEFAULT_MRU];
  int		nread;

/* Get event */

  lnk = (Link) cookie;
  bund = lnk->bund;
  tcp = (TcpInfo) lnk->phys->info;

/* Read data */

  if ((nread = read(tcp->sock, buf, sizeof(buf))) <= 0)
  {
    if (nread < 0)
    {
      if (errno == EAGAIN)
	goto done;
      Log(LG_LINK, ("[%s] device read: %s", lnk->name, strerror(errno)));
      PhysDown(STR_READ_ERR, "%s", strerror(errno));
    }
    else
      PhysDown(STR_READ_EOF, NULL);
    TcpDoClose(tcp);
    return;
  }

/* Run bytes through async decoder */

  AsyncDecode(tcp->async, buf, nread);

/* Reregister input event */

done:
  EventRegister(&tcp->readEvent, EVENT_READ,
    tcp->sock, DEV_PRIO, TcpRead, lnk);
}

/*
 * TcpOutput()
 */

static Mbuf
TcpOutput(PhysInfo p, Mbuf frame, int proto)
{
  TcpInfo	const tcp = (TcpInfo) p->info;

  if (proto != PROTO_UNKNOWN
    && (proto == PROTO_LCP || !lnk->lcp.peer_acfcomp))
  {
    Mbuf	hdr;

    hdr = mballoc(MB_FRAME_OUT, 2);
    MBDATA(hdr)[0] = PPP_ALLSTATIONS;
    MBDATA(hdr)[1] = PPP_UI;
    hdr->next = frame;
    frame = hdr;
  }
  if (tcp->out)
    return(frame);
  tcp->out = AsyncEncode(tcp->async, frame, proto == PROTO_LCP);
  TcpWrite(EVENT_WRITE, lnk);
  return(NULL);
}

/*
 * TcpWrite()
 */

static void
TcpWrite(int type, void *cookie)
{
  TcpInfo	tcp;

  lnk = (Link) cookie;
  bund = lnk->bund;
  tcp = (TcpInfo) lnk->phys->info;
  if (WriteMbuf(&tcp->out, tcp->sock, "socket") < 0)
  {
    PhysDown(STR_WRITE_ERR, "%s", strerror(errno));
    TcpDoClose(tcp);
    return;
  }
  if (tcp->out)
    EventRegister(&tcp->writeEvent, EVENT_WRITE,
      tcp->sock, DEV_PRIO, TcpWrite, lnk);
}

/*
 * TcpOriginated()
 */

static int
TcpOriginated(PhysInfo p)
{
  TcpInfo	const tcp = (TcpInfo) lnk->phys->info;

  return(tcp->active ? LINK_ORIGINATE_LOCAL : LINK_ORIGINATE_REMOTE);
}

/*
 * TcpStat()
 */

void
TcpStat(PhysInfo p)
{
  TcpInfo	const tcp = (TcpInfo) lnk->phys->info;

  printf("TCP configuration:\n");
  printf("\tSelf address : %s, port %u\n",
    inet_ntoa(tcp->self_addr), tcp->self_port);
  printf("\tPeer address : %s, port %u\n",
    inet_ntoa(tcp->peer_addr), tcp->peer_port);
  printf("\tConnect mode : %s\n", tcp->active ? "ACTIVE" : "PASSIVE");
  AsyncStat(tcp->async);
}

/*
 * TcpSetCommand()
 */

static int
TcpSetCommand(int ac, char *av[], void *arg)
{
  TcpInfo		const tcp = (TcpInfo) lnk->phys->info;
  struct in_addr	*ap;
  u_short		*pp;

  switch ((int) arg)
  {
    case SET_MODE:
      if (ac != 1)
	return(-1);
      if (!strcasecmp(av[0], "active"))
	tcp->active = TRUE;
      else if (!strcasecmp(av[0], "passive"))
	tcp->active = FALSE;
      else
	return(-1);
      break;

    case SET_PEERADDR:
      ap = &tcp->peer_addr;
      pp = &tcp->peer_port;
      goto getAddrPort;
    case SET_SELFADDR:
      ap = &tcp->self_addr;
      pp = &tcp->self_port;
getAddrPort:
      if (ac < 1 || ac > 2)
	return(-1);
      if (!inet_aton(av[0], ap))
      {
	Log(LG_ERR, ("Bad ip address \"%s\"", av[0]));
	return(-1);
      }
      if (ac > 1)
      {
	if (atoi(av[1]) <= 0)
	{
	  Log(LG_ERR, ("Bad port \"%s\"", av[1]));
	  return(-1);
	}
	*pp = atoi(av[1]);
      }
      break;

    default:
      assert(0);
  }
  return(0);
}


