#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <net/route.h>
#include <net/if_dl.h>
#include <arpa/inet.h>
#include <sys/sysctl.h>
#include <errno.h>
#include <assert.h>

static void do_netroutes(void);
static char *rflags_tostr(int);
static char *addrs_tostr(int);
static char *cidr_saddrstr(const struct sockaddr *);

 
#define HAVE_STRUCT_SOCKADDR_LEN 1

/*
 */
#define ROUNDUP(a) \
	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))

#ifdef HAVE_STRUCT_SOCKADDR_LEN
#define NEXTSA(s) \
	((struct sockaddr *)((u_char *)(s) + ROUNDUP((s)->sa_len)))
#else
#define NEXTSA(s) \
	((struct sockaddr *)((u_char *)(s) + ROUNDUP(sizeof(*(s)))))
#endif

int main(int argc, char **argv) {

	do_netroutes();

	exit(0);
}

/*
 * based upon /usr/src/sbin/route.c from FreeBSD 4.7
 * if they do it this way, then i should too
 */

static void do_netroutes(void) {
	union {
		void *p;
		char *step;
		struct rt_msghdr *rtm;
	} r_u;
	int mib[6];
	size_t alen=0;
	void *end=NULL, *start=NULL;
	struct sockaddr *ss=NULL;

	mib[0]=CTL_NET;
	mib[1]=PF_ROUTE;
	mib[2]=0;		/* protocol */
	mib[3]=0;		/* wildcard address family? */
	mib[4]=NET_RT_DUMP;	/* */
	mib[5]=0;		/* flags */

	if (sysctl(mib, 6, NULL, &alen, NULL, 0) < 0) {
		fprintf(stderr, "sysctl estimate memory size: %s\n", strerror(errno));
		return;
	}

	assert(alen > 0);

	start=malloc(alen); assert(start != NULL);
	end=start + alen;
	r_u.p=start;

	if (sysctl(mib, 6, r_u.p, &alen, NULL, 0) < 0) {
		fprintf(stderr, "sysctl giveittome! fails: %s\n", strerror(errno));
		free(start);
		return;
	}

	for (; r_u.p < end && r_u.rtm->rtm_msglen > 0; r_u.step += r_u.rtm->rtm_msglen) {
		if (r_u.rtm->rtm_version != RTM_VERSION) {
			continue;
		}

		switch (r_u.rtm->rtm_type) {
			case RTM_GET:
				printf("route length %hu version %hu type %hu index %hu flags `%s' "
					"addrs `%s' pid %d seq %08x errno %d use %u inits %ul\n",
					r_u.rtm->rtm_msglen, r_u.rtm->rtm_version, r_u.rtm->rtm_type,
					r_u.rtm->rtm_index,  rflags_tostr(r_u.rtm->rtm_flags),
					addrs_tostr(r_u.rtm->rtm_addrs),  r_u.rtm->rtm_pid,
					r_u.rtm->rtm_seq,
					r_u.rtm->rtm_errno,  r_u.rtm->rtm_use,     r_u.rtm->rtm_inits
				);
				ss=r_u.p + sizeof(*r_u.rtm);
				for (	ss=r_u.p + sizeof(*r_u.rtm);
					(char *)ss < r_u.step + (r_u.rtm->rtm_msglen - sizeof(r_u.rtm));
					ss=NEXTSA(ss)
					) {
						printf("addr %s\n", cidr_saddrstr(ss));
				}
				break;

			default:
				fprintf(stderr, "hey there little guy! what is this? type %d\n", r_u.rtm->rtm_type);
				break;
		}

	}

	free(start);
	return;
}

#define S_APPEND(x) \
	if (out_off) { \
		sret=snprintf(out + out_off, sizeof(out) - (out_off + 1), ", %s", (x)); \
		out_off += sret; \
	} \
	else { \
		sret=snprintf(out, sizeof(out) -1, "%s", (x)); \
		out_off += sret; \
	}

static char *addrs_tostr(int addrs) {
	static char out[1024];
	unsigned int out_off=0;
	int sret=0;

#define ADDRS_ADD(x, y) \
	if (addrs & (x)) { \
		S_APPEND((y)); \
		addrs &= ~(x); \
	}

	ADDRS_ADD(RTA_DST,	"dst addr present");
	ADDRS_ADD(RTA_GATEWAY,	"gw addr present");
	ADDRS_ADD(RTA_NETMASK,	"netmask addr present");
	ADDRS_ADD(RTA_GENMASK,	"clone mask present");
	ADDRS_ADD(RTA_IFP,	"int name addr present");
	ADDRS_ADD(RTA_IFA,	"int addr present");
	ADDRS_ADD(RTA_AUTHOR,	"addr for redirect author present");
	ADDRS_ADD(RTA_BRD,	"broadcast/peer addr present");
	if (addrs) {
		S_APPEND("some other nonsense i dont know about");
	}

	return out;
}

static char *rflags_tostr(int flags) {
	static char out[1024];
	unsigned int out_off=0;
	int sret=0;

#define RFLAGS_ADD(x, y) \
	if (flags & (x)) { \
		S_APPEND((y)); \
		flags &= ~(x); \
	}

	RFLAGS_ADD(RTF_UP,		"usable");
	RFLAGS_ADD(RTF_GATEWAY,		"gateway");
	RFLAGS_ADD(RTF_HOST,		"host");
	RFLAGS_ADD(RTF_REJECT,		"unrechable");
	RFLAGS_ADD(RTF_DYNAMIC,		"dynamicly created");
	RFLAGS_ADD(RTF_MODIFIED,	"dynamicly modified");
	RFLAGS_ADD(RTF_DONE,		"message confirmed");
	RFLAGS_ADD(RTF_CLONING,		"generate new on use");
	RFLAGS_ADD(RTF_XRESOLVE,	"daemon is made of wood");
	RFLAGS_ADD(RTF_LLINFO,		"generated by link");
	RFLAGS_ADD(RTF_STATIC,		"static");
	RFLAGS_ADD(RTF_BLACKHOLE,	"blackhole");
	RFLAGS_ADD(RTF_PROTO1,		"protocol specific 1");
	RFLAGS_ADD(RTF_PROTO2,		"protocol specific 2");
	RFLAGS_ADD(RTF_PROTO3,		"protocol specific 3");
#ifdef RTF_PRCLONING
	RFLAGS_ADD(RTF_PRCLONING,	"protocol needs cloning");
#endif
#ifdef RTF_WASCLONED
	RFLAGS_ADD(RTF_WASCLONED,	"route is a clone!!!!!111on1o1!O1noeone");
#endif
#ifdef RTF_PINNED
	RFLAGS_ADD(RTF_PINNED,		"timewarp!");
#endif
#ifdef RTF_LOCAL
	RFLAGS_ADD(RTF_LOCAL,		"local");
#endif
#ifdef RTF_BROADCAST
	RFLAGS_ADD(RTF_BROADCAST,	"broadcast");
#endif
#ifdef RTF_MULTICAST
	RFLAGS_ADD(RTF_MULTICAST,	"multicast");
#endif
	if (flags) {
		S_APPEND("some other nonsense i dont know about");
	}

	return out;
}

#ifdef HAVE_STRUCT_SOCKADDR_LEN
struct f_s {
	uint8_t len;
	uint8_t family;
};
#else
struct f_s {
	uint16_t family;
};
#endif

static char *cidr_saddrstr(const struct sockaddr *in) {
        union {
                struct f_s *fs;
                const struct sockaddr *s;
                const struct sockaddr_in *sin;
                const struct sockaddr_in6 *sin6;
#ifdef AF_LINK
		const struct sockaddr_dl *dl;
#endif
        } s_u;
        static char nbuf[256], *ret=NULL;
        const void *p=NULL;

        if (in == NULL) {
                return NULL;
        }

        s_u.s=in;

        switch (s_u.fs->family) {
                case AF_INET:
                        p=&s_u.sin->sin_addr;
                        break;

                case AF_INET6:
                        p=&s_u.sin6->sin6_addr;
                        break;

#ifdef AF_LINK
		case AF_LINK:
			return link_ntoa(s_u.dl);
			break;
#endif

                default:
                        snprintf(nbuf, sizeof(nbuf) -1, "unknown address family `%d'", s_u.fs->family);
                        return nbuf;
        }

        ret=inet_ntop(s_u.fs->family, p, nbuf, sizeof(nbuf) - 1); /* GAH */
        if (ret == NULL) {
                fprintf(stderr, "inet_ntop fails: %s", strerror(errno));
		return NULL;
        }

        return ret;
}
