/*
 * ip tunnel/ethertap device for MacOSX.
 *
 * The class tuntaptap_interface contains the common functionality of tuntap_interface and
 * tap_interface.
 */
/*
 * Copyright (c) 2011 Mattias Nissler <mattias.nissler@gmx.de>
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright notice, this list of
 *      conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright notice, this list of
 *      conditions and the following disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *   3. The name of the author may not be used to endorse or promote products derived from this
 *      software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __TUNTAP_H__
#define __TUNTAP_H__

#include "util.h"
#include "lock.h"

extern "C" {

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/systm.h>
#include <sys/kpi_mbuf.h>

#include <kern/locks.h>

#include <net/if.h>
#include <net/bpf.h>
#include <net/kpi_interface.h>

}

extern "C" {

errno_t tuntap_if_output(ifnet_t ifp, mbuf_t m);
errno_t tuntap_if_ioctl(ifnet_t ifp, long unsigned int cmd, void *arg);
errno_t tuntap_if_set_bpf_tap(ifnet_t ifp, bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t));
errno_t tuntap_if_demux(ifnet_t ifp, mbuf_t m, char *header, protocol_family_t *proto);
errno_t tuntap_if_framer(ifnet_t ifp, mbuf_t *m, const struct sockaddr *dest,
		const char *dest_linkaddr, const char *frame_type);
errno_t tuntap_if_add_proto(ifnet_t ifp, protocol_family_t proto,
		const struct ifnet_demux_desc *ddesc, u_int32_t ndesc);
errno_t tuntap_if_del_proto(ifnet_t ifp, protocol_family_t proto);
errno_t tuntap_if_check_multi(ifnet_t ifp, const struct sockaddr *maddr);
void tuntap_if_detached(ifnet_t ifp);

}

/* forward declaration */
class tuntap_interface;

/* both interface families have their manager object that will create, initialize, shutdown and
 * delete interfaces. This is (mostly) generic so it can be used both for tun and tap. The only
 * exception is the interface creation, therefore this class is abstract. tun and tap have their own
 * versions that simply fill in create_interface().
 */
class tuntap_manager {

	protected:
		/* manager cdev gate */
		tt_gate cdev_gate;
		/* interface count */
		unsigned int count;
		/* an array holding all the interface instances */
		tuntap_interface **tuntaps;
		/* the major device number */
		int dev_major;
		/* family name */
		char *family;

		/* wether static members are initialized */
		static bool statics_initialized;

		/* major-to-manager-map */
		static const int MAX_CDEV = 256;
		static tuntap_manager *mgr_map[MAX_CDEV];

		/* initializes static members */
		void initialize_statics();

	public:
		/* sets major device number, allocates the interface table. */
		bool initialize(unsigned int count, char *family);

		/* tries to shutdown the family. returns true if successful. the manager object may
		 * not be deleted if this wasn't called successfully.
		 */
		bool shutdown();

		/* the destructor deletes allocated memory and unregisters the character device
		 * switch */
		virtual ~tuntap_manager();

		/* here are the cdev routines for the class. They will figure out the manager object
		 * and call the service methods declared below.
		 */
		static int cdev_open(dev_t dev, int flags, int devtype, proc_t p);
		static int cdev_close(dev_t dev, int flags, int devtype, proc_t p);
		static int cdev_read(dev_t dev, uio_t uio, int ioflag);
		static int cdev_write(dev_t dev, uio_t uio, int ioflag);
		static int cdev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag,
				proc_t p);
		static int cdev_select(dev_t dev, int which, void *wql, proc_t p);

	protected:
		/* Here are the actual service routines that will do the required things (creating
		 * interfaces and such) and forward to the interface's implementation.
		 */
		int do_cdev_open(dev_t dev, int flags, int devtype, proc_t p);
		int do_cdev_close(dev_t dev, int flags, int devtype, proc_t p);
		int do_cdev_read(dev_t dev, uio_t uio, int ioflag);
		int do_cdev_write(dev_t dev, uio_t uio, int ioflag);
		int do_cdev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, proc_t p);
		int do_cdev_select(dev_t dev, int which, void *wql, proc_t p);

		/* abstract method that will create an interface. Implemented by tun and tap */
		virtual tuntap_interface *create_interface() = 0;
		
		/* makes sure there is one idle interface available (if nothing fails */
		void ensure_idle_device();

};

/* a class implementing a mbuf packet queue. On Darwin 7 we had struct ifqueue, but that is now
 * internal to the kernel for Darwin 8. So lets have our own.
 */
class tuntap_mbuf_queue {

	private:
		/* output end of the queue. dequeueing takes mbufs from here */
		mbuf_t head;
		/* input end. new mbufs are appended here. */
		mbuf_t tail;

		/* size */
		unsigned int size;

		/* maximum queue size */
		static const unsigned int QUEUE_SIZE = 128;

	public:
		/* initialize new empty queue */
		tuntap_mbuf_queue();
		~tuntap_mbuf_queue();

		/* is the queue full? */
		bool full() { return size == QUEUE_SIZE; }
		/* is it emtpy? */
		bool empty() { return size == 0; }

		/* enqueue an mbuf. returns true if there was space left, so the mbuf could be
		 * queued, false otherwise */
		bool enqueue(mbuf_t mb);

		/* tries to dequeue the next mbuf. If the queue is empty, NULL is returned */
		mbuf_t dequeue();

		/* makes the queue empty, discarding any queue packets */
		void clear();
};

class tuntap_interface {

	protected:
		/* interface number */
		unsigned int unit;
		/* family name */
		char *family_name;
		/* family identifier */
		ifnet_family_t family;
		/* interface type */
		u_int32_t type;
		/* id string */
		static const unsigned int UIDLEN = 20;
		char unique_id[UIDLEN];

		/* synchronization */
		tt_mutex lock;
		tt_mutex bpf_lock;
		tt_mutex thread_sync_lock;

		/* the interface structure registered */
		ifnet_t ifp;
		/* whether the device has been opened */
		bool open;
		/* whether we are doing blocking i/o */
		bool block_io;
		/* whether the interface has properly been detached */
		bool interface_detached;
		/* handle to the devfs node for the character device */
		void *dev_handle;
		/* the pid of the process that opened the cdev, if any */
		pid_t pid;
		/* read select info */
		struct selinfo rsel;
		/* bpf mode, wether filtering is on or off */
		bpf_tap_mode bpf_mode;
		/* bpf callback. called when packet arrives/leaves */
		int (*bpf_callback)(ifnet_t, mbuf_t);
		/* pending packets queue (for output), must be accessed with the lock held */
		tuntap_mbuf_queue send_queue;
		/* whether an ioctl that we issued is currently being processed */
		bool in_ioctl;

		/* protected constructor. initializes most of the members */
		tuntap_interface();
		virtual ~tuntap_interface();

		/* initialize the device */
		virtual bool initialize(unsigned short major, unsigned short unit) = 0;

		/* character device management */
		virtual bool register_chardev(unsigned short major);
		virtual void unregister_chardev();

		/* network interface management */
		virtual bool register_interface(const struct sockaddr_dl *lladdr,
				void *bcaddr, u_int32_t bcaddrlen);
		virtual void unregister_interface();
		virtual void cleanup_interface();

		/* called when the character device is opened in order to intialize the network
		 * interface.
		 */
		virtual int initialize_interface() = 0;
		/* called when the character device is closed to shutdown the network interface */
		virtual void shutdown_interface() = 0;

		/* check wether the interface is idle (so it can be brought down) */
		virtual bool idle();

		/* shut it down */
		virtual void shutdown() = 0;

		/* notifies BPF of a packet coming through */
		virtual void notify_bpf(mbuf_t mb, bool out);

		/* executes a socket ioctl through a temporary socket */
		virtual void do_sock_ioctl(sa_family_t af, unsigned long cmd, void* arg);

		/* character device service methods. Called by the manager */
		virtual int cdev_open(int flags, int devtype, proc_t p);
		virtual int cdev_close(int flags, int devtype, proc_t p);
		virtual int cdev_read(uio_t uio, int ioflag);
		virtual int cdev_write(uio_t uio, int ioflag);
		virtual int cdev_ioctl(u_long cmd, caddr_t data, int fflag, proc_t p);
		virtual int cdev_select(int which, void *wql, proc_t p);

		/* interface functions. friends and implementation methods */
		friend errno_t tuntap_if_output(ifnet_t ifp, mbuf_t m);
		friend errno_t tuntap_if_ioctl(ifnet_t ifp, long unsigned int cmd, void *arg);
		friend errno_t tuntap_if_set_bpf_tap(ifnet_t ifp, bpf_tap_mode mode,
				int (*cb)(ifnet_t, mbuf_t));
		friend errno_t tuntap_if_demux(ifnet_t ifp, mbuf_t m, char *header,
				protocol_family_t *proto);
		friend errno_t tuntap_if_framer(ifnet_t ifp, mbuf_t *m, const struct sockaddr *dest,
				const char *dest_linkaddr, const char *frame_type);
		friend errno_t tuntap_if_add_proto(ifnet_t ifp, protocol_family_t proto,
				const struct ifnet_demux_desc *ddesc, u_int32_t ndesc);
		friend errno_t tuntap_if_del_proto(ifnet_t ifp, protocol_family_t proto);
		friend errno_t tuntap_if_check_multi(ifnet_t ifp, const struct sockaddr *maddr);
		friend void tuntap_if_detached(ifnet_t ifp);

		virtual errno_t if_output(mbuf_t m);
		virtual errno_t if_ioctl(u_int32_t cmd, void *arg);
		virtual errno_t if_set_bpf_tap(bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t));
		virtual errno_t if_demux(mbuf_t m, char *header, protocol_family_t *proto) = 0;
		virtual errno_t if_framer(mbuf_t *m, const struct sockaddr *dest,
				const char *dest_linkaddr, const char *frame_type) = 0;
		virtual errno_t if_add_proto(protocol_family_t proto,
				const struct ifnet_demux_desc *ddesc, u_int32_t ndesc) = 0;
		virtual errno_t if_del_proto(protocol_family_t proto) = 0;
		virtual errno_t if_check_multi(const struct sockaddr *maddr);
		virtual void if_detached();

		/* tuntap_manager feeds us with cdev input, so it is our friend */
		friend class tuntap_manager;
};

#endif /* __TUNTAP_H__ */

