
/*
 * udp.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 "udp.h"
#include "ngfunc.h"

#include <netgraph/ng_socket.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_ksocket.h>
#include <netgraph.h>

/*
 * XXX this is currently broken, as it can deliver out-of-order frames.
 * we need to use a node type that prepends sequence numbers
 */

/*
 * DEFINITIONS
 */

  #define UDP_MTU		2048
  #define UDP_MRU		2048

  #define UDP_MAX_ERRORS	10

  #define UDP_REOPEN_PAUSE	10

  struct udpinfo {
    struct in_addr	self_addr;	/* Configured local IP address */
    struct in_addr	peer_addr;	/* Configured peer IP address */
    u_int16_t		self_port;	/* Configured local port */
    u_int16_t		peer_port;	/* Configured peer port */
    u_int16_t		rxSeq;		/* Last seq received */
    u_int16_t		txSeq;		/* Last seq sent */
    int			origination;	/* Link origination */
  };
  typedef struct udpinfo	*UdpInfo;

/* Set menu options */

  enum {
    SET_PEERADDR,
    SET_SELFADDR,
    SET_ORIGINATION,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int	UdpInit(PhysInfo p);
  static void	UdpOpen(PhysInfo p);
  static void	UdpClose(PhysInfo p);
  static void	UdpStat(PhysInfo p);
  static int	UdpOrigination(PhysInfo p);

  static void	UdpDoClose(UdpInfo udp);
  static int	UdpSetCommand(int ac, char *av[], void *arg);

/*
 * GLOBAL VARIABLES
 */

  const struct phystype gUdpPhysType = {
    "udp",
    TRUE, UDP_REOPEN_PAUSE,
    UDP_MTU, UDP_MRU,
    UdpInit,
    UdpOpen,
    UdpClose,
    NULL,
    NULL,	/* XXX when another node is involved, need a function here */
    UdpStat,
    UdpOrigination,
  };

  const struct cmdtab UdpSetCmds[] = {
    { "self ip [port]",			"Set local IP address",
	UdpSetCommand, NULL, (void *) SET_SELFADDR },
    { "peer ip [port]",			"Set remote IP address",
	UdpSetCommand, NULL, (void *) SET_PEERADDR },
    { "origination < local | remote >",	"Set link origination",
	UdpSetCommand, NULL, (void *) SET_ORIGINATION },
    { NULL },
  };

/*
 * UdpInit()
 */

static int
UdpInit(PhysInfo p)
{
  UdpInfo	udp;

  udp = (UdpInfo) (p->info = Malloc(MB_PHYS, sizeof(*udp)));
  udp->origination = LINK_ORIGINATE_UNKNOWN;
  return(0);
}

/*
 * UdpOpen()
 */

static void
UdpOpen(PhysInfo p)
{
  UdpInfo		const udp = (UdpInfo) lnk->phys->info;
  char        		path[NG_PATHLEN+1];
  struct ngm_mkpeer	mkp;
  struct sockaddr_in	addr;

  /* Attach ksocket node to PPP node */
  snprintf(mkp.type, sizeof(mkp.type), "%s", NG_KSOCKET_NODE_TYPE);
  snprintf(mkp.ourhook, sizeof(mkp.ourhook),
    "%s%d", NG_PPP_HOOK_LINK_PREFIX, lnk->bundleIndex);
  snprintf(mkp.peerhook, sizeof(mkp.peerhook), "inet/dgram/udp");
  if (NgSendMsg(bund->csock, MPD_HOOK_PPP, NGM_GENERIC_COOKIE,
      NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
    Log(LG_PHYS, ("[%s] can't attach %s node: %s",
      lnk->name, NG_KSOCKET_NODE_TYPE, strerror(errno)));
    PhysDown(STR_ERROR, NULL);
    return;
  }
  snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, mkp.ourhook);

  /* Bind socket */
  memset(&addr, 0, sizeof(addr));
  addr.sin_len = sizeof(addr);
  addr.sin_family = AF_INET;
  addr.sin_addr = udp->self_addr;
  addr.sin_port = htons(udp->self_port);
  if (NgSendMsg(bund->csock, path, NGM_KSOCKET_COOKIE,
      NGM_KSOCKET_BIND, &addr, sizeof(addr)) < 0) {
    Log(LG_PHYS, ("[%s] can't bind %s node: %s",
      lnk->name, NG_KSOCKET_NODE_TYPE, strerror(errno)));
    UdpDoClose(udp);
    PhysDown(STR_ERROR, NULL);
    return;
  }

  /* Connect socket if peer address and port is specified */
  if (udp->peer_addr.s_addr != 0 && udp->peer_port != 0) {
    memset(&addr, 0, sizeof(addr));
    addr.sin_len = sizeof(addr);
    addr.sin_family = AF_INET;
    addr.sin_addr = udp->peer_addr;
    addr.sin_port = htons(udp->peer_port);
    if (NgSendMsg(bund->csock, path, NGM_KSOCKET_COOKIE,
	NGM_KSOCKET_CONNECT, &addr, sizeof(addr)) < 0
        && errno != EINPROGRESS) {	/* happens in -current (weird) */
      Log(LG_PHYS, ("[%s] can't connect %s node: %s",
	lnk->name, NG_KSOCKET_NODE_TYPE, strerror(errno)));
      UdpDoClose(udp);
      PhysDown(STR_ERROR, NULL);
      return;
    }
  }

  /* Reset sequence numbers */
  udp->rxSeq = 0;		/* XXX not used */
  udp->txSeq = 0;		/* XXX not used */

  /* OK */
  PhysUp();
}

/*
 * UdpClose()
 */

static void
UdpClose(PhysInfo p)
{
  UdpDoClose((UdpInfo) p->info);
  PhysDown(0, NULL);
}

/*
 * UdpDoClose()
 */

static void
UdpDoClose(UdpInfo udp)
{
  char	hook[NG_HOOKLEN + 1];

  snprintf(hook, sizeof(hook),
    "%s%d", NG_PPP_HOOK_LINK_PREFIX, lnk->bundleIndex);
  NgFuncDisconnect(MPD_HOOK_PPP, hook);
}


#if 0
READ {
  /* Check sequence number to avoid out of order packets */
  seq = ntohs(((u_int16_t *) buf)[0]);
  if ((int) seq - (int) udp->rxSeq <= 0)
    return;
  udp->rxSeq = seq;
  LinkInput(mbwrite(mballoc(MB_FRAME_IN, len - 2), buf + 2, len - 2));
}

WRITE{
  /* Prepend sequence number */
  if (proto != PROTO_UNKNOWN) {
    Mbuf	hdr;

    udp->txSeq++;
    hdr = mballoc(MB_FRAME_OUT, 2);
    ((u_int16_t *) MBDATA(hdr))[0] = htons(udp->txSeq);
    hdr->next = frame;
    frame = hdr;
  }
}
#endif

/*
 * UdpStat()
 */

void
UdpStat(PhysInfo p)
{
  UdpInfo	const udp = (UdpInfo) lnk->phys->info;

  printf("UDP configuration:\n");
  printf("\tSelf address : %s, port %u\n",
    inet_ntoa(udp->self_addr), udp->self_port);
  printf("\tPeer address : %s, port %u\n",
    inet_ntoa(udp->peer_addr), udp->peer_port);
}

/*
 * UdpOrigination()
 */

static int
UdpOrigination(PhysInfo p)
{
  UdpInfo	const udp = (UdpInfo) lnk->phys->info;

  return (udp->origination);
}

/*
 * UdpSetCommand()
 */

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

  switch ((intptr_t)arg) {
    case SET_PEERADDR:
      ap = &udp->peer_addr;
      pp = &udp->peer_port;
      goto getAddrPort;
    case SET_SELFADDR:
      ap = &udp->self_addr;
      pp = &udp->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;
    case SET_ORIGINATION:
      if (ac != 1)
	return(-1);
      if (strcasecmp(av[0], "local") == 0) {
	udp->origination = LINK_ORIGINATE_LOCAL;
	break;
      }
      if (strcasecmp(av[0], "remote") == 0) {
	udp->origination = LINK_ORIGINATE_REMOTE;
	break;
      }
      Log(LG_ERR, ("Invalid link origination \"%s\"", av[0]));
      return(-1);

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


