/*
 * Copyright (c) 2002-2009 Chris Cappuccio <chris@nmedia.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/sockio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/limits.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <netinet6/in6_var.h>
#include <net/if_vlan_var.h>
#include <net/route.h>
#include <net/pfvar.h>
#include <netmpls/mpls.h>
#include <netinet/ip_ipsp.h>
#include <net/if_pfsync.h>
#include <net/if_pflow.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <limits.h>
#include "stringlist.h"
#include "externs.h"
#include "bridge.h"
#include "ctl.h"
#include "sysctl.h"

#define IPSIZ  256	/*
			 * max theoretical size of ipv4 or ipv6
			 * text representation
			 */
#define TMPSIZ 1024	/* size of temp strings */
#define ROUTEMTU 32768	/* common route MTU */
#define MTU_IGNORE ULONG_MAX	/* ignore this "default" mtu */

void conf_db_single(FILE *, char *, char *, char *);
void conf_print_rtm(FILE *, struct rt_msghdr *, char *, int);
int conf_ifaddrs(FILE *, char *, int, int);
int conf_ifaddr_dhcp(FILE *, char *, int, int);
void conf_lladdr(FILE *, char *);
void conf_ifflags(FILE *, int, char *, int, u_char);
void conf_vnetid(FILE *, int, char *);
void conf_vnetflowid(FILE *, int, char *);
void conf_parent(FILE *, int, char *);
void conf_patch(FILE *, int, char *);
void conf_brcfg(FILE *, int, struct if_nameindex *, char *);
void conf_ifxflags(FILE *, int, char *);
void conf_rtables(FILE *);
void conf_rtables_rtable(FILE *, int);
void conf_rdomain(FILE *, int, char *);
void conf_tunnel(FILE *, int, char *);
void conf_ifmetrics(FILE *, int, struct if_data, char *);
void conf_pflow(FILE *, int, char *);
void conf_pwe3(FILE *, int, char *);
void conf_ctl(FILE *, char *, char *, int);
void conf_intrtlabel(FILE *, int, char *);
void conf_intgroup(FILE *, int, char *);
void conf_keepalive(FILE *, int, char *);
void conf_rtflags(char *, int, struct rt_msghdr *rtm);
int dhcpleased_has_defaultroute(struct sockaddr_rtlabel *);
int dhcpleased_controls_interface(char *, int);
int default_txprio(char *);
int default_rxprio(char *);
int default_llpriority(char *);
int islateif(char *);
int isdefaultroute(struct sockaddr *, struct sockaddr *);
int scantext(char *, char *);
int ipv6ll_db_compare(struct sockaddr_in6 *, struct sockaddr_in6 *,
    char *);

static const struct {
	char *name;
	u_long mtu;
} defmtus[] = {
	{ "gre",	1476 },
	{ "wg",		1420 },
	{ "gif",	1280 },
	{ "enc",	1536 },
	{ "pflow",	MTU_IGNORE },
	{ "pflog",	MTU_IGNORE },
	{ "pfsync",	MTU_IGNORE },
	{ "lo",		MTU_IGNORE },
};

static const struct {
	char *name;
	int rxprio;
	int txprio;
} defprios[] = {
	{ "bpe",	IF_HDRPRIO_OUTER,	IF_HDRPRIO_PACKET },
	{ "nvgre",	IF_HDRPRIO_PACKET,	0 },
	{ "etherip",	IF_HDRPRIO_PACKET,	0 },
	{ "gre",	IF_HDRPRIO_PACKET,	IF_HDRPRIO_PAYLOAD },
	{ "mgre",	IF_HDRPRIO_PACKET,	IF_HDRPRIO_PAYLOAD },
	{ "egre",	IF_HDRPRIO_PACKET,	0 },
	{ "mpe",	IF_HDRPRIO_PACKET,	0 },
	{ "mpip",	IF_HDRPRIO_PACKET,	0 },
	{ "mpw",	IF_HDRPRIO_PACKET,	0 },
	{ "vxlan",	IF_HDRPRIO_OUTER,	0 },
	{ "vlan",	IF_HDRPRIO_OUTER,	IF_HDRPRIO_PACKET },
	{ "svlan",	IF_HDRPRIO_OUTER,	IF_HDRPRIO_PACKET },
	{ "gif",	IF_HDRPRIO_PAYLOAD,	IF_HDRPRIO_PAYLOAD },
	{ "eoip",	IF_HDRPRIO_PACKET,	0 },
};

static const struct {
	char *name;
	int llpriority;
} defllpriority[] = {
	{ "gre",	IFQ_TOS2PRIO(IPTOS_PREC_INTERNETCONTROL) },
	{ "aggr",	IFQ_MAXPRIO },
	{ "tpmr",	IFQ_MAXPRIO },
};

/*
 * these interfaces get started in a specific order
 *
 * pfsync gets delayed until pf rules are loaded
 *
 * /etc/netstart says:
 *
 * The trunk interfaces need to come up first in this list.
 * The (s)vlan interfaces need to come up after trunk.
 * Configure all the carp interfaces which we know about before default route.
 *
 * Configure PPPoE, GIF, GRE and TUN interfaces, delayed because they require
 * routes to be set.  TUN might depend on PPPoE, and GIF or GRE may depend on
 * either of them.
 */

static const struct {
	char *name;
} latestartifs[] = {
	{ "aggr" },
	{ "trunk" },
	{ "svlan" },
	{ "vlan" },
	{ "carp" },
	{ "pppoe" },
	{ "pfsync" },
	{ "tun" },
	{ "tap" },
	{ "gif" },
	{ "etherip" },
	{ "gre" },
	{ "egre" },
	{ "nvgre" },
	{ "eoip" },
	{ "vxlan" },
	{ "pflow" },
	{ "wg" },
	{ "bridge" },
	{ "veb" },
	{ "tpmr" }
};

int
conf(FILE *output)
{
	char cpass[_PASSWORD_LEN+1];
	char hostbuf[MAXHOSTNAMELEN];
	off_t offset;

	fprintf(output, "!\n");

	gethostname (hostbuf, sizeof(hostbuf));
	fprintf(output, "hostname %s\n", hostbuf);
	if (read_pass(cpass, sizeof(cpass))) {
		fprintf(output, "enable secret blowfish %s\n", cpass);
	} else {
		if (errno != ENOENT)
			printf("%% Unable to read run-time crypt repository:"
			    " %s\n", strerror(errno));
	}
	fprintf(output, "!\n");
	conf_ctl(output, "", "motd", 0);

	/*
	 * start all intefaces not listed in 'latestartifs'
	 */
	conf_interfaces(output, NULL, 0);
	/*
	 * start these interfaces in specific order
	 */
	conf_interfaces(output, "aggr", 0);
	conf_interfaces(output, "trunk", 0);
	conf_interfaces(output, "svlan", 0);
	conf_interfaces(output, "vlan", 0);
	conf_interfaces(output, "carp", 0);
	conf_interfaces(output, "pppoe", 0);

	fprintf(output, "!\n");

	/*
	 * check out how sysctls are doing these days
	 *
	 * Each of these options, like most other things in the config output
	 * (such as interface flags), must display if the kernel's default
	 * setting is not currently set.
	 */
	conf_sysctls(output);

	fprintf(output, "!\n");

	/*
	 * print static arp and route entries in configuration file format
	 */
	conf_arp(output, "arp ");
	conf_ndp(output, "ndp ");
	conf_routes(output, "route ", AF_INET, RTF_STATIC, 0);
	conf_routes(output, "route ", AF_INET6, RTF_STATIC, 0);

	fprintf(output, "!\n");
	/*
	 * these interfaces must start after routes are set
	 */
	conf_interfaces(output, "tun", 0);
	conf_interfaces(output, "tap", 0);
	conf_interfaces(output, "gif", 0);
	conf_interfaces(output, "etherip", 0);
	conf_interfaces(output, "gre", 0);
	conf_interfaces(output, "egre", 0);
	conf_interfaces(output, "nvgre", 0);
	conf_interfaces(output, "eoip", 0);
	conf_interfaces(output, "vxlan", 0);
	conf_interfaces(output, "wg", 0);
	conf_interfaces(output, "bridge", 0);
	conf_interfaces(output, "veb", 0);
	conf_interfaces(output, "tpmr", 0);

	fprintf(output, "!\n");
	conf_ctl(output, "", "pf", 0);

	/*
	 * this interface must start after pf is loaded
	 */
	conf_interfaces(output, "pfsync", 0);
	conf_interfaces(output, "pflow", 0);

	conf_ctl(output, "", "snmp", 0);
	conf_ctl(output, "", "resolv", 0);
	conf_ctl(output, "", "ldp", 0);
	conf_ctl(output, "", "rip", 0);
	conf_ctl(output, "", "ospf", 0);
	conf_ctl(output, "", "ospf6", 0);
	conf_ctl(output, "", "bgp", 0);
	conf_ctl(output, "", "ifstate", 0);
	conf_ctl(output, "", "ipsec", 0);
	conf_ctl(output, "", "ike", 0);
	conf_ctl(output, "", "rad", 0);
	conf_ctl(output, "", "dvmrp", 0);
	conf_ctl(output, "", "relay", 0);
	conf_ctl(output, "", "sasync", 0);
	conf_ctl(output, "", "dhcp", 0);
	conf_ctl(output, "", "ntp", 0);
	conf_ctl(output, "", "smtp", 0);
	conf_ctl(output, "", "ldap", 0);
	conf_ctl(output, "", "ftp-proxy", 0);
	conf_ctl(output, "", "inet", 0);
	conf_ctl(output, "", "sshd", 0);

	offset = ftello(output);
	conf_ctl(output, "", "crontab", 0);
	if (offset != ftello(output)) /* we have custom crontab rules */
		fprintf(output, "crontab install\n");

	conf_rtables(output);

	fprintf(output, "!\n");
	conf_nameserver(output);

	return(0);
}

void conf_rtables(FILE *output)
{
	int i, rtableid;
	StringList *rtables;

	rtables = sl_init();
	if (db_select_rtable_rtables(rtables) < 0) {
		printf("%% database failure select rtables rtable\n");
		sl_free(rtables, 1);
		return;
	}
	for (i = 0; i < rtables->sl_cur; i++) {
		const char *errmsg = NULL;

		rtableid = strtonum(rtables->sl_str[i], 0, RT_TABLEID_MAX, &errmsg);
		if (rtableid == 0)
			continue;
		if (errmsg) {
			printf("%% Invalid route table (%d) %s: %s\n",  i,
			    rtables->sl_str[i], errmsg);
			continue;
		}
		conf_rtables_rtable(output, rtableid);
	}

	sl_free(rtables, 1);
}

void conf_rtables_rtable(FILE *output, int rtableid)
{
	int i;
	StringList *rtable_name, *rtable_daemons;

	rtable_name = sl_init();

	if (db_select_name_rtable(rtable_name, rtableid) < 0) {
		printf("%% database failure select rtables name\n");
		sl_free(rtable_name, 1);
		return;
	} else {
		fprintf(output, "rtable %d %s\n", rtableid,
		    rtable_name->sl_str[0]);
	}

	sl_free(rtable_name, 1);

	/*
	 * Routes must be printed before we attempt to start daemons,
	 * else rtables will not be created in the kernel (Unless an
	 * rdomain is created by specifing one on an interface prior
	 * to this point. An rdomain creates a new corresponding rtable)
	 */
	conf_arp(output, " arp ");
	conf_ndp(output, " ndp ");
	conf_routes(output, " route ", AF_INET, RTF_STATIC, rtableid);
	conf_routes(output, " route ", AF_INET6, RTF_STATIC, rtableid);

	rtable_daemons = sl_init();

	if (db_select_flag_x_ctl_rtable(rtable_daemons, "ctl", rtableid) < 0) {
		printf("%% database failure select ctl rtable\n");
		sl_free(rtable_daemons, 1);
		return;
	} else {
		for (i = 0; i < rtable_daemons->sl_cur; i++)
			conf_ctl(output, " ", rtable_daemons->sl_str[i], rtableid);
	}

	sl_free(rtable_daemons, 1);

	fprintf(output, "!\n");
}

/*
 * Figure out whether this particular ctl accepts the standard "enable",
 * etc. argument or calls them something else.
 * This is done by mapping DB_X_ flags to ctl table entry names.
 *
 * XXX This currently ignores "disable" arguments; They will always be
 * displayed as "disable" even when the table entry with the DB_X_DISABLE
 * flag uses a different name...
 */ 
static void
get_argnames(int flag_x, char *name, int *defenable, char **fenablenm,
    char **flocalnm, char **fothernm)
{
	switch(flag_x) {
	case DB_X_ENABLE_DEFAULT:
		*defenable = 1;
		/* FALLTHROUGH */
	case DB_X_ENABLE:
		*fenablenm = name;
		break;
	case DB_X_LOCAL:
		*flocalnm = name;
		break;
	case DB_X_OTHER:
		*fothernm = name;
		break;
	case DB_X_DISABLE:
	case DB_X_REMOVE:
	case DB_X_DISABLE_ALWAYS:
	case 0:
		break;
	default:
		printf("%% conf_ctl: flag_x %d unknown\n", flag_x);
	}
}

void conf_ctl(FILE *output, char *delim, char *name, int rtableid)
{
	FILE *conf;
	struct daemons *x;
	struct ctl *ctl;
	char tmp_str[TMPSIZ], tmpfile[64];
	char *fenablenm = NULL, *fothernm = NULL, *flocalnm = NULL;
	int defenable = 0, pntdrules = 0, pntdflag = 0, dbflag;

	x = (struct daemons *)genget(name, (char **)ctl_daemons,
	    sizeof(struct daemons));
	if (x == NULL || Ambiguous(x)) {
		printf("%% conf_ctl: %s: genget internal failure\n",
		    name);
		return;
	}

	/* print rules if they exist */
	snprintf(tmpfile, sizeof(tmpfile), "%s.%d", x->tmpfile, rtableid);
	if ((conf = fopen(tmpfile, "r")) != NULL) {
		fprintf(output, "%s%s rules\n", delim, name);
		for (;;) {
			if(fgets(tmp_str, TMPSIZ, conf) == NULL)
				break;
			if(tmp_str[0] == 0)
				break;
			fprintf(output, "%s %s", delim, tmp_str);
		}
		fclose(conf);
		fprintf(output, "%s!\n", delim);
		pntdrules = 1;
	} else if (errno != ENOENT || (errno == ENOENT && verbose))
		printf("%% conf_ctl: %s: %s\n", tmpfile, strerror(errno));

	/* fill in argument names from table */
	for (ctl = x->table; ctl != NULL && ctl->name != NULL; ctl++) {
		get_argnames(ctl->flag_x, ctl->name, &defenable, &fenablenm,
		    &flocalnm, &fothernm);
	}

	/* print rules as currently enabled in running time database */
	if ((dbflag = db_select_flag_x_dbflag_rtable("ctl", x->name, rtableid))
	    < 0) {
		printf("%% database ctl select failure (%s, %d)\n", x->name,
		    rtableid);
		return;
	}
	switch(dbflag) {
	case DB_X_ENABLE:
		fprintf(output, "%s%s %s\n", delim, x->name, fenablenm ?
		    fenablenm : "enable");
		pntdflag = 1;
		break;
	case DB_X_LOCAL:
		fprintf(output, "%s%s %s\n", delim, x->name, flocalnm ?
		    flocalnm : "local");
		pntdflag = 1;
		break;
	case DB_X_OTHER:
		fprintf(output, "%s%s %s\n", delim, x->name, fothernm ?
		    fothernm : "other");
		pntdflag = 1;
		break;
	case DB_X_DISABLE_ALWAYS:
		fprintf(output, "%s%s disable\n", delim, x->name);
		pntdflag = 1;
		/* FALLTHROUGH */
	case DB_X_DISABLE:
		defenable = 0;
		break;
	case DB_X_REMOVE:
	case DB_X_ENABLE_DEFAULT:
	case 0:
		break;
	default:
		printf("%% conf_ctl: dbflag %d unknown\n", dbflag);
	}
	if (defenable) {
		fprintf(output, "%s%s %s\n", delim, x->name, fenablenm ?
		    fenablenm : "enable");
		pntdflag = 1;
	}
	if (pntdrules && x->doreload) {
		fprintf(output, "%s%s reload\n", delim, x->name);
		pntdflag = 1;
	}
	if (pntdflag)
		fprintf(output, "%s!\n", delim);
}

int
dhcpleased_has_defaultroute(struct sockaddr_rtlabel *sr)
{
	return (sr != NULL && strcmp(sr->sr_label, "dhcpleased") == 0);
}

/* Check whether IPv4 addresses on an interface are managed by dhcpleased. */
int
dhcpleased_controls_interface(char *ifname, int ifs)
{
	int ifxflags;

	if (!dhcpleased_is_running())
		return 0;

	ifxflags = get_ifxflags(ifname, ifs);
	return ((ifxflags & IFXF_AUTOCONF4) != 0);
}

/*
 * Check whether a specific IPv4 addresses on an interface is
 * managed by dhcpleased.
 */
int
dhcpleased_has_address(char *ifname, const char *address,
    const char *netmask)
{
	char *argv[] = { DHCPLEASECTL, "-l", ifname, NULL };
	int address_found = 0;
	char ortext[128];
	char outpath[PATH_MAX];
	int fd = -1, nullfd = -1;

	if (!dhcpleased_is_running())
		return 0;

	snprintf(ortext, sizeof(ortext), "\tinet %s netmask %s\n",
	    address, netmask);

	nullfd = open("/dev/null", O_WRONLY | O_NOFOLLOW | O_CLOEXEC);
	if (nullfd == -1) {
		printf("%% open /dev/null: %s\n", strerror(errno));
		return 0;
	}

	strlcpy(outpath, "/tmp/nsh-XXXXXX", sizeof(outpath));
	fd = mkstemp(outpath);
	if (fd == -1) {
		printf("%% mkstemp: %s\n", strerror(errno));
		close(nullfd);
		return 0;
	}

	if (cmdargs_output(DHCPLEASECTL, argv, fd, nullfd) == 0 &&
	    scantext(outpath, ortext))
		address_found = 1;

	unlink(outpath);
	close(fd);
	close(nullfd);
	return (address_found);
}

/* find string in file */
int scantext(char *fname, char *string)
{
	FILE *file;
	char line[128];
	int found = 0;

	if ((file = fopen(fname, "r")) == 0) {
		printf("%% Unable to open %s: %s\n", fname, strerror(errno));
		return(0);
	}

	for (;;) {
		if (fgets(line, sizeof(line), file) == NULL)
			break;
		if (strcmp(line, string) == 0) {
			found = 1;
			break;
		}
	}

	fclose(file);
	return(found);
}

int islateif(char *ifname)
{
	int i;

	for (i = 0; i < nitems(latestartifs); i++)
		if (isprefix(latestartifs[i].name, ifname))  
			return(1);

	return(0);
}

void
conf_db_single(FILE *output, char *dbname, char *lookup, char *ifname)
{
	StringList *dbreturn;
	dbreturn = sl_init();

	if (db_select_flag_x_ctl(dbreturn, dbname, ifname) < 0) {
		printf("%% conf_db_single %s database select failed\n", dbname);
	}
	if (dbreturn->sl_cur > 0) {
		if (lookup == NULL)
			fprintf(output, " %s\n", dbname);
		else if (strcmp(dbreturn->sl_str[0], lookup) != 0)
			fprintf(output, " %s %s\n", dbname, dbreturn->sl_str[0]);
	}
	sl_free(dbreturn, 1);
}

void conf_interfaces(FILE *output, char *only, int exact_match)
{
	int ifs, flags, ippntd, br;
	char ifdescr[IFDESCRSIZE];

	struct if_nameindex *ifn_list, *ifnp;
	struct ifreq ifr, ifrdesc;
	struct if_data if_data;

	if ((ifn_list = if_nameindex()) == NULL) {
		printf("%% conf_interfaces: if_nameindex failed\n");
		return;
	}

	if ((ifs = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		printf("%% conf_interfaces socket: %s\n", strerror(errno));
		if_freenameindex(ifn_list);
		return;
	}

	for (ifnp = ifn_list; ifnp->if_name != NULL; ifnp++) {
		if (only) {
			/* only interfaces which match, or start with ... */
			if (exact_match) {
				if (strcmp(only, ifnp->if_name) != 0)
					continue;
			} else if (!isprefix(only, ifnp->if_name))
				continue;
		}
		if (!only && islateif(ifnp->if_name))
			/* interface prefixes to exclude on generic run */
			continue;

		strlcpy(ifr.ifr_name, ifnp->if_name, sizeof(ifr.ifr_name));

		if (ioctl(ifs, SIOCGIFFLAGS, (caddr_t)&ifr) < 0) {
			printf("%% conf: SIOCGIFFLAGS: %s\n", strerror(errno));
			continue;
		}
		flags = ifr.ifr_flags;

		ifr.ifr_data = (caddr_t)&if_data;
		if (ioctl(ifs, SIOCGIFDATA, (caddr_t)&ifr) < 0) {
			printf("%% conf: SIOCGIFDATA: %s\n", strerror(errno));
			continue;
		}

		/* The output order is important! */

		/* set interface/bridge mode */
		if (!(br = is_bridge(ifs, ifnp->if_name)))
			br = 0;
		fprintf(output, "%s %s\n", br ? "bridge" : "interface",
		    ifnp->if_name);

		/*
		 * description, if available
		 * copied straight from ifconfig.c
		 */
		memset(&ifrdesc, 0, sizeof(ifrdesc));
		strlcpy(ifrdesc.ifr_name, ifnp->if_name,
		    sizeof(ifrdesc.ifr_name));
		ifrdesc.ifr_data = (caddr_t)&ifdescr;
		if (ioctl(ifs, SIOCGIFDESCR, &ifrdesc) == 0 &&
		    strlen(ifrdesc.ifr_data))
			fprintf(output, " description %s\n", ifrdesc.ifr_data);

		conf_lladdr(output, ifnp->if_name);

		conf_vnetid(output, ifs, ifnp->if_name);
		conf_vnetflowid(output, ifs, ifnp->if_name);
		conf_parent(output, ifs, ifnp->if_name);
		conf_patch(output, ifs, ifnp->if_name);
		conf_rdomain(output, ifs, ifnp->if_name);
		conf_intrtlabel(output, ifs, ifnp->if_name);
		conf_intgroup(output, ifs, ifnp->if_name);
		conf_carp(output, ifs, ifnp->if_name);
		conf_tunnel(output,  ifs, ifnp->if_name);
		conf_ifmetrics(output,  ifs, if_data, ifnp->if_name);

		ippntd = conf_ifaddr_dhcp(output, ifnp->if_name, ifs, flags);

		if (br) {
			conf_brcfg(output, ifs, ifn_list, ifnp->if_name);
		} else {
			char tmp[24];

			conf_media_status(output, ifs, ifnp->if_name);
			conf_keepalive(output, ifs, ifnp->if_name);
			conf_pfsync(output, ifs, ifnp->if_name);
			conf_trunk(output, ifs, ifnp->if_name);
			conf_pflow(output, ifs, ifnp->if_name);
			conf_pwe3(output, ifs, ifnp->if_name);
			conf_ifxflags(output, ifs, ifnp->if_name);
			if (conf_dhcrelay(ifnp->if_name, tmp, sizeof(tmp))
			    > 0)
				fprintf(output, " dhcrelay %s\n", tmp);
			conf_sppp(output, ifs, ifnp->if_name);
			conf_pppoe(output, ifs, ifnp->if_name);
			conf_wg(output, ifs, ifnp->if_name);
			conf_umb(output, ifs, ifnp->if_name);
		}
		conf_ifflags(output, flags, ifnp->if_name, ippntd,
		    if_data.ifi_type);
	}
	close(ifs);
	if_freenameindex(ifn_list);
}

void conf_lladdr(FILE *output, char *ifname)
{
	StringList *hwdaddr;
	char *lladdr;

	/* We assume lladdr only useful if interface can get_hwdaddr */
	if ((lladdr = get_hwdaddr(ifname)) == NULL)
		return;

	hwdaddr = sl_init();

	if (db_select_flag_x_ctl(hwdaddr, "lladdr", ifname) < 0) {
		printf("%% lladdr database select failed\n");
	}
	if (hwdaddr->sl_cur > 0 && (strcmp(hwdaddr->sl_str[0],
	    lladdr) != 0))
		fprintf(output, " lladdr %s\n", lladdr);

	sl_free(hwdaddr, 1);
}

int conf_ifaddr_dhcp(FILE *output, char *ifname, int ifs, int flags)
{
	int ippntd;

	/* find dhcpleased controlled interfaces */
	if (dhcpleased_controls_interface(ifname, ifs)) {
		fprintf(output, " autoconf4\n");
		/* print all non-autoconf ipv6 addresses */
		conf_ifaddrs(output, ifname, flags, AF_INET6);
		ippntd = 1;
	} else if (is_pppoe(ifname, ifs)) {
		int ipaddrmode = pppoe_get_ipaddrmode(ifname);
		if (ipaddrmode == NSH_PPPOE_IPADDR_IPCP) {
			fprintf(output, " autoconf4\n");
			ippntd = 1;
		} else if (ipaddrmode == NSH_PPPOE_IPADDR_STATIC) {
			fprintf(output, " no autoconf4\n");
			/* print all non-autoconf addresses */
			ippntd = conf_ifaddrs(output, ifname, flags, 0);
		} else
			ippntd = 0;
	} else {
		/* print all non-autoconf addresses */
		ippntd = conf_ifaddrs(output, ifname, flags, 0);
	}

	return ippntd;
}

void conf_vnetid(FILE *output, int ifs, char *ifname)
{
	int64_t vnetid;

	if (((vnetid = get_vnetid(ifs, ifname)) != 0)) {
		if (vnetid < 0)
			fprintf(output, " vnetid any\n");
		else
			fprintf(output, " vnetid %lld\n", vnetid);
	}
}

void conf_vnetflowid(FILE *output, int ifs, char *ifname)
{
	struct ifreq ifr;

	bzero(&ifr, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(ifs, SIOCGVNETFLOWID, &ifr) == -1)
		return;

	if (ifr.ifr_vnetid)
		fprintf(output, " vnetflowid\n");
}

void conf_patch(FILE *output, int ifs, char *ifname)
{
	struct ifreq ifr;
	char ifix_buf[IFNAMSIZ];

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(ifs, SIOCGIFPAIR, &ifr) == 0 && ifr.ifr_index != 0 &&
	    if_indextoname(ifr.ifr_index, ifix_buf) != NULL)
		fprintf(output, " patch %s\n", ifix_buf);
}

void conf_parent(FILE *output, int ifs, char *ifname)
{
	struct if_parent ifp;

	memset(&ifp, 0, sizeof(ifp));
	strlcpy(ifp.ifp_name, ifname, IFNAMSIZ);

	if (ioctl(ifs, SIOCGIFPARENT, (caddr_t)&ifp) == -1) {
		if (errno != EADDRNOTAVAIL && errno != ENOTTY)
			printf("%% SIOCGIFPARENT %s: %s\n", ifname,
			    strerror(errno));
		return;
	}

	fprintf(output, " parent %s\n", ifp.ifp_parent);
}

void conf_ifflags(FILE *output, int flags, char *ifname, int ippntd, u_char ift)
{
	if (flags & IFF_DEBUG)
		fprintf(output, " debug\n");
	if (flags & (IFF_LINK0|IFF_LINK1|IFF_LINK2)) {
		fprintf(output, " link ");
		if(flags & IFF_LINK0)
			fprintf(output, "0 ");
		if(flags & IFF_LINK1)
			fprintf(output, "1 ");
		if(flags & IFF_LINK2)
			fprintf(output, "2");
		fprintf(output, "\n");
	}

	/* wg(4) sets IFF_NOARP by default and this should not be changed. */
	if (ift != IFT_WIREGUARD) {
		if (flags & IFF_NOARP)
			fprintf(output, " no arp\n");
		if (flags & IFF_STATICARP)
			fprintf(output, " staticarp\n");
	}

	if (isprefix("pppoe", ifname)) {		/* XXX */
		fprintf(output, " no shutdown\n");
	} else {
		/*
		 * address X/Y turns the interface up (just like 'no shutdown')
		 * ...but if we never had an ip address set and the interface
		 * is up, we need to save this state explicitly.
		 */
		if (!ippntd && (flags & IFF_UP))
			fprintf(output, " no shutdown\n");
		else if (!(flags & IFF_UP))
			fprintf(output, " shutdown\n");
	}
	fprintf(output, "!\n");
}

int conf_dhcrelay(char *ifname, char *server, int serverlen)
{
	StringList *data;
	int alen;

	data = sl_init();
	if ((alen = db_select_flag_x_data_ctl_rtable(data, "dhcrelay", ifname, 0))
	    > 0) {
		strlcpy(server, data->sl_str[0], serverlen);
		alen = strlen(data->sl_str[0]);
	}
	sl_free(data, 1);

	return(alen);
}

void conf_pflow(FILE *output, int ifs, char *ifname)
{
	char sender[INET6_ADDRSTRLEN];
	char receiver[INET6_ADDRSTRLEN];
	char version[INET6_ADDRSTRLEN];

	if (pflow_status(PFLOW_SENDER, ifs, ifname, sender)) {
		return;
	}
	if (pflow_status(PFLOW_RECEIVER, ifs, ifname, receiver)) {
		return;
	}
	if (pflow_status(PFLOW_VERSION, ifs, ifname, version)) {
		return;
	}
	fprintf(output, " pflow sender %s receiver %s version %s\n",
	    sender, receiver, version);
}

void conf_ifxflags(FILE *output, int ifs, char *ifname)
{
	struct ifreq ifr;

	bzero(&ifr, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(ifs, SIOCGIFXFLAGS, (caddr_t)&ifr) != -1) {
 		/* set mpls mode for eth interfaces */
		if (ifr.ifr_flags & IFXF_MPLS)
			fprintf(output, " mpls\n");
		if (ifr.ifr_flags & IFXF_AUTOCONF6)
			fprintf(output, " autoconf6\n");
		if (ifr.ifr_flags & IFXF_AUTOCONF6TEMP)
			fprintf(output, " temporary\n");
		if (ifr.ifr_flags & IFXF_MONITOR)
			fprintf(output, " monitor\n");
		if (ifr.ifr_flags & IFXF_WOL)
			fprintf(output, " wol\n");
	}
}

void conf_rdomain(FILE *output, int ifs, char *ifname)
{
	int rdomainid;

	rdomainid = get_rdomain(ifs, ifname);
	if (rdomainid > 0)
		fprintf(output, " rdomain %d\n", rdomainid);
}
	
int get_rdomain(int ifs, char *ifname)
{
	struct ifreq ifr;

	bzero(&ifr, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(ifs, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1)
		return ifr.ifr_rdomainid;
	return -1;
}

void conf_keepalive(FILE *output, int ifs, char *ifname)
{
	struct ifkalivereq ikar;

	bzero(&ikar, sizeof(ikar));
	strlcpy(ikar.ikar_name, ifname, IFNAMSIZ);

	if (ioctl(ifs, SIOCGETKALIVE, &ikar) == 0 &&
	    (ikar.ikar_timeo != 0 || ikar.ikar_cnt != 0))
		fprintf(output, " keepalive %d %d\n",
		    ikar.ikar_timeo, ikar.ikar_cnt);
}

void conf_pwe3(FILE *output, int ifs, char *ifname)
{
	int error, nei = 0, fat = 0, cw = 0;
	struct shim_hdr shim;
	struct ifreq ifr;

	struct if_laddrreq req;
	char hbuf[NI_MAXHOST];
	struct sockaddr_mpls *smpls;

	bzero(&ifr, sizeof(ifr));
	bzero(&shim, sizeof(shim));
	bzero(&req, sizeof(req));

	ifr.ifr_data = (caddr_t)&shim;
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	strlcpy(req.iflr_name, ifname, sizeof(req.iflr_name));

	if (ioctl(ifs, SIOCGETLABEL, (caddr_t) &ifr) >= 0) {
		fprintf(output, " mplslabel %u\n", shim.shim_label);
	}

	if (ioctl(ifs, SIOCGPWE3NEIGHBOR, (caddr_t)&req) >= 0) {
		if (req.dstaddr.ss_family == AF_MPLS) {
			smpls = (struct sockaddr_mpls *)&req.dstaddr;
			error = getnameinfo((struct sockaddr *)&req.addr,
			    sizeof(req.addr), hbuf, sizeof(hbuf), NULL, 0,
			    NI_NUMERICHOST);
			if (error != 0) {
				printf("%% conf_pwe3: getnameinfo: %s\n",
				    gai_strerror(error));
			} else {
				nei = 1;
			}
		}
				
	}

	if (ioctl(ifs,  SIOCGPWE3FAT, (caddr_t)&ifr) >= 0) {
		if (ifr.ifr_pwe3)
			fat = 1;
	}

	if (ioctl(ifs,  SIOCGPWE3CTRLWORD, (caddr_t)&ifr) >= 0) {
		if (ifr.ifr_pwe3)
			cw = 1;
	}

	if (nei || cw || fat)
		fprintf(output, " pwe");
	if (nei)
		fprintf(output, " neighbor %u %s", smpls->smpls_label, hbuf);
	if (cw)
		fprintf(output, " cw");
	if (fat)
		fprintf(output, " fat");
	if (nei || cw || fat)
		fprintf(output, "\n");

}

void conf_tunnel(FILE *output, int ifs, char *ifname)
{
	int dstport, physrtable;
	char tmpa[IPSIZ], tmpb[IPSIZ];

	if ((dstport=
	    phys_status(ifs, ifname, tmpa, tmpb, IPSIZ, IPSIZ)) >= 0) {
		int physttl;

		fprintf(output, " tunnel %s %s", tmpa, tmpb);
		if (dstport > 0)
			fprintf(output, ":%i", dstport);
		if ((physttl = get_physttl(ifs, ifname)) > 0)
			fprintf(output, " ttl %i", physttl);
		if ((get_physecn(ifs, ifname)) > 0)
			fprintf(output, " ecn");
		if ((get_physdf(ifs, ifname)) > 0)
			fprintf(output, " df");	
		fprintf(output, "\n");
	}
	/* non-tunnel interfaces can have a tunneldomain */
	if ((physrtable = get_physrtable(ifs, ifname)) != 0)
		fprintf(output, " tunneldomain %i\n", physrtable);
}

void conf_ifmetrics(FILE *output, int ifs, struct if_data if_data,
    char *ifname)
{
	char tmp[TMPSIZ];
	struct ifreq ifr;

	/*
	 * print interface mtu, metric
	 */
	if (if_data.ifi_mtu != default_mtu(ifname) &&
	   default_mtu(ifname) != MTU_IGNORE && if_data.ifi_mtu != 0)
		fprintf(output, " mtu %u\n", if_data.ifi_mtu);
	if (if_data.ifi_metric)
		fprintf(output, " metric %u\n", if_data.ifi_metric);

	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(ifs, SIOCGIFPRIORITY, (caddr_t)&ifr) == 0 &&
	    ifr.ifr_metric)
		fprintf(output, " priority %u\n", ifr.ifr_metric);
	if (ioctl(ifs, SIOCGIFLLPRIO, (caddr_t)&ifr) == 0 &&
	    ifr.ifr_llprio != default_llpriority(ifr.ifr_name))
		fprintf(output, " llpriority %u\n", ifr.ifr_llprio);
	if (ioctl(ifs, SIOCGTXHPRIO, (caddr_t)&ifr) == 0 &&
	    ifr.ifr_hdrprio != default_txprio(ifr.ifr_name)) {
		switch(ifr.ifr_hdrprio) {
			case IF_HDRPRIO_PACKET:
				fprintf(output, " txprio packet\n");
				break;
			case IF_HDRPRIO_PAYLOAD:
				fprintf(output, " txprio payload\n");
				break;
			default:
				fprintf(output, " txprio %u\n",
				    ifr.ifr_hdrprio);
		}
	}
	if (ioctl(ifs, SIOCGRXHPRIO, (caddr_t)&ifr) == 0 &&
	    ifr.ifr_hdrprio != default_rxprio(ifr.ifr_name)) {
		switch(ifr.ifr_hdrprio) {
			case IF_HDRPRIO_PACKET:
				fprintf(output, " rxprio packet\n");
				break;
			case IF_HDRPRIO_PAYLOAD:
				fprintf(output, " rxprio payload\n");
				break;
			case IF_HDRPRIO_OUTER:
				fprintf(output, " rxprio outer\n");
				break;
			default:
				fprintf(output, " rxprio %u\n",
				    ifr.ifr_hdrprio);
		}
	}

	if (get_nwinfo(ifname, tmp, TMPSIZ, NWID) != 0) {
		fprintf(output, " nwid %s\n", tmp);
		if (get_nwinfo(ifname, tmp, TMPSIZ, NWKEY) != 0)
			fprintf(output, " nwkey %s\n", tmp);
		if (get_nwinfo(ifname, tmp, TMPSIZ, TXPOWER) != 0)
			fprintf(output, " txpower %s\n", tmp);
		if (get_nwinfo(ifname, tmp, TMPSIZ, POWERSAVE) != 0)
			fprintf(output, " powersave %s\n", tmp);
	}
}

void conf_brcfg(FILE *output, int ifs, struct if_nameindex *ifn_list,
    char *ifname)
{
	struct if_nameindex *br_ifnp;

	char tmp_str[TMPSIZ];
	long l_tmp;

	if ((l_tmp = bridge_cfg(ifs, ifname, PRIORITY))
	    != -1 && l_tmp != DEFAULT_PRIORITY)
		fprintf(output, " priority %ld\n", l_tmp);
	if ((l_tmp = bridge_cfg(ifs, ifname, HELLOTIME))
	    != -1 && l_tmp != DEFAULT_HELLOTIME)
		fprintf(output, " hellotime %ld\n", l_tmp);
	if ((l_tmp = bridge_cfg(ifs, ifname, FWDDELAY))
	    != -1 && l_tmp != DEFAULT_FWDDELAY)
		fprintf(output, " fwddelay %ld\n", l_tmp);
	if ((l_tmp = bridge_cfg(ifs, ifname, MAXAGE))
	    != -1 && l_tmp != DEFAULT_MAXAGE)
		fprintf(output, " maxage %ld\n", l_tmp);
	if ((l_tmp = bridge_cfg(ifs, ifname, MAXADDR))
	    != -1 && l_tmp != DEFAULT_MAXADDR)
		fprintf(output, " maxaddr %ld\n", l_tmp);
	if ((l_tmp = bridge_cfg(ifs, ifname, TIMEOUT))
	    != -1 && l_tmp != DEFAULT_TIMEOUT)
		fprintf(output, " timeout %ld\n", l_tmp);

	if (bridge_list(ifs, ifname, NULL, tmp_str, TMPSIZ, MEMBER))
		fprintf(output, " member %s\n", tmp_str);
	if (bridge_list(ifs, ifname, NULL, tmp_str, TMPSIZ, STP))
		fprintf(output, " stp %s\n", tmp_str);
	if (bridge_list(ifs, ifname, NULL, tmp_str, TMPSIZ, SPAN))
		fprintf(output, " span %s\n", tmp_str);
	if (bridge_list(ifs, ifname, NULL, tmp_str, TMPSIZ, NOLEARNING))
		fprintf(output, " no learning %s\n", tmp_str);
	if (bridge_list(ifs, ifname, NULL, tmp_str, TMPSIZ, NODISCOVER))
		fprintf(output, " no discover %s\n", tmp_str);
	if (bridge_list(ifs, ifname, NULL, tmp_str, TMPSIZ, BLOCKNONIP))
		fprintf(output, " blocknonip %s\n", tmp_str);
	if (bridge_list(ifs, ifname, " ", tmp_str, TMPSIZ, CONF_IFPRIORITY))
		fprintf(output, "%s", tmp_str);
	if (bridge_list(ifs, ifname, " ", tmp_str, TMPSIZ, CONF_IFCOST))
		fprintf(output, "%s", tmp_str);
	if (bridge_list(ifs, ifname, " ", tmp_str, TMPSIZ, PROTECTED))
		fprintf(output, "%s", tmp_str);
	bridge_confaddrs(ifs, ifname, " static ", output);

	for (br_ifnp = ifn_list; br_ifnp->if_name != NULL; br_ifnp++)
		/* try all interface names for member rules */
		bridge_rules(ifs, ifname, br_ifnp->if_name, " rule ",
		    output);
}

int
ipv6ll_db_compare(struct sockaddr_in6 *sin6, struct sockaddr_in6 *sin6mask,
    char *ifname)
{
	int count, scope;
	StringList *data;
	struct in6_addr store;

	if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
	    IN6_IS_ADDR_MC_LINKLOCAL(&sin6->sin6_addr) ||
	    IN6_IS_ADDR_MC_INTFACELOCAL(&sin6->sin6_addr)) {
		/*
		 * Save any scope or embedded scope.
		 * The kernel does not set sin6_scope_id.
		 * But if it ever does, we're already prepared.
		 */
		store.s6_addr[0] = sin6->sin6_addr.s6_addr[2];
		store.s6_addr[1] = sin6->sin6_addr.s6_addr[3];
		sin6->sin6_addr.s6_addr[2] = sin6->sin6_addr.s6_addr[3] = 0;
		scope = sin6->sin6_scope_id;
		sin6->sin6_scope_id = 0;
		
		data = sl_init();
		db_select_flag_x_ctl_data(data, "ipv6linklocal", ifname,
		    netname6(sin6, sin6mask));
		count = data->sl_cur;
		sl_free(data, 1);

		/* restore any scope or embedded scope */
		sin6->sin6_addr.s6_addr[2] = store.s6_addr[0];
		sin6->sin6_addr.s6_addr[3] = store.s6_addr[1];
		sin6->sin6_scope_id = scope;
		return(count);
	}
	return 1;
}


int conf_ifaddrs(FILE *output, char *ifname, int flags, int af)
{
	struct ifaddrs *ifa, *ifap;
	struct sockaddr_in *sin, *sinmask, *sindest;
	struct sockaddr_in6 *sin6, *sin6mask, *sin6dest;
	struct in6_ifreq ifr6;
	int ippntd = 0;

	if (getifaddrs(&ifap) != 0) {
		printf("%% conf: getifaddrs failed: %s\n",
		strerror(errno));
		return(-1);
	}

	/*
	 * Cycle through getifaddrs for interfaces with our
	 * desired name that sport af or (AF_INET | AF_INET6).
	 * Print the IP and related information.
	 */
	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL)
			continue;
		if (strncmp(ifname, ifa->ifa_name, IFNAMSIZ))
			continue;

		switch (ifa->ifa_addr->sa_family) {
		int s;
		case AF_INET:
			if (af != AF_INET && af != 0)
				continue;
			sin = (struct sockaddr_in *)ifa->ifa_addr;
			if (sin->sin_addr.s_addr == INADDR_ANY)
				continue;
			sinmask = (struct sockaddr_in *)ifa->ifa_netmask;
			if (flags & IFF_POINTOPOINT) {
				sindest = (struct sockaddr_in *)ifa->ifa_dstaddr;
				fprintf(output, " inet %s",
				    routename4(sin->sin_addr.s_addr));
				if (ntohl(sindest->sin_addr.s_addr) !=
				    INADDR_ANY)
					fprintf(output, " %s",
					    inet_ntoa(sindest->sin_addr));
			} else if (flags & IFF_BROADCAST) {
				sindest = (struct sockaddr_in *)ifa->ifa_broadaddr;
				fprintf(output, " inet %s",
				    netname4(sin->sin_addr.s_addr, sinmask));
				/*
				 * don't save a broadcast address that would be
				 * automatically calculated by the kernel anyways
				 */
				if (ntohl(sindest->sin_addr.s_addr) !=
				    in4_brdaddr(sin->sin_addr.s_addr,
				    sinmask->sin_addr.s_addr) &&
				    ntohl(sindest->sin_addr.s_addr) !=
				    INADDR_ANY)
					fprintf(output, " %s",
					    inet_ntoa(sindest->sin_addr));
			} else {
				fprintf(output, " inet %s",
				    netname4(sin->sin_addr.s_addr, sinmask));
			}
			ippntd = 1;
			break;
		case AF_INET6:
			if (af != AF_INET6 && af != 0)
				continue;
			sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
			sin6mask = (struct sockaddr_in6 *)ifa->ifa_netmask;

			if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
				continue;
			if (!ipv6ll_db_compare(sin6, sin6mask, ifname))
				continue;
			in6_fillscopeid(sin6);

			/* get address flags */
			memset(&ifr6, 0, sizeof(ifr6));
			strlcpy(ifr6.ifr_name, ifname, sizeof(ifr6.ifr_name));
			memcpy(&ifr6.ifr_addr, &sin6, sizeof(ifr6.ifr_addr));
			s = socket(PF_INET6, SOCK_DGRAM, 0);
			if (s < 0)
				printf("%% conf_ifaddrs: socket: %s\n",
				    strerror(errno));
			if (ioctl(s, SIOCGIFAFLAG_IN6, (caddr_t)&ifr6) < 0) {
				if (errno != EADDRNOTAVAIL)
					printf("%% conf_ifaddrs: " \
					    "SIOCGIFAFLAG_IN6: %s\n",
					    strerror(errno));
			} else {
				/* skip autoconf addresses */
				if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_AUTOCONF)
					continue;
			}

			if (flags & IFF_POINTOPOINT) {
				fprintf(output, " inet %s", routename6(sin6));
				sin6dest = (struct sockaddr_in6 *)ifa->ifa_dstaddr;
				in6_fillscopeid(sin6dest);
				fprintf(output, " %s", routename6(sin6dest));
			} else {
				fprintf(output, " inet %s",
				    netname6(sin6, sin6mask));
			}
			ippntd = 1;
			break;
		default:
			continue;
		}
		fprintf(output, "\n");
	}
	freeifaddrs(ifap);

	return ippntd;
}

u_long
default_mtu(char *ifname)
{
	u_int i;

	for (i = 0; i < nitems(defmtus); i++)
		if (isprefix(defmtus[i].name, ifname))  
			return(defmtus[i].mtu);

	return(DEFAULT_MTU); /* default mtu */
}

int
default_txprio(char *ifname)
{
	u_int i;

	for (i = 0; i < nitems(defprios); i++)
		if (isprefix(defprios[i].name, ifname))
			return(defprios[i].txprio);

	return 0;	/* default txprio */
}

int
default_rxprio(char *ifname)
{
	u_int i;

	for (i = 0; i < nitems(defprios); i++)
		if (isprefix(defprios[i].name, ifname))
			return(defprios[i].rxprio);

	return 0;	/* default rxprio */
}

int
default_llpriority(char *ifname)
{
	u_int i;

	for (i = 0; i < nitems(defllpriority); i++)
		if (isprefix(defllpriority[i].name, ifname))
			return(defllpriority[i].llpriority);

	return IFQ_DEFPRIO;	/* default llpriority */
}

/*
 * Show IPv4/6 or ARP entries from the routing table
 */
int
conf_routes(FILE *output, char *delim, int af, int flags, int tableid)
{
	char *next;
	struct rt_msghdr *rtm;
	struct rtdump *rtdump;
	struct sockaddr *sa;

	if (tableid < 0 || tableid > RT_TABLEID_MAX) {
		printf("%% conf_routes: tableid %d out of range\n", tableid);
		return(1);
	}
	rtdump = getrtdump(0, flags, tableid);
	if (rtdump == NULL)
		return(1);

	/* walk through routing table */
	for (next = rtdump->buf; next < rtdump->lim; next += rtm->rtm_msglen) {
		rtm = (struct rt_msghdr *)next;
		if (rtm->rtm_version != RTM_VERSION)
			continue;
		sa = (struct sockaddr *)(next + rtm->rtm_hdrlen);
		if (af != AF_UNSPEC && sa->sa_family != af)
			continue;
		if (!rtm->rtm_errno) {
			if (rtm->rtm_addrs)
				conf_print_rtm(output, rtm, delim, af);
		} else if (verbose)
			printf("%% conf_routes: rtm: %s (errno %d)\n",
			    strerror(rtm->rtm_errno), rtm->rtm_errno);
	}
	freertdump(rtdump);
	return(1);
}

void
conf_intgroup(FILE *output, int ifs, char *ifname)
{
	/* ripped straight from ifconfig.c */
	int cnt;
	u_int len;
	struct ifgroupreq	ifgr;
	struct ifg_req		*ifg;

	bzero(&ifgr, sizeof(ifgr));
	strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ);

	if (ioctl(ifs, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
		if (errno != ENOTTY)
			printf("%% conf_intgroup: SIOCGIFGROUP/1: %s\n",
			    strerror(errno));
		return;
	}

	len = ifgr.ifgr_len;
	ifgr.ifgr_groups =
	    (struct ifg_req *)calloc(len / sizeof(struct ifg_req),
	    sizeof(struct ifg_req));
	if (ifgr.ifgr_groups == NULL) {
		printf("%% conf_intgroup: calloc: %s\n", strerror(errno));
		return;
	}
	if (ioctl(ifs, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
		printf("%% conf_intgroup: SIOCGIFGROUP/2: %s\n",
		    strerror(errno));
		free(ifgr.ifgr_groups);
		return;
	}

	ifg = ifgr.ifgr_groups;
	for (cnt = 0; ifg && len >= sizeof(struct ifg_req); ifg++) {
		len -= sizeof(struct ifg_req);
		if (strcmp(ifg->ifgrq_group, "all")) {
			if (cnt == 0)
				fprintf(output, " group");
			cnt++;
			fprintf(output, " %s", ifg->ifgrq_group);
		}
	}
	if (cnt)
		fprintf(output, "\n");
	free(ifgr.ifgr_groups);
}

void
conf_intrtlabel(FILE *output, int ifs, char *ifname)
{
	struct ifreq ifr;
	char ifrtlabelbuf[RTLABEL_LEN];

	bzero(&ifr, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	ifr.ifr_data = (caddr_t)&ifrtlabelbuf;

	if (ioctl(ifs, SIOCGIFRTLABEL, (caddr_t)&ifr) == -1) {
		if (errno != ENOENT)
			printf("%% conf_intrtlabel: SIOCGIFRTLABEL: %s\n",
			    strerror(errno));
		return;
	}

	fprintf(output, " rtlabel %s\n", ifr.ifr_data);
}

int
isdefaultroute(struct sockaddr *sa, struct sockaddr *samask)
{
	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;

	switch (sa->sa_family) {
	case AF_INET:
		/* XXX check for zero mask */
		return
		    (((struct sockaddr_in *)sa)->sin_addr.s_addr) == INADDR_ANY;
		break;
	case AF_INET6:
		/* XXX check for zero mask */
		return (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr));
		break;
	default:
		break;
	}
	return 0;
}

static const struct {
	char *name;
	long flag;
} rtflags[] = {
	{ "blackhole",	RTF_BLACKHOLE },
	{ "cloning",	RTF_CLONING },
	{ "iface",	-RTF_GATEWAY },
	{ "llinfo",	RTF_LLINFO },
	{ "nompath",	-RTF_MPATH },
	{ "nostatic",	-RTF_STATIC },
	{ "proto1",	RTF_PROTO1 },
	{ "proto2",	RTF_PROTO2 },
	{ "reject",	RTF_REJECT }
};

void
conf_rtflags(char *txt, int flags, struct rt_msghdr *rtm)
{
	int i;

	for (i = 0; i < nitems(rtflags); i++)
		if (rtflags[i].flag < 0) {
			if (!(flags & -rtflags[i].flag)) {
				strlcat(txt, " ", TMPSIZ);
				strlcat(txt, rtflags[i].name, TMPSIZ);
			}
		} else if ((flags & rtflags[i].flag)) {
				strlcat(txt, " ", TMPSIZ);
				strlcat(txt, rtflags[i].name, TMPSIZ);
		}

	if (rtm->rtm_rmx.rmx_mtu && rtm->rtm_rmx.rmx_mtu != ROUTEMTU) {
		char sn1[16];
		snprintf(sn1, sizeof(sn1), " mtu %d", rtm->rtm_rmx.rmx_mtu);
		strlcat(txt, sn1, TMPSIZ);
	}
	if (rtm->rtm_rmx.rmx_expire) {
		char sn1[16];
		snprintf(sn1, sizeof(sn1), " expire %lld",
		    rtm->rtm_rmx.rmx_expire);
		strlcat(txt, sn1, TMPSIZ);
	}
}

void
conf_print_rtm(FILE *output, struct rt_msghdr *rtm, char *delim, int af)
{
	int i;
	char *cp, flags[TMPSIZ], ifname[IFNAMSIZ];
	struct sockaddr *dst = NULL, *gate = NULL, *mask = NULL;
	struct sockaddr_rtlabel *sa_rl = NULL;
	struct sockaddr *sa;
	struct sockaddr_in sin;
	struct sockaddr_dl *ifp = NULL;

	memset(ifname, 0, sizeof(ifname));

	sin.sin_addr.s_addr = htonl(INADDR_BROADCAST);
	bzero(&flags, TMPSIZ);

	cp = ((char *)rtm + rtm->rtm_hdrlen);
	for (i = 1; i; i <<= 1)
		if (i & rtm->rtm_addrs) {
			sa = (struct sockaddr *)cp;

			switch (i) {
			case RTA_DST:
				/* allow arp to print when af==AF_LINK */
				if (sa->sa_family == af) {
					conf_rtflags(flags, rtm->rtm_flags,
					    rtm);
					dst = sa;
				}
				break;
			case RTA_GATEWAY:
				if (sa->sa_family == af)
					gate = sa;
				break;
			case RTA_NETMASK:
				/* netmasks will not have a valid sa_family */
				mask = sa;
				break;
			case RTA_IFP:
				if (sa->sa_family == AF_LINK &&
				   ((struct sockaddr_dl *)sa)->sdl_nlen) {
					ifp = (struct sockaddr_dl *)sa;
					if (ifp->sdl_nlen < sizeof(ifname)) {
						memcpy(ifname, ifp->sdl_data,
						    ifp->sdl_nlen);
					}
				}
				break;
			case RTA_LABEL:
				sa_rl = (struct sockaddr_rtlabel *)sa;
				break;
			}
			ADVANCE(cp, sa);
		}
	if (dst && gate && mask && (af == AF_INET || af == AF_INET6)) {
		/* Special case for dynamic default route via pppoe(4) */
		if (af == AF_INET && isdefaultroute(dst, mask) &&
		    isprefix("pppoe", ifname)) {
			pppoe_conf_default_route(output, ifname,
			    delim, netname(dst, mask),
			    routename(gate), flags);
		}
		/*
		 * Suppress printing IPv4 route if it's the default
		 * route and dhcp (dhcpleased) is enabled.
		 */
		else if (!(af == AF_INET && isdefaultroute(dst, mask)
		    && dhcpleased_has_defaultroute(sa_rl))) {
			fprintf(output, "%s%s ", delim, netname(dst, mask));
			fprintf(output, "%s%s\n", routename(gate), flags);
		}
	} else if (dst && gate && (af == AF_LINK)) {
		/* print arp */
		fprintf(output, "%s%s ", delim, routename(dst));
		fprintf(output, "%s\n", routename(gate));
	}
	explicit_bzero(flags, TMPSIZ);
}