//#include <sys/syslog.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_ether.h>
#include <sys/mbuf.h>
#include "sys/io-pkt.h"
#include <errno.h>
#include <stdio.h>

#define log(x,...) fprintf(stderr, __VA_ARGS__)
#define LOGERR

typedef struct {
	struct ifnet	*port_ifp;
	struct ifnet	*mirror_ifp;
	void		*dll_hdl;
} mirror_hdl_t;

static int mirror_input (void *arg, struct mbuf **mp, struct ifnet *ifp,
		int dir)
{
	mirror_hdl_t	*mirror = arg;
	struct mbuf		*m;
	struct sockaddr	edst;

	m = m_dup(*mp, 0, (*mp)->m_len, M_DONTWAIT);
	if (m == NULL) {
		goto DONE;
	}

	/*
	 * A full ether packet is in m, we need to put the ether header into a
	 * sockaddr to satisfy ether_output()
	 */
	memset(&edst, 0, sizeof(edst));
	edst.sa_family = AF_UNSPEC;
	memcpy(edst.sa_data, m->m_data, sizeof(struct ether_header));
	m->m_data += sizeof(struct ether_header);
	m->m_len -= sizeof(struct ether_header);
	m->m_pkthdr.len -= sizeof(struct ether_header);

	mirror->mirror_ifp->if_output(mirror->mirror_ifp, m, &edst, 0);

DONE:
	/* Let the packet continue */
	return 0;
}

static int mirror_entry (void *dll_hdl, struct _iopkt_self *iopkt,
		char *options)
{
	mirror_hdl_t	*mirror;
	int			opt, ret;
	char		*value;

	char *myopt[] = {
#define INTERFACE 0
		"if",
#define MIRROR 1
		"mirror",
		NULL
	};

	if ((mirror = calloc(sizeof(*mirror), 1)) == NULL) {
		log(LOG_ERR, "mirror can't calloc: %s", strerror(errno));
		return ENOMEM;
	}

	while(options && *options != '\0') {
		opt = getsubopt(&options, myopt, &value);
		switch (opt) {
			case INTERFACE:
				if ((value == NULL) || (strlen(value) < 3)){
					log(LOG_ERR, "bad if value");
					ret = EINVAL;
					goto FAIL1;
				}
				mirror->port_ifp = ifunit(value);
				if ((mirror->port_ifp != NULL) &&
						(mirror->port_ifp->if_type != IFT_ETHER)) {
					log(LOG_ERR, "bad if port");
					ret = EINVAL;
					goto FAIL1;
				}
				break;
			case MIRROR:
				if ((value == NULL) || (strlen(value) < 3)){
					log(LOG_ERR, "bad mirror value");
					ret = EINVAL;
					goto FAIL1;
				}
				mirror->mirror_ifp = ifunit(value);
				if ((mirror->mirror_ifp != NULL) &&
						(mirror->mirror_ifp->if_type != IFT_ETHER)) {
					log(LOG_ERR, "bad mirror port");
					ret = EINVAL;
					goto FAIL1;
				}
				break;
			default: 
				log(LOG_ERR, "Ignoring unknown option %s", value);
		}
	}

	if (mirror->port_ifp == NULL){
		log(LOG_ERR, "bad if port: not specified or not present\n");
		ret = EINVAL;
		goto FAIL1;
	}

	if (mirror->mirror_ifp == NULL){
		log(LOG_ERR, "bad mirror port: not specified or not present\n");
		ret = EINVAL;
		goto FAIL1;
	}

	if (mirror->port_ifp == mirror->mirror_ifp){
		log(LOG_ERR, "invalid mirror target: if and mirror ports can not be the same\n");
		ret = EINVAL;
		goto FAIL1;
	}

	if (pfil_add_hook(mirror_input, mirror, PFIL_ALL,
				&mirror->port_ifp->if_pfil) != EOK) {
		log(LOG_ERR, "Failed to add packet hook: %s", strerror(errno));
		goto FAIL1;
	}
	return EOK;

FAIL1:
	(free)(mirror);
	return ret;
}

struct _iopkt_lsm_entry IOPKT_LSM_ENTRY_SYM(mirror) =
IOPKT_LSM_ENTRY_SYM_INIT(mirror_entry);
