/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

/*
 * ethernet driver for BPF
 *
 * This driver does not work in the current bsdi release (1.1) because of:
 *	- the bpfwrite code refuses to transmit packets bigger than a mbuf
 *	- the ether_output code insert the interface's hardware address into
 *	  the source address field of the transmitted frame
 * You need to apply the patches in the Misc/bsdi-patches directory. See
 * the README.
 */

/*
# define DUMPFILTER
*/

# include "epp.h"

RCSID("$Id: epp_bpf.c 461 2000-12-04 17:03:43Z hbb $")

# include <assert.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
# include <net/if.h>
# include <net/bpf.h>

# define BPF	"/dev/bpf%d"

/* short names */
typedef struct bpf_insn		Bpf_insn;
typedef struct bpf_program 	Bpf_program;
typedef struct bpf_hdr		Bpf_hdr;
typedef struct bpf_stat		Bpf_stat;
typedef struct bpf_version	Bpf_version;

char	*ifname;			/* interface name */
u_char	hwaddr[6];			/* my own address */
char	bpf_name[sizeof(BPF)+20];	/* attached bpf */
u_int	bpf_buflen;			/* receive buffer length */
u_char	*bpf_buf;			/* bpf receive buffer */
u_char	*bpf_ptr;			/* pointer to current frame */
u_char	*bpf_end;			/* end of buffer contents */

Bpf_insn	insns[BPF_MAXINSNS];	/* filter program instructions */
Bpf_program	prog;			/* program */
Bpf_insn	insns_null[1];		/* reject all instruction */
Bpf_program	prog_null;		/* program to reject all */

Bpf_stat	bpf_stat;		/* accumulated status */
Bpf_version 	vers;			/* kernal version */
Bpf_program	*cur_prog;		/* current program */

filter_t	oldf;			/* filter for which program was generated */

/*
 * macros to dynamicaly create filter instructions
 */
# define GEN_STMT(P,C,K) 	(void)((P)->code = (u_short)(C), (P)->k = (K), (P)++)
# define GEN_JUMP(P,C,K,T,F)	(void)((P)->code = (u_short)(C), (P)->jt = (T), (P)->jf = (F), (P)->k = (K), (P)++)


void	open_bpf(void);
void	gen_filter(void);
void	upd_status(void);

# ifdef DUMPFILTER
void	dump_filter(Bpf_program *);
# endif

int
main(int argc, char *argv[])
{
	int	opt;

	set_argv0(progname = argv[0]);
	while((opt = getopt(argc, argv, "v")) != EOF)
		switch(opt) {

		case 'v':
			verb_option("all");
			break;
		}

	argc -= optind;
	argv += optind;

	if(argc < 2)
		panic("need interface name and hardware address");
	ifname = argv[0];
	parse_ether(hwaddr, argv[1]);

	init_sigs();
	open_shmem();
	open_bpf();

	loop();
}

/*
 * open and setup bpf
 *	XXX should check bpf version number
 *	assumes, that rcv_enable is FALSE
 */
void
open_bpf()
{
	int	i;
	struct ifreq ifr;
	u_int	dlt;
	u_int	immed;
	Bpf_insn *p;

	/*
	 * find free bpf
	 */
	for(i = 0, fd = -1;; i++) {
		sprintf(bpf_name, BPF, i);
		if((fd = open(bpf_name, O_RDWR)) >= 0 || errno != EBUSY)
			break;
	}
	if(fd < 0)
		panic("bpf: %s", strerror(errno));

	/*
 	 * attach interface
	 */
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
	ifr.ifr_name[IFNAMSIZ-1] = '\0';
	if(ioctl(fd, BIOCSETIF, &ifr))
		panic("ioctl(BIOCSETIF): %s", strerror(errno));

	/*
	 * check that data link type is really 10Mbit ethernet
	 */
	if(ioctl(fd, BIOCGDLT, &dlt))
		panic("ioctl(BIOCGDLT): %s", strerror(errno));
	if(dlt != DLT_EN10MB)
		panic("%s: not an ethernet", ifname);

	/*
	 * get version.
	 */
	if(ioctl(fd, BIOCVERSION, &vers))
		panic("ioctl(BIOCVERSION): %s", strerror(errno));

	/*
	 * get buffer size and allocate buffer
	 */
	if(ioctl(fd, BIOCGBLEN, &bpf_buflen))
		panic("ioctl(BIOCGBLEN): %s", strerror(errno));
	if((bpf_buf = malloc(bpf_buflen)) == NULL)
		panic("bpf buffer allocation failed: %s", strerror(errno));
	bpf_end = bpf_ptr = bpf_buf;
	
	/*
	 * set immediate mode
	 */
	immed = 1;
	if(ioctl(fd, BIOCIMMEDIATE, &immed))
		panic("ioctl(BIOCIMMEDIATE): %s", strerror(errno));

	/*
	 * generate program to reject all frames
	 */
	p = insns_null;
	GEN_STMT(p, BPF_RET+BPF_K, 0);
	prog_null.bf_len = p - insns_null;
	prog_null.bf_insns = insns_null;

	gen_filter();
	set_filter();

	/*
	 * force promiscuous mode
	 * would be nice if there where a method to toggle this state
	 */
	if(ioctl(fd, BIOCPROMISC, 0))
		panic("ioctl(BIOCPROMISC): %s", strerror(errno));
}

/*
 * generate filter expression from global filter variables
 *
 * DEQNA doesn't hear his own transmissions, so filter them out
 */
# define SPLIT_W2(A)	(((u_int)(A)[2] << 24) | ((u_int)(A)[3] << 16) | \
			 ((u_int)(A)[4] <<  8) | ((u_int)(A)[5] <<  0))
# define SPLIT_H0(A)	(((u_int)(A)[0] <<  8) | ((u_int)(A)[1] <<  0))

void
gen_filter()
{
	Bpf_insn *p = insns;
	Bpf_insn *btrue, *bfalse;
	u_long hw, hl;
	u_int i;

	if(f.naddr == 0 && !f.promisc && !f.allmulti) {
		/*
		 * reject all
		 */
		GEN_STMT(p, BPF_RET+BPF_K, 0);

	} else if(f.promisc) {
		/*
		 * all, but ours
	 	 */
		bfalse = &insns[4];
		btrue = bfalse + 1;
		hw = SPLIT_W2(hwaddr);
		hl = SPLIT_H0(hwaddr);
		GEN_STMT(p, BPF_LD+BPF_W+BPF_ABS,	8);
		GEN_JUMP(p, BPF_JMP+BPF_JEQ+BPF_K,	hw, 0, btrue-(p+1));
		GEN_STMT(p, BPF_LD+BPF_H+BPF_ABS,	6);
		GEN_JUMP(p, BPF_JMP+BPF_JEQ+BPF_K,	hl, 0, btrue-(p+1));

		assert(p == bfalse);
		GEN_STMT(p, BPF_RET+BPF_K, 0);
		GEN_STMT(p, BPF_RET+BPF_K, (u_int)-1);

	} else {
		/*
		 * normal and, perhaps, allmulti
		 */
		bfalse = &insns[f.naddr * 4 	/* 4 instructions per address */
			    + 4 		/* filter for own address */
			    + 2 * (f.allmulti != 0)];
		btrue = bfalse + 1;

		/*
		 * filter own packets
		 */
		hw = SPLIT_W2(hwaddr);
		hl = SPLIT_H0(hwaddr);
		GEN_STMT(p, BPF_LD+BPF_W+BPF_ABS,	8);
		GEN_JUMP(p, BPF_JMP+BPF_JEQ+BPF_K,	hw, 0, 2);
		GEN_STMT(p, BPF_LD+BPF_H+BPF_ABS,	6);
		GEN_JUMP(p, BPF_JMP+BPF_JEQ+BPF_K,	hl, bfalse-(p+1), 0);

		if(f.allmulti) {
			GEN_STMT(p, BPF_LD+BPF_B+BPF_ABS,	0);
			GEN_JUMP(p, BPF_JMP+BPF_JSET+BPF_K,	1, btrue-(p+1), 0);
		}

		for(i = 0; i < f.naddr; i++) {
			hw = SPLIT_W2(f.addr[i]);
			hl = SPLIT_H0(f.addr[i]);
			GEN_STMT(p, BPF_LD+BPF_W+BPF_ABS,	2);
			GEN_JUMP(p, BPF_JMP+BPF_JEQ+BPF_K,	hw, 0, 2);
			GEN_STMT(p, BPF_LD+BPF_H+BPF_ABS,	0);
			GEN_JUMP(p, BPF_JMP+BPF_JEQ+BPF_K,	hl, btrue-(p+1), 0);
		}
		assert(p == bfalse);
		GEN_STMT(p, BPF_RET+BPF_K, 0);
		GEN_STMT(p, BPF_RET+BPF_K, (u_int)-1);
	}

	prog.bf_insns = insns;
	prog.bf_len = p - insns;

	oldf = f;

# ifdef DUMPFILTER
	dump_filter(&prog);
# endif
}

/*
 * got new filter.
 * If receiver is disable reject all packets, else set filter program
 */
void
set_filter()
{
	if(memcmp(&f, &oldf, sizeof(filter_t))) {
		gen_filter();
		if(ioctl(fd, BIOCSETF, &prog))
			panic("ioctl(BIOCSETF): %s", strerror(errno));
	}
}

/*
 * got reset from p11
 *
 * flush bpf. The accumulated status may not be exact because of
 * the window between GSTATS and FLUSH.
 */
void
reset()
{
	Bpf_stat statb;

	if(ioctl(fd, BIOCGSTATS, &statb))
		panic("ioctl(BIOCGSTATS): %s", strerror(errno));
	bpf_stat.bs_recv += statb.bs_recv;
	bpf_stat.bs_drop += statb.bs_drop;

	if(ioctl(fd, BIOCFLUSH, 0))
		panic("ioctl(BIOCSFLUSH): %s", strerror(errno));
	bpf_end = bpf_ptr = bpf_buf;
}

/*
 * receiver enable flag changed
 *
 * must change filter
 */
void
rcv_enable_change()
{
	set_filter();
}

/*
 * filter an input packet
 *
 * filtering already done by bpf so return TRUE
 */
int
input_filter(u_char *buf UNUSED)
{
	return 1;
}

/*
 * select return fd ready
 *
 * read frames from bpf
 */
u_int
read_input(u_char **pbuf, int *more)
{
	int	ret;
	Bpf_hdr	*h;

	*more = 0;

	if(bpf_ptr >= bpf_end) {
		if((ret = read(fd, bpf_buf, bpf_buflen)) <= 0)
			panic("read(bpf): %s", strerror(errno));
		bpf_ptr = bpf_buf;
		bpf_end = bpf_buf + ret;
# ifdef EPPDEBUG
		verb(V_DFLT, 1, "read_input: bpf_read = %d.\n", ret);
# endif
	}

	/*
	 * extract next frame
	 */
	h = (Bpf_hdr *)bpf_ptr;
	bpf_ptr = bpf_ptr + BPF_WORDALIGN(h->bh_hdrlen + h->bh_caplen);

	if(h->bh_caplen < h->bh_datalen) {
# ifdef EPPDEBUG
		verb(V_DFLT, 1, "caplen(%lu) < datalen(%lu) ??? - packet dropped.\n", h->bh_caplen, h->bh_datalen);
# endif
		ret = 0;
	} else {
		*pbuf = (u_char *)h + h->bh_hdrlen;
		ret = (int)h->bh_caplen;
	}

	*more = bpf_ptr < bpf_end;

# ifdef EPPDEBUG
	verb(V_DFLT, 1, "read_input: %d. (more=%d)\n", ret, *more);
# endif

	return ret;
}

/*
 * transmit a frame
 */
void
transmit()
{
	if((u_int)write(fd, xbuf, xlen) != xlen)
		panic("bpf_write: %s", strerror(errno));
}


/*
 * info
 */
# define P	(buf+strlen(buf))

void
do_info(char *buf)
{
	sprintf(P, "%s\n", progname);

	sprintf(P, "  bpf=%s", bpf_name);
	sprintf(P, "  buflen=%d", bpf_buflen);
	sprintf(P, "  if=%s", ifname);
	sprintf(P, "\n");

	sprintf(P, "  recv=%u", bpf_stat.bs_recv);
	sprintf(P, "  drop=%u", bpf_stat.bs_drop);

	sprintf(P, "  version=%d.%d", vers.bv_major, vers.bv_minor);
}

# ifdef DUMPFILTER
void
dump_filter(Bpf_program *prog)
{
	static FILE *fp;
	static int fn;
	int i;
	Bpf_insn *p;

	if(fp == NULL && (fp = fopen("Filter", "w")) == NULL)
		return;
	fprintf(fp, "FILTER %d:\n", fn++);

	for(p = prog->bf_insns, i = 0; i < prog->bf_len; p++, i++) {
		fprintf(fp, "%3d: ", i);

		switch(p->code) {

		case BPF_RET + BPF_K:
			fprintf(fp, "ret #0x%lx", p->k);
			break;

		case BPF_LD + BPF_B + BPF_ABS:
			fprintf(fp, "ldb [%lu]", p->k);
			break;

		case BPF_LD + BPF_H + BPF_ABS:
			fprintf(fp, "ldh [%lu]", p->k);
			break;

		case BPF_LD + BPF_W + BPF_ABS:
			fprintf(fp, "ld [%lu]", p->k);
			break;

		case BPF_JMP + BPF_JSET + BPF_K:
			fprintf(fp, "jset #0x%-8lx  jt %-3d      jf %-3d", p->k, p->jt + i + 1, p->jf + i + 1);
			break;

		case BPF_JMP + BPF_JEQ + BPF_K:
			fprintf(fp, "jeq  #0x%-8lx  jt %-3d      jf %-3d", p->k, p->jt + i + 1, p->jf + i + 1);
			break;

		default:
			fprintf(fp, "code=0x%4x k=%08lx jt=%u jf=%u", p->code, p->k, p->jt, p->jf);
			break;
		}

		fprintf(fp, "\n");
	}
}
# endif
