File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mpd / src / tcp.c
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:39:23 2021 UTC (3 years, 3 months ago) by misho
Branches: mpd, MAIN
CVS tags: v5_9p16, v5_9, HEAD
mpd 5.9


/*
 * tcp.c
 *
 * Written by Alexander Motin <mav@FreeBSD.org>
 */

#include "ppp.h"
#include "phys.h"
#include "mbuf.h"
#include "ngfunc.h"
#include "tcp.h"
#include "log.h"

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

/*
 * DEFINITIONS
 */

#define TCP_MTU		2048
#define TCP_MRU		2048
#define LISTENHOOK		"listen"

#ifndef SMALL_SYSTEM
#define TCP_MAXPARENTIFS	256
#else
#define TCP_MAXPARENTIFS	64
#endif


struct tcpinfo {
	/* Configuration */
	struct	{
	    struct optinfo  	options;
	    struct u_addr	self_addr;
	    struct u_range	peer_addr;
	    in_port_t		self_port;
	    in_port_t		peer_port;
	    char		*fqdn_peer_addr; /* FQDN Peer address */
	} conf;

	/* State */
	u_char		incoming;		/* incoming vs. outgoing */
	struct TcpIf 	*If;
	int		csock;
	struct u_addr	peer_addr;
	in_port_t	peer_port;
	EventRef	ev_connect;
	ng_ID_t		async_node_id;
	ng_ID_t		node_id;
};

typedef struct tcpinfo	*TcpInfo;

/* Set menu options */
enum {
	SET_PEERADDR,
	SET_SELFADDR,
	SET_ENABLE,
	SET_DISABLE
};

  /* Binary options */
  enum {
    TCP_CONF_RESOLVE_ONCE	/* Only once resolve peer_addr */
  };

/*
 * INTERNAL FUNCTIONS
 */

static int	TcpInit(Link l);
static int	TcpInst(Link l, Link lt);
static void	TcpOpen(Link l);
static void	TcpClose(Link l);
static void	TcpShutdown(Link l);
static void	TcpStat(Context ctx);
static int	TcpOriginate(Link l);
static int	TcpIsSync(Link l);
static int	TcpSelfAddr(Link l, void *buf, size_t buf_len);
static int	TcpPeerAddr(Link l, void *buf, size_t buf_len);
static int	TcpPeerPort(Link l, void *buf, size_t buf_len);
static int	TcpCallingNum(Link l, void *buf, size_t buf_len);
static int	TcpCalledNum(Link l, void *buf, size_t buf_len);

static void	TcpDoClose(Link l);
static void	TcpAcceptEvent(int type, void *cookie);
static void	TcpConnectEvent(int type, void *cookie);

static int	TcpSetCommand(Context ctx, int ac, const char *const av[], const void *arg);
static void	TcpNodeUpdate(Link l);
static int 	TcpListen(Link l);
static void	TcpUnListen(Link l);

/*
 * GLOBAL VARIABLES
 */

const struct phystype gTcpPhysType = {
	.name		= "tcp",
	.descr		= "PPP over TCP",
	.mtu		= TCP_MTU,
	.mru		= TCP_MRU,
	.tmpl		= 1,
	.init		= TcpInit,
	.inst		= TcpInst,
	.open		= TcpOpen,
	.close		= TcpClose,
	.update		= TcpNodeUpdate,
	.shutdown	= TcpShutdown,
	.showstat	= TcpStat,
	.originate	= TcpOriginate,
	.issync		= TcpIsSync,
	.selfaddr	= TcpSelfAddr,
	.peeraddr	= TcpPeerAddr,
	.peerport	= TcpPeerPort,
	.callingnum	= TcpCallingNum,
	.callednum	= TcpCalledNum,
};

const struct cmdtab TcpSetCmds[] = {
    { "self {ip} [{port}]",		"Set local IP address",
	TcpSetCommand, NULL, 2, (void *) SET_SELFADDR },
    { "peer {ip} [{port}]",		"Set remote IP address",
	TcpSetCommand, NULL, 2, (void *) SET_PEERADDR },
    { "enable [opt ...]",		"Enable option",
	TcpSetCommand, NULL, 2, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	TcpSetCommand, NULL, 2, (void *) SET_DISABLE },
    { NULL, NULL, NULL, NULL, 0, NULL },
};

struct TcpIf {
    struct u_addr	self_addr;
    in_port_t	self_port;
    int		refs;
    int		csock;                  /* netgraph Control socket */
    EventRef	ctrlEvent;		/* listen for ctrl messages */
};
static struct TcpIf TcpIfs[TCP_MAXPARENTIFS];

 /*
 * INTERNAL VARIABLES
 */

  static const struct confinfo	gConfList[] = {
    { 0,	TCP_CONF_RESOLVE_ONCE,	"resolve-once"	},
    { 0,	0,			NULL		},
  };

/*
 * TcpInit()
 */

static int
TcpInit(Link l)
{
	TcpInfo pi;

	pi = (TcpInfo) (l->info = Malloc(MB_PHYS, sizeof(*pi)));

	u_addrclear(&pi->conf.self_addr);
	u_rangeclear(&pi->conf.peer_addr);
	pi->conf.self_port=0;
	pi->conf.peer_port=0;

	pi->incoming = 0;
	pi->If = NULL;
	pi->csock = -1;

	u_addrclear(&pi->peer_addr);
	pi->peer_port=0;
	pi->conf.fqdn_peer_addr = NULL;
	Enable(&pi->conf.options, TCP_CONF_RESOLVE_ONCE);

	return (0);
}

/*
 * TcpInst()
 */

static int
TcpInst(Link l, Link lt)
{
	TcpInfo pi;
	TcpInfo const pit = (TcpInfo) lt->info;

	/* Initialize this link */
	pi = (TcpInfo) (l->info = Mdup(MB_PHYS, lt->info, sizeof(*pi)));
	if (pit->conf.fqdn_peer_addr != NULL)
	    pi->conf.fqdn_peer_addr =
		Mstrdup(MB_PHYS, pit->conf.fqdn_peer_addr);
    
	if (pi->If)
	    pi->If->refs++;

	return (0);
}

/*
 * TcpOpen()
 */

static void
TcpOpen(Link l)
{
	TcpInfo	const 		pi = (TcpInfo) l->info;
	struct ngm_mkpeer	mkp;
	struct ngm_connect	cn;
	struct ngm_name		nm;
	char 			path[NG_PATHSIZ];
	char 			hook[NG_HOOKSIZ];
	struct sockaddr_storage addr;
	struct ng_async_cfg	acfg;
	int 			rval;
	char 			buf[48];

	/* Create a new netgraph node to control TCP ksocket node. */
	if (NgMkSockNode(NULL, &pi->csock, NULL) < 0) {
		Perror("[%s] TCP can't create control socket", l->name);
		goto fail;
	}
	(void)fcntl(pi->csock, F_SETFD, 1);

        if (!PhysGetUpperHook(l, path, hook)) {
		Log(LG_PHYS, ("[%s] TCP: can't get upper hook", l->name));
    		goto fail;
        }
    
	strcpy(mkp.type, NG_ASYNC_NODE_TYPE);
	strlcpy(mkp.ourhook, hook, sizeof(mkp.ourhook));
	strcpy(mkp.peerhook, NG_ASYNC_HOOK_SYNC);
	if (NgSendMsg(pi->csock, path, NGM_GENERIC_COOKIE,
	    NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
		Perror("[%s] can't attach %s %s node",
		    l->name, NG_ASYNC_NODE_TYPE, mkp.ourhook);
		goto fail;
	}
	
	strlcat(path, ".", sizeof(path));
	strlcat(path, hook, sizeof(path));
	
	/* Give it a name */
	snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-as", gPid, l->name);
	if (NgSendMsg(pi->csock, path,
	    NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
		Perror("[%s] can't name %s node", l->name, NG_ASYNC_NODE_TYPE);
	}

	/* Get async node ID */
	if ((pi->async_node_id = NgGetNodeID(pi->csock, path)) == 0) {
	    Perror("[%s] Cannot get %s node id", l->name, NG_ASYNC_NODE_TYPE);
	    goto fail;
	};

	/* Configure the async converter node. */
	memset(&acfg, 0, sizeof(acfg));
	acfg.enabled = TRUE;
	acfg.accm = 0;  /* we do not need thie on TCP */
	acfg.amru = TCP_MRU;
	acfg.smru = TCP_MTU;
	if (NgSendMsg(pi->csock, path, NGM_ASYNC_COOKIE,
	    NGM_ASYNC_CMD_SET_CONFIG, &acfg, sizeof(acfg)) < 0) {
		Log(LG_ERR, ("[%s] can't config %s", l->name, path));
		goto fail;
	}

	if (pi->incoming) {
		Log(LG_PHYS2, ("[%s] %s() on incoming call", l->name,
		    __func__));

		/* Connect new born ksocket to our link. */
		snprintf(cn.path, sizeof(cn.path), "[%x]:", pi->node_id);
		snprintf(cn.ourhook, sizeof(cn.ourhook), NG_ASYNC_HOOK_ASYNC);
		snprintf(cn.peerhook, sizeof(cn.peerhook), "data");
		if (NgSendMsg(pi->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT,
		    &cn, sizeof(cn)) < 0) {
			Perror("[%s] can't connect new born ksocket", l->name);
			goto fail;
	  	}

		l->state = PHYS_STATE_UP;
		PhysUp(l);
		return;
	}
	if ((!Enabled(&pi->conf.options, TCP_CONF_RESOLVE_ONCE)) &&
	    (pi->conf.fqdn_peer_addr != NULL)) {
	    struct u_range	rng;
	    if (ParseRange(pi->conf.fqdn_peer_addr, &rng, ALLOW_IPV4|ALLOW_IPV6))
		pi->conf.peer_addr = rng;
	}

	u_addrcopy(&pi->conf.peer_addr.addr,&pi->peer_addr);
	pi->peer_port = pi->conf.peer_port;

	/*
	 * Attach fresh ksocket node next to async node.
	 */
	strcpy(mkp.type, NG_KSOCKET_NODE_TYPE);
	strcpy(mkp.ourhook, NG_ASYNC_HOOK_ASYNC);
	if ((pi->conf.self_addr.family==AF_INET6) || 
	    (pi->conf.self_addr.family==AF_UNSPEC && pi->conf.peer_addr.addr.family==AF_INET6)) {
	    snprintf(mkp.peerhook, sizeof(mkp.peerhook), "%d/%d/%d", PF_INET6, SOCK_STREAM, IPPROTO_TCP);
	} else {
	    snprintf(mkp.peerhook, sizeof(mkp.peerhook), "inet/stream/tcp");
	}
	if (NgSendMsg(pi->csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mkp,
	    sizeof(mkp)) < 0) {
		Perror("[%s] can't attach %s node", l->name, NG_KSOCKET_NODE_TYPE);
		goto fail;
	}

	strlcat(path, ".", sizeof(path));
	strlcat(path, NG_ASYNC_HOOK_ASYNC, sizeof(path));

	/* Give it a name */
	snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-kso", gPid, l->name);
	if (NgSendMsg(pi->csock, path,
	    NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
		Perror("[%s] can't name %s node", l->name, NG_KSOCKET_NODE_TYPE);
	}

	/* Start connecting to peer. */
	u_addrtosockaddr(&pi->peer_addr, pi->peer_port, &addr);
	rval = NgSendMsg(pi->csock, path, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_CONNECT, &addr, addr.ss_len);
	if (rval < 0 && errno != EINPROGRESS) {
		Perror("[%s] can't connect() %s node", l->name,
		    NG_KSOCKET_NODE_TYPE);
		goto fail;
	}

	l->state = PHYS_STATE_CONNECTING;

	if (rval == 0)	/* Can happen when peer is local. */
		TcpConnectEvent(EVENT_READ, l);
	else {
		assert(errno == EINPROGRESS);
		EventRegister(&pi->ev_connect, EVENT_READ, pi->csock,
		    0, TcpConnectEvent, l);
		Log(LG_PHYS, ("[%s] connecting to %s %u", l->name,
		    u_addrtoa(&pi->conf.peer_addr.addr, buf, sizeof(buf)), pi->conf.peer_port));
	}

	return;
fail:
	l->state = PHYS_STATE_DOWN;
	TcpDoClose(l);
	PhysDown(l, STR_ERROR, NULL);
}

/*
 * TcpConnectEvent() triggers when outgoing connection succeeds/fails.
 */

static void
TcpConnectEvent(int type, void *cookie)
{
	union {
		u_char buf[sizeof(struct ng_mesg) + sizeof(uint32_t)];
		struct ng_mesg	resp;
	} cn;
	uint32_t *const rval = (uint32_t *)(void *)cn.resp.data;

	Link		l;
	TcpInfo		pi;
	char path[NG_PATHSIZ];

	/* Restore context. */
	l = (Link)cookie;
	pi = (TcpInfo)l->info;

	assert(type == EVENT_READ);

	/* Check whether the connection was successful or not. */
	if (NgRecvMsg(pi->csock, &cn.resp, sizeof(cn), path) < 0) {
		Perror("[%s] error reading message from \"%s\"", l->name, path);
		goto failed;
	}

	assert(cn.resp.header.typecookie == NGM_KSOCKET_COOKIE);
	assert(cn.resp.header.cmd == NGM_KSOCKET_CONNECT);

	if (*rval != 0) {
		Log(LG_PHYS, ("[%s] failed to connect: %s", l->name,
		    strerror(*rval)));
		goto failed;
	}

	/* Report connected. */
	Log(LG_PHYS, ("[%s] connection established", l->name));

	l->state = PHYS_STATE_UP;
	PhysUp(l);

	return;
failed:
	l->state = PHYS_STATE_DOWN;
	TcpDoClose(l);
	PhysDown(l, STR_ERROR, NULL);

}

/*
 * TcpAcceptEvent() triggers when we accept incoming connection.
 */
static void
TcpAcceptEvent(int type, void *cookie)
{
	union {
		uint32_t	id;
		struct sockaddr_storage sin;
		u_char buf[sizeof(struct ng_mesg) + sizeof(uint32_t)
					+ sizeof(struct sockaddr_storage)];
		struct ng_mesg	resp;
	} ac;
	uint32_t *const id = (uint32_t *)(void *)ac.resp.data;
	struct sockaddr_storage *const sin =
		(struct sockaddr_storage*)(void *)(id + 1);

	struct ngm_name         nm;
	char path[NG_PATHSIZ];
	struct u_addr	addr;
	in_port_t	port;
	char		buf[48];
	int 		k;
	struct TcpIf 	*If=(struct TcpIf *)(cookie);
	Link		l = NULL;
	TcpInfo		pi = NULL;

	assert(type == EVENT_READ);

	/* Accept cloned ng_ksocket(4). */
	if (NgRecvMsg(If->csock, &ac.resp, sizeof(ac), NULL) < 0) {
		Perror("TCP: error reading message from \"%s\"", path);
		goto failed;
	}
	sockaddrtou_addr(sin, &addr, &port);

	Log(LG_PHYS, ("Incoming TCP connection from %s %u",
	    u_addrtoa(&addr, buf, sizeof(buf)), port));

	if (gShutdownInProgress) {
		Log(LG_PHYS, ("Shutdown sequence in progress, ignoring request."));
		return;
	}

	if (OVERLOAD()) {
		Log(LG_PHYS, ("Daemon overloaded, ignoring request."));
		return;
	}

	/* Examine all TCP links. */
	for (k = 0; k < gNumLinks; k++) {
		Link l2;
	        TcpInfo pi2;

		if (!gLinks[k] || gLinks[k]->type != &gTcpPhysType)
			continue;

		l2 = gLinks[k];
		pi2 = (TcpInfo)l2->info;

		if ((!PhysIsBusy(l2)) &&
		    Enabled(&l2->conf.options, LINK_CONF_INCOMING) &&
		    (pi2->If == If) &&
		    IpAddrInRange(&pi2->conf.peer_addr, &addr) &&
		    (pi2->conf.peer_port == 0 || pi2->conf.peer_port == port)) {

			if (pi == NULL || pi2->conf.peer_addr.width > pi->conf.peer_addr.width) {
				l = l2;
				pi = pi2;
				if (u_rangehost(&pi->conf.peer_addr)) {
					break;	/* Nothing could be better */
				}
			}
		}
	}
	if (l != NULL && l->tmpl)
    		l = LinkInst(l, NULL, 0, 0);

	if (l != NULL) {
    		pi = (TcpInfo)l->info;
		Log(LG_PHYS, ("[%s] Accepting TCP connection from %s %u",
		    l->name, u_addrtoa(&addr, buf, sizeof(buf)), port));

		sockaddrtou_addr(sin, &pi->peer_addr, &pi->peer_port);

		pi->node_id = *id;

		/* Give it a name */
		snprintf(nm.name, sizeof(nm.name), "mpd%d-%s", gPid, l->name);
		snprintf(path, sizeof(path), "[%x]:", *id);
		if (NgSendMsg(If->csock, path,
		    NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
			Perror("[%s] can't name %s node",
			    l->name, NG_KSOCKET_NODE_TYPE);
		}

		pi->incoming=1;
		l->state = PHYS_STATE_READY;

		PhysIncoming(l);
	} else {
	    Log(LG_PHYS, ("No free TCP link with requested parameters "
	        "was found"));
	    snprintf(path, sizeof(path), "[%x]:", *id);
	    NgFuncShutdownNode(If->csock, "", path);
	}

failed:
	/* Tell that we are willing to receive accept message. */
	if (NgSendMsg(If->csock, LISTENHOOK, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_ACCEPT, NULL, 0) < 0) {
		Perror("TCP: can't accept on %s node", NG_KSOCKET_NODE_TYPE);
	}
	EventRegister(&If->ctrlEvent, EVENT_READ, If->csock,
	    0, TcpAcceptEvent, If);
}

/*
 * TcpClose()
 */

static void
TcpClose(Link l)
{
	TcpInfo const pi = (TcpInfo) l->info;

	TcpDoClose(l);

	if (l->state != PHYS_STATE_DOWN) {
	    pi->incoming=0;
	    l->state = PHYS_STATE_DOWN;

	    u_addrclear(&pi->peer_addr);
	    pi->peer_port=0;

	    PhysDown(l, STR_MANUALLY, NULL);
	}
}

/*
 * TcpShutdown()
 */

static void
TcpShutdown(Link l)
{
	TcpInfo const pi = (TcpInfo) l->info;

	if (pi->conf.fqdn_peer_addr)
		Freee(pi->conf.fqdn_peer_addr);

	TcpDoClose(l);
	TcpUnListen(l);
	Freee(l->info);
}

/*
 * TcpDoClose()
 */

static void
TcpDoClose(Link l)
{
	char path[NG_PATHSIZ];
	TcpInfo const pi = (TcpInfo) l->info;

	EventUnRegister(&pi->ev_connect);

	if (pi->csock<=0) {
	    return;
	};

	if (pi->node_id != 0) {
	    snprintf(path, sizeof(path), "[%lx]:", (u_long)pi->node_id);
	    NgFuncShutdownNode(pi->csock, l->name, path);
	    pi->node_id = 0;
	}
	
	if (pi->async_node_id != 0) {
	    snprintf(path, sizeof(path), "[%lx]:", (u_long)pi->async_node_id);
	    NgFuncShutdownNode(pi->csock, l->name, path);
	    pi->async_node_id = 0;
	}
	
	close(pi->csock);
	pi->csock = -1;
	pi->node_id = 0;
}

/*
 * TcpOriginate()
 */

static int
TcpOriginate(Link l)
{
	TcpInfo const pi = (TcpInfo) l->info;

	return (pi->incoming ? LINK_ORIGINATE_REMOTE : LINK_ORIGINATE_LOCAL);
}

/*
 * TcpIsSync()
 */

static int
TcpIsSync(Link l)
{
	(void)l;
	return (1);
}

static int
TcpSelfAddr(Link l, void *buf, size_t buf_len)
{
    TcpInfo const pi = (TcpInfo) l->info;

    if (!u_addrempty(&pi->conf.self_addr)) {
	if (u_addrtoa(&pi->conf.self_addr, buf, buf_len))
	    return (0);
  	else {
	    ((char*)buf)[0]=0;
	    return (-1);
	}
    }
    ((char*)buf)[0]=0;
    return (0);
}

static int
TcpPeerAddr(Link l, void *buf, size_t buf_len)
{
	TcpInfo const pi = (TcpInfo) l->info;

	if (u_addrtoa(&pi->peer_addr, buf, buf_len))
		return (0);
  	else
		return (-1);
}

static int
TcpPeerPort(Link l, void *buf, size_t buf_len)
{
	TcpInfo const pi = (TcpInfo) l->info;

	if (snprintf( buf, buf_len, "%u", pi->peer_port))
		return (0);
  	else
		return (-1);
}

static int
TcpCallingNum(Link l, void *buf, size_t buf_len)
{
	TcpInfo const pi = (TcpInfo) l->info;

	if (pi->incoming) {
	    if (u_addrtoa(&pi->peer_addr, buf, buf_len))
	    	return (0);
  	    else
		return (-1);
	} else {
	    if (u_addrtoa(&pi->conf.self_addr, buf, buf_len))
	    	return (0);
  	    else
		return (-1);
	}
}

static int
TcpCalledNum(Link l, void *buf, size_t buf_len)
{
	TcpInfo const pi = (TcpInfo) l->info;

	if (!pi->incoming) {
	    if (u_addrtoa(&pi->peer_addr, buf, buf_len))
	    	return (0);
  	    else
		return (-1);
	} else {
	    if (u_addrtoa(&pi->conf.self_addr, buf, buf_len))
	    	return (0);
  	    else
		return (-1);
	}
}

/*
 * TcpStat()
 */

void
TcpStat(Context ctx)
{
	TcpInfo const pi = (TcpInfo) ctx->lnk->info;
	char	buf[48];

	Printf("TCP configuration:\r\n");
	Printf("\tPeer FQDN    : %s\r\n", pi->conf.fqdn_peer_addr);
	Printf("\tSelf address : %s, port %u\r\n",
	    u_addrtoa(&pi->conf.self_addr, buf, sizeof(buf)), pi->conf.self_port);
	Printf("\tPeer address : %s, port %u\r\n",
	    u_rangetoa(&pi->conf.peer_addr, buf, sizeof(buf)), pi->conf.peer_port);
	Printf("TCP state:\r\n");
	if (ctx->lnk->state != PHYS_STATE_DOWN) {
	    Printf("\tIncoming     : %s\r\n", (pi->incoming?"YES":"NO"));
	    Printf("\tCurrent peer : %s, port %u\r\n",
		u_addrtoa(&pi->peer_addr, buf, sizeof(buf)), pi->peer_port);
	}
}

static int 
TcpListen(Link l)
{
	TcpInfo const pi = (TcpInfo) l->info;
	struct ngm_mkpeer mkp;
	struct sockaddr_storage addr;
	int32_t backlog = 1;
	char buf[48];
	union {
	    u_char buf[sizeof(struct ng_ksocket_sockopt) + sizeof(int)];
	    struct ng_ksocket_sockopt ksso;
	} u;
	struct ng_ksocket_sockopt *const ksso = &u.ksso;
	int i, j = -1, free = -1;
	
	if (pi->If)
	    return(1);

	for (i = 0; i < TCP_MAXPARENTIFS; i++) {
	    if (TcpIfs[i].self_port == 0)
	        free = i;
	    else if ((u_addrcompare(&TcpIfs[i].self_addr, &pi->conf.self_addr) == 0) &&
	        (TcpIfs[i].self_port == pi->conf.self_port)) {
	            j = i;
	    	    break;
	    }
	}

	if (j >= 0) {
	    TcpIfs[j].refs++;
	    pi->If=&TcpIfs[j];
	    return(1);
	}

	if (free < 0) {
	    Log(LG_ERR, ("[%s] TCP: Too many different listening ports! ", 
		l->name));
	    return (0);
	}

	TcpIfs[free].refs = 1;
	pi->If=&TcpIfs[free];
	
	u_addrcopy(&pi->conf.self_addr,&pi->If->self_addr);
	pi->If->self_port=pi->conf.self_port;
	
	/* Create a new netgraph node */
	if (NgMkSockNode(NULL, &pi->If->csock, NULL) < 0) {
	    Perror("TCP: can't create ctrl socket");
	    return(0);
	}
	(void)fcntl(pi->If->csock, F_SETFD, 1);

	/* Make listening TCP ksocket node. */
	strcpy(mkp.type, NG_KSOCKET_NODE_TYPE);
	strcpy(mkp.ourhook, LISTENHOOK);
	if (pi->If->self_addr.family==AF_INET6) {
	    snprintf(mkp.peerhook, sizeof(mkp.peerhook), "%d/%d/%d", PF_INET6, SOCK_STREAM, IPPROTO_TCP);
	} else {
	    snprintf(mkp.peerhook, sizeof(mkp.peerhook), "inet/stream/tcp");
	}
	if (NgSendMsg(pi->If->csock, ".:", NGM_GENERIC_COOKIE, NGM_MKPEER,
	    &mkp, sizeof(mkp)) < 0) {
		Perror("TCP: can't attach %s node", NG_KSOCKET_NODE_TYPE);
		goto fail2;
	}

	/* Setsockopt socket. */
	ksso->level=SOL_SOCKET;
	ksso->name=SO_REUSEPORT;
	((int *)(void *)(ksso->value))[0]=1;
	if (NgSendMsg(pi->If->csock, LISTENHOOK, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_SETOPT, &u, sizeof(u)) < 0) {
		Perror("TCP: can't setsockopt() %s node", NG_KSOCKET_NODE_TYPE);
		goto fail2;
	}

	/* Bind socket. */
	u_addrtosockaddr(&pi->If->self_addr, pi->If->self_port, &addr);
	if (NgSendMsg(pi->If->csock, LISTENHOOK, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_BIND, &addr, addr.ss_len) < 0) {
		Perror("TCP: can't bind() %s node", NG_KSOCKET_NODE_TYPE);
		goto fail2;
	}

	/* Listen. */
	if (NgSendMsg(pi->If->csock, LISTENHOOK, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_LISTEN, &backlog, sizeof(backlog)) < 0) {
		Perror("TCP: can't listen() on %s node", NG_KSOCKET_NODE_TYPE);
		goto fail2;
	}

	/* Tell that we are willing to receive accept message. */
	if (NgSendMsg(pi->If->csock, LISTENHOOK, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_ACCEPT, NULL, 0) < 0) {
		Perror("TCP: can't accept() on %s node", NG_KSOCKET_NODE_TYPE);
		goto fail2;
	}

	Log(LG_PHYS, ("TCP: waiting for connection on %s %u",
	    u_addrtoa(&pi->If->self_addr, buf, sizeof(buf)), pi->If->self_port));
	EventRegister(&pi->If->ctrlEvent, EVENT_READ, pi->If->csock,
	    0, TcpAcceptEvent, pi->If);

	return (1);
fail2:
	NgSendMsg(pi->If->csock, LISTENHOOK, NGM_GENERIC_COOKIE, NGM_SHUTDOWN,
	    NULL, 0);
	return (0);
}

static void
TcpUnListen(Link l)
{
	TcpInfo const pi = (TcpInfo) l->info;
	char buf[48];
	
	if (!pi->If)
	    return;

	pi->If->refs--;
	if (pi->If->refs == 0) {
	    Log(LG_PHYS, ("TCP: stop waiting for connection on %s %u",
		u_addrtoa(&pi->If->self_addr, buf, sizeof(buf)), pi->If->self_port));
	    EventUnRegister(&pi->If->ctrlEvent);
	    close(pi->If->csock);
	    pi->If->csock = -1;
	    pi->If->self_port = 0;
	    pi->If = NULL;
	}
}

/*
 * TcpNodeUpdate()
 */

static void
TcpNodeUpdate(Link l)
{
    TcpInfo const pi = (TcpInfo) l->info;
    if (!pi->If) {
	if (Enabled(&l->conf.options, LINK_CONF_INCOMING))
	    TcpListen(l);
    } else {
	if (!Enabled(&l->conf.options, LINK_CONF_INCOMING))
	    TcpUnListen(l);
    }
}

/*
 * TcpSetCommand()
 */

static int
TcpSetCommand(Context ctx, int ac, const char *const av[], const void *arg)
{
    TcpInfo		const pi = (TcpInfo) ctx->lnk->info;
    char		**fqdn_peer_addr = &pi->conf.fqdn_peer_addr;
    struct u_range	rng;
    int			port;

    switch ((intptr_t)arg) {
	case SET_PEERADDR:
	case SET_SELFADDR:
	    if ((ac == 1 || ac == 2) && (intptr_t)arg == SET_PEERADDR) {
		if (*fqdn_peer_addr)
		    Freee(*fqdn_peer_addr);
		*fqdn_peer_addr = Mstrdup(MB_PHYS, av[0]);
	    }
    	    if (ac < 1 || ac > 2 || !ParseRange(av[0], &rng, ALLOW_IPV4|ALLOW_IPV6))
		return(-1);
	    if (ac > 1) {
		if ((port = atoi(av[1])) < 0 || port > 0xffff)
		    return(-1);
    	    } else {
	        port = 0;
    	    }
    	    if ((intptr_t)arg == SET_SELFADDR) {
	    	pi->conf.self_addr = rng.addr;
		pi->conf.self_port = port;
    	    } else {
		pi->conf.peer_addr = rng;
		pi->conf.peer_port = port;
    	    }
	    if (pi->If) {
		TcpUnListen(ctx->lnk);
		TcpListen(ctx->lnk);
	    }
	    break;
	case SET_ENABLE:
	    EnableCommand(ac, av, &pi->conf.options, gConfList);
	    TcpNodeUpdate(ctx->lnk);
	    break;
	case SET_DISABLE:
	    DisableCommand(ac, av, &pi->conf.options, gConfList);
	    TcpNodeUpdate(ctx->lnk);
	    break;
	default:
		assert(0);
    }

    return (0);
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>