/*
 * 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>
#ifdef __DragonFly__
#include <netgraph/socket/ng_socket.h>
#include <netgraph/async/ng_async.h>
#include <netgraph/ksocket/ng_ksocket.h>
#else
#include <netgraph/ng_socket.h>
#include <netgraph/ng_async.h>
#include <netgraph/ng_ksocket.h>
#endif
#include <netgraph.h>

/*
 * DEFINITIONS
 */

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

#define TCP_MAXPARENTIFS	256

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;
	} conf;

	/* State */
	u_char		incoming:1;		/* 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,
};

enum {
	TCP_CONF_ORIGINATE,	/* allow originating connections to peer */
	TCP_CONF_INCOMING,	/* allow accepting connections from peer */
};

/*
 * INTERNAL FUNCTIONS
 */

static int	TcpInit(PhysInfo p);
static void	TcpOpen(PhysInfo p);
static void	TcpClose(PhysInfo p);
static void	TcpShutdown(PhysInfo p);
static void	TcpStat(Context ctx);
static int	TcpOriginate(PhysInfo p);
static int	TcpIsSync(PhysInfo p);
static int	TcpPeerAddr(PhysInfo p, void *buf, int buf_len);
static int	TcpPeerPort(PhysInfo p, void *buf, int buf_len);
static int	TcpCallingNum(PhysInfo p, void *buf, int buf_len);
static int	TcpCalledNum(PhysInfo p, void *buf, int buf_len);

static void	TcpDoClose(PhysInfo p);
static void	TcpAcceptEvent(int type, void *cookie);
static void	TcpConnectEvent(int type, void *cookie);

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

/*
 * GLOBAL VARIABLES
 */

const struct phystype gTcpPhysType = {
	.name		= "tcp",
	.minReopenDelay	= TCP_REOPEN_PAUSE,
	.mtu		= TCP_MTU,
	.mru		= TCP_MRU,
	.init		= TcpInit,
	.open		= TcpOpen,
	.close		= TcpClose,
	.shutdown	= TcpShutdown,
	.showstat	= TcpStat,
	.originate	= TcpOriginate,
	.issync		= TcpIsSync,
	.peeraddr	= TcpPeerAddr,
	.peerport	= TcpPeerPort,
	.callingnum	= TcpCallingNum,
	.callednum	= TcpCalledNum,
};

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

static struct confinfo	gConfList[] = {
    { 0,	TCP_CONF_ORIGINATE,	"originate"	},
    { 0,	TCP_CONF_INCOMING,	"incoming"	},
    { 0,	0,			NULL		},
};

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

int TcpListenUpdateSheduled=0;
struct pppTimer TcpListenUpdateTimer;

/*
 * TcpInit()
 */

static int
TcpInit(PhysInfo p)
{
	TcpInfo pi;

	pi = (TcpInfo) (p->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;

	return (0);
}

/*
 * TcpOpen()
 */

static void
TcpOpen(PhysInfo p)
{
	TcpInfo	const 		pi = (TcpInfo) p->info;
	struct ngm_mkpeer	mkp;
	struct ngm_connect	cn;
	struct ngm_name		nm;
	char 			path[NG_PATHLEN + 1];
	char 			hook[NG_HOOKLEN + 1];
	struct sockaddr_storage addr;
	struct ng_async_cfg	acfg;
	int 			rval;
	char 			buf[64];
    union {
        u_char buf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
        struct ng_mesg reply;
    } repbuf;
    struct ng_mesg *const reply = &repbuf.reply;
    struct nodeinfo *ninfo = (struct nodeinfo *)&reply->data;

	if ((!pi->incoming) && (!Enabled(&pi->conf.options, TCP_CONF_ORIGINATE))) {
		Log(LG_ERR, ("[%s] Originate option is not enabled",
		    p->name));
		p->state = PHYS_STATE_DOWN;
		TcpDoClose(p);
		PhysDown(p, STR_DEV_NOT_READY, NULL);
		return;
	};

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

#if NG_NODESIZ>=32
	/* Give it a name */
	snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-so", gPid, p->name);
	if (NgSendMsg(pi->csock, ".:",
	    NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
		Log(LG_ERR, ("[%s] can't name %s node: %s",
		    p->name, NG_BPF_NODE_TYPE, strerror(errno)));
	}
#endif

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

	/* Get async node ID */
	if (NgSendMsg(pi->csock, path,
    	    NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) != -1) {
		if (NgRecvMsg(pi->csock, reply, sizeof(repbuf), NULL) != -1) {
	    	    pi->async_node_id = ninfo->id;
		}
	}

	/* 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", p->name, path));
		goto fail;
	}

	if (pi->incoming) {
		Log(LG_PHYS2, ("[%s] %s() on incoming call", p->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) {
			Log(LG_ERR, ("[%s] can't connect new born ksocket: %s",
			    p->name, strerror(errno)));
			goto fail;
	  	}

		p->state = PHYS_STATE_UP;
		PhysUp(p);
		return;
	}

	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.
	 */
	snprintf(mkp.type, sizeof(mkp.type), "%s", NG_KSOCKET_NODE_TYPE);
	snprintf(mkp.ourhook, sizeof(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) {
		Log(LG_ERR, ("[%s] can't attach %s node: %s", p->name,
		    NG_KSOCKET_NODE_TYPE, strerror(errno)));
		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", gPid, p->name);
	if (NgSendMsg(pi->csock, path,
	    NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
		Log(LG_ERR, ("[%s] can't name %s node: %s",
		    p->name, NG_BPF_NODE_TYPE, strerror(errno)));
	}

	/* 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) {
		Log(LG_ERR, ("[%s] can't connect() %s node: %s", p->name,
		    NG_KSOCKET_NODE_TYPE, strerror(errno))); 
		goto fail;
	}

	p->state = PHYS_STATE_CONNECTING;

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

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

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

static void
TcpConnectEvent(int type, void *cookie)
{
	struct {
		struct ng_mesg	resp;
		int32_t		rval;
	} cn;
	PhysInfo	p;
	TcpInfo		pi;
	char path[NG_PATHLEN + 1];

	/* Restore context. */
	p = (PhysInfo)cookie;
	pi = (TcpInfo)p->info;

	assert(type == EVENT_READ);

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

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

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

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

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

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

}

/*
 * TcpAcceptEvent() triggers when we accept incoming connection.
 */
static void
TcpAcceptEvent(int type, void *cookie)
{
	struct {
		struct ng_mesg	resp;
		uint32_t	id;
		struct sockaddr_storage sin;
	} ac;
	struct ngm_name         nm;
	char path[NG_PATHLEN + 1];
	struct u_addr	addr;
	in_port_t	port;
	char		buf[64];
	int 		k;
	struct TcpIf 	*If=(struct TcpIf *)(cookie);
	time_t const 	now = time(NULL);
	PhysInfo	p = NULL;
	TcpInfo		pi = NULL;

	assert(type == EVENT_READ);

	/* Accept cloned ng_ksocket(4). */
	if (NgRecvMsg(If->csock, &ac.resp, sizeof(ac), NULL) < 0) {
		Log(LG_ERR, ("TCP: error reading message from \"%s\": %s",
		    path, strerror(errno)));
		goto failed;
	}
	sockaddrtou_addr(&ac.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 < gNumPhyses; k++) {
		PhysInfo p2;
	        TcpInfo pi2;

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

		p2 = gPhyses[k];
		pi2 = (TcpInfo)p2->info;

		if ((p2->state == PHYS_STATE_DOWN) &&
		    (now - p2->lastClose >= TCP_REOPEN_PAUSE) &&
		    Enabled(&pi2->conf.options, TCP_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) {
				p = p2;
				pi = pi2;
				if (u_rangehost(&pi->conf.peer_addr)) {
					break;	/* Nothing could be better */
				}
			}
		}
	}
	if (pi != NULL) {
		Log(LG_PHYS, ("[%s] Accepting TCP connection from %s %u",
		    p->name, u_addrtoa(&addr, buf, sizeof(buf)), port));

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

		pi->node_id = ac.id;

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

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

		PhysIncoming(p);
	} else {
	    Log(LG_PHYS, ("No free TCP link with requested parameters "
	        "was found"));
	    snprintf(path, sizeof(path), "[%x]:", ac.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) {
		Log(LG_ERR, ("TCP: can't accept on %s node: %s",
		    NG_KSOCKET_NODE_TYPE, strerror(errno)));
	}
	EventRegister(&If->ctrlEvent, EVENT_READ, If->csock,
	    0, TcpAcceptEvent, If);
}

/*
 * TcpClose()
 */

static void
TcpClose(PhysInfo p)
{
	TcpInfo const pi = (TcpInfo) p->info;

	TcpDoClose(p);

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

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

	    PhysDown(p, 0, NULL);
	}
}

/*
 * TcpShutdown()
 */

static void
TcpShutdown(PhysInfo p)
{
	TcpDoClose(p);
}

/*
 * TcpDoClose()
 */

static void
TcpDoClose(PhysInfo p)
{
	char path[NG_PATHLEN + 1];
	TcpInfo const pi = (TcpInfo) p->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, p->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, p->name, path);
	    pi->async_node_id = 0;
	}
	
	close(pi->csock);
	pi->csock = -1;
	pi->node_id = 0;
}

/*
 * TcpOriginate()
 */

static int
TcpOriginate(PhysInfo p)
{
	TcpInfo const pi = (TcpInfo) p->info;

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

/*
 * TcpIsSync()
 */

static int
TcpIsSync(PhysInfo p)
{
	return (1);
}

static int
TcpPeerAddr(PhysInfo p, void *buf, int buf_len)
{
	TcpInfo const pi = (TcpInfo) p->info;

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

static int
TcpPeerPort(PhysInfo p, void *buf, int buf_len)
{
	TcpInfo const pi = (TcpInfo) p->info;

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

static int
TcpCallingNum(PhysInfo p, void *buf, int buf_len)
{
	TcpInfo const pi = (TcpInfo) p->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(PhysInfo p, void *buf, int buf_len)
{
	TcpInfo const pi = (TcpInfo) p->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->phys->info;
	char	buf[64];

	Printf("TCP configuration:\r\n");
	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 options:\r\n");
	OptStat(ctx, &pi->conf.options, gConfList);
	Printf("TCP state:\r\n");
	Printf("\tState        : %s\r\n", gPhysStateNames[ctx->phys->state]);
	if (ctx->phys->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 
ListenTcpNode(struct TcpIf *If)
{
	struct ngm_mkpeer mkp;
	struct sockaddr_storage addr;
	int32_t backlog = 1;
	int error;
	char buf[64];
	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;
	
	/* Create a new netgraph node */
	if (NgMkSockNode(NULL, &If->csock, NULL) < 0) {
	    Log(LG_ERR, ("TCP: can't create ctrl socket: %s",
	        strerror(errno)));
	    return(0);
	}
	(void)fcntl(If->csock, F_SETFD, 1);

	/* Make listening TCP ksocket node. */
	snprintf(mkp.type, sizeof(mkp.type), "%s",
	    NG_KSOCKET_NODE_TYPE);
	snprintf(mkp.ourhook, sizeof(mkp.ourhook), LISTENHOOK);
	if (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(If->csock, ".", NGM_GENERIC_COOKIE, NGM_MKPEER,
	    &mkp, sizeof(mkp)) < 0) {
		Log(LG_ERR, ("TCP: can't attach %s node: %s",
		    NG_KSOCKET_NODE_TYPE, strerror(errno)));
		error = errno;
		goto fail2;
	}

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

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

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

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

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

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

/*
 * TcpListenUpdate()
 */

static void
TcpListenUpdate(void *arg)
{
	int k;

	TcpListenUpdateSheduled = 0;

	/* Examine all PPPoE links. */
	for (k = 0; k < gNumPhyses; k++) {
        	PhysInfo p;
        	TcpInfo pi;
		int i, j = -1;

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

		p = gPhyses[k];
		pi = (TcpInfo)p->info;

		if (!Enabled(&pi->conf.options, TCP_CONF_INCOMING))
			continue;

		if (!pi->conf.self_port) {
			Log(LG_ERR, ("Tcp: Skipping link %s with undefined "
			    "port number", p->name));
			continue;
		}

		for (i = 0; i < TcpIfCount; i++)
			if ((u_addrcompare(&TcpIfs[i].self_addr, &pi->conf.self_addr) == 0) &&
			    (TcpIfs[i].self_port == pi->conf.self_port))
				j = i;

		if (j == -1) {
			if (TcpIfCount>=TCP_MAXPARENTIFS) {
			    Log(LG_ERR, ("[%s] TCP: Too many different parent interfaces! ", 
				p->name));
			    continue;
			}
			u_addrcopy(&pi->conf.self_addr,&TcpIfs[TcpIfCount].self_addr);
			TcpIfs[TcpIfCount].self_port=pi->conf.self_port;

			if (ListenTcpNode(&(TcpIfs[TcpIfCount]))) {

				pi->If=&TcpIfs[TcpIfCount];
				TcpIfCount++;
			}
		} else {
			pi->If=&TcpIfs[j];
		}
	}
}

/*
 * TcpNodeUpdate()
 */

static void
TcpNodeUpdate(PhysInfo p)
{
  TcpInfo pi = (TcpInfo)p->info;

  if (Enabled(&pi->conf.options, TCP_CONF_INCOMING) &&
        (!TcpListenUpdateSheduled)) {
    	    /* Set a timer to run TcpListenUpdate(). */
	    TimerInit(&TcpListenUpdateTimer, "TcpListenUpdate",
		0, TcpListenUpdate, NULL);
	    TimerStart(&TcpListenUpdateTimer);
	    TcpListenUpdateSheduled = 1;
  }
}

/*
 * TcpSetCommand()
 */

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

	switch ((intptr_t)arg) {
	case SET_PEERADDR:
	case SET_SELFADDR:
    		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;
    		}
		break;
	case SET_ENABLE:
		EnableCommand(ac, av, &pi->conf.options, gConfList);
    	    	TcpNodeUpdate(ctx->phys);
        	break;
        case SET_DISABLE:
    		DisableCommand(ac, av, &pi->conf.options, gConfList);
    		break;

	default:
		assert(0);
	}

	return (0);
}


syntax highlighted by Code2HTML, v. 0.9.1