/**
 * @file genalg.c  Detecting Generic ALGs
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <re_types.h>
#include <re_mbuf.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_sa.h>
#include <re_list.h>
#include <re_stun.h>
#include <re_natbd.h>


#define DEBUG_MODULE "natbd_genalg"
#define DEBUG_LEVEL 7
#include <re_dbg.h>


/*
  Detecting Generic ALGs

   A number of NAT boxes are now being deployed into the market which
   try to provide "generic" ALG functionality.  These generic ALGs hunt
   for IP addresses, either in text or binary form within a packet, and
   rewrite them if they match a binding.  This behavior can be detected
   because the STUN server returns both the MAPPED-ADDRESS and XOR-
   MAPPED-ADDRESS in the same response.  If the result in the two does
   not match, there a NAT with a generic ALG in the path.
 */


/** Defines a NAT Generic ALG detection session */
struct nat_genalg {
	struct stun *stun;      /**< STUN Client               */
	struct sa srv;          /**< Server address and port   */
	int proto;              /**< IP protocol               */
	nat_genalg_h *h;        /**< Result handler            */
	void *arg;              /**< Handler argument          */
};


static void stun_response_handler(int err, uint16_t scode, const char *reason,
				  const struct stun_msg *msg, void *arg)
{
	struct stun_attr *xmap, *map;
	struct nat_genalg *ng = arg;
	int status = 0;
	(void)reason;

	if (err) {
		ng->h(err, 0, NULL, -1, NULL, ng->arg);
		return;
	}

	switch (scode) {

	case 0:
		map  = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
		xmap = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
		if (!map || !xmap) {
			ng->h(EINVAL, scode, reason, -1, NULL, ng->arg);
			break;
		}

		status = sa_cmp(&map->v.sa, &xmap->v.sa, SA_ALL) ? -1 : 1;

		ng->h(0, scode, reason, status, &xmap->v.sa, ng->arg);
		break;

	default:
		ng->h(0, scode, reason, -1, NULL, ng->arg);
		break;
	}
}


static void genalg_destructor(void *data)
{
	struct nat_genalg *ng = data;

	mem_deref(ng->stun);
}


/**
 * Allocate a new NAT Generic ALG detection session
 *
 * @param ngp       Pointer to allocated NAT Generic ALG object
 * @param srv       STUN Server IP address and port
 * @param proto     Transport protocol
 * @param conf      STUN configuration (Optional)
 * @param gh        Generic ALG handler
 * @param arg       Handler argument
 *
 * @return 0 if success, errorcode if failure
 */
int nat_genalg_alloc(struct nat_genalg **ngp, const struct sa *srv, int proto,
		     const struct stun_conf *conf,
		     nat_genalg_h *gh, void *arg)
{
	struct nat_genalg *ng;
	int err;

	if (!ngp || !srv || !proto || !gh)
		return EINVAL;

	ng = mem_zalloc(sizeof(*ng), genalg_destructor);
	if (!ng)
		return ENOMEM;

	err = stun_alloc(&ng->stun, conf, NULL, NULL);
	if (err)
		goto out;

	sa_cpy(&ng->srv, srv);
	ng->proto = proto;
	ng->h     = gh;
	ng->arg   = arg;

 out:
	if (err)
		mem_deref(ng);
	else
		*ngp = ng;

	return err;
}


/**
 * Start the NAT Generic ALG detection
 *
 * @param ng NAT Generic ALG object
 *
 * @return 0 if success, errorcode if failure
 */
int nat_genalg_start(struct nat_genalg *ng)
{
	int err;

	if (!ng)
		return EINVAL;

	err = stun_request(NULL, ng->stun, ng->proto, NULL, &ng->srv, 0,
			   STUN_METHOD_BINDING, NULL, 0, false,
			   stun_response_handler, ng, 1,
			   STUN_ATTR_SOFTWARE, stun_software);
	if (err) {
		DEBUG_WARNING("start: stunc_request_send(): (%m)\n", err);
	}

	return err;
}
