/* $Id: pcpserver.c,v 1.51 2019/05/21 08:39:44 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
 * MiniUPnP project
 * Website : http://miniupnp.free.fr/
 * Author : Peter Tatrai

Copyright (c) 2013 by Cisco Systems, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * 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.
    * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
*/

/* Current assumptions:
   - IPv4 is always NATted (internal -> external)
   - IPv6 is always firewalled (this may need some work, NAT6* do exist)

   - we make the judgement based on (in order, picking first one available):
     - third party address
     - internal client address

   TODO : handle NAT46, NAT64, NPT66. In addition, beyond FW/NAT
   choice, should also add for proxy (=as much as possible transparent
   pass-through to one or more servers).

   TODO: IPv6 permission handling (for the time being, we just assume
   anyone on IPv6 is a good guy, but fixing that would include
   upnppermissions rewrite to be AF neutral).
*/

#include "config.h"

#ifdef ENABLE_PCP

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <signal.h>
#include <stdio.h>
#include <ctype.h>
#include <syslog.h>

#include "pcpserver.h"
#include "natpmp.h"
#include "macros.h"
#include "upnpglobalvars.h"
#include "pcplearndscp.h"
#include "upnpredirect.h"
#include "commonrdr.h"
#include "getifaddr.h"
#include "asyncsendto.h"
#include "upnputils.h"
#include "portinuse.h"
#include "pcp_msg_struct.h"
#ifdef ENABLE_UPNPPINHOLE
#include "upnppinhole.h"
#endif /* ENABLE_UPNPPINHOLE */


#ifdef PCP_PEER
/* TODO make this platform independent */
#ifdef USE_NETFILTER
#include "netfilter/iptcrdr.h"
#else
#error "PCP Peer is only supported with NETFILTER"
#endif /* USE_NETFILTER */
#endif /* PCP_PEER */

/* server specific information */
struct pcp_server_info {
	uint8_t server_version;
};

/* default server settings, highest version supported is the default */
static const struct pcp_server_info this_server_info = {2};

/* structure holding information from PCP msg*/
/* all variables are in host byte order except IP addresses */
typedef struct pcp_info {
	uint8_t     version;
	uint8_t     opcode;
	uint8_t     result_code;
	uint32_t    lifetime;             /* lifetime of the mapping */
	uint32_t    epochtime;
	/* both MAP and PEER opcode specific information */
	uint32_t	nonce[3];	/* random value generated by client */
	uint8_t     protocol;
	uint16_t    int_port;
	const struct in6_addr    *int_ip; /* in network order */
	uint16_t    ext_port;
	const struct in6_addr    *ext_ip; /* Suggested external IP in network order*/
	/* PEER specific information */
#ifdef PCP_PEER
	uint16_t    peer_port;
	const struct in6_addr    *peer_ip; /* Destination IP in network order */
#endif /* PCP_PEER */

#ifdef PCP_SADSCP
	/* SADSCP specific information */
	uint8_t delay_tolerance;
	uint8_t loss_tolerance;
	uint8_t jitter_tolerance;
	uint8_t app_name_len;
	const char*   app_name;
	uint8_t sadscp_dscp;
	uint8_t matched_name;
	int8_t is_sadscp_op;
#endif

#ifdef PCP_FLOWP
	uint8_t dscp_up;
	uint8_t dscp_down;
	int flowp_present;
#endif
	uint8_t is_map_op;
	uint8_t is_peer_op;
	const struct in6_addr *thirdp_ip;
	const struct in6_addr *mapped_ip;
	char mapped_str[INET6_ADDRSTRLEN];
	int pfailure_present;
	struct in6_addr sender_ip;
	int is_fw; /* is this firewall operation? if not, nat. */
	char desc[64];
} pcp_info_t;

/* getPCPOpCodeStr()
 * return a string representation of the PCP OpCode
 * can be used for debug output */
static const char * getPCPOpCodeStr(uint8_t opcode)
{
	switch(opcode) {
	case PCP_OPCODE_ANNOUNCE:
		return "ANNOUNCE";
	case PCP_OPCODE_MAP:
		return "MAP";
	case PCP_OPCODE_PEER:
		return "PEER";
#ifdef  PCP_SADSCP
	case PCP_OPCODE_SADSCP:
		return "SADSCP";
#endif	/* PCP_SADSCP */
	default:
		return "UNKNOWN";
	}
}

/* useful to copy ext_ip only if needed, as request and response
 * buffers are same */
static void copyIPv6IfDifferent(void * dest, const void * src)
{
	if(dest != src && src != NULL) {
		memcpy(dest, src, sizeof(struct in6_addr));
	}
}

#ifdef PCP_SADSCP
int get_dscp_value(pcp_info_t *pcp_msg_info) {

	unsigned int ind;

	for (ind = 0; ind < num_dscp_values; ind++) {

		if ((dscp_values_list[ind].app_name) &&
		    (!strcmp(dscp_values_list[ind].app_name,
			     pcp_msg_info->app_name)) &&
		    (pcp_msg_info->delay_tolerance == dscp_values_list[ind].delay) &&
		    (pcp_msg_info->loss_tolerance == dscp_values_list[ind].loss) &&
		    (pcp_msg_info->jitter_tolerance == dscp_values_list[ind].jitter)
		   )
		{
			pcp_msg_info->sadscp_dscp = dscp_values_list[ind].dscp_value;
			pcp_msg_info->matched_name = 1;
			return 0;
		} else
		  if ((pcp_msg_info->app_name_len==0) &&
		      (dscp_values_list[ind].app_name_len==0) &&
		      (pcp_msg_info->delay_tolerance == dscp_values_list[ind].delay) &&
		      (pcp_msg_info->loss_tolerance == dscp_values_list[ind].loss) &&
		      (pcp_msg_info->jitter_tolerance == dscp_values_list[ind].jitter)
		     )
		{
			pcp_msg_info->sadscp_dscp = dscp_values_list[ind].dscp_value;
			pcp_msg_info->matched_name = 0;
			return 0;
		} else
		  if ((dscp_values_list[ind].app_name_len==0) &&
		      (pcp_msg_info->delay_tolerance == dscp_values_list[ind].delay) &&
		      (pcp_msg_info->loss_tolerance == dscp_values_list[ind].loss) &&
		      (pcp_msg_info->jitter_tolerance == dscp_values_list[ind].jitter)
		     )
		{
			pcp_msg_info->sadscp_dscp = dscp_values_list[ind].dscp_value;
			pcp_msg_info->matched_name = 0;
			return 0;
		}
	}
	//if nothing matched return Default value i.e. 0
	pcp_msg_info->sadscp_dscp = 0;
	pcp_msg_info->matched_name = 0;
	return 0;
}
#endif
/*
 * Function extracting information from common_req (common request header)
 * into pcp_msg_info.
 * @return : when no problem occurred 0 is returned, 1 otherwise and appropriate
 *          result code is assigned to pcp_msg_info->result_code to indicate
 *          what kind of error occurred
 */
static int parseCommonRequestHeader(const uint8_t *common_req, pcp_info_t *pcp_msg_info)
{
	pcp_msg_info->version = common_req[0] ;
	pcp_msg_info->opcode = common_req[1] & 0x7f;
	pcp_msg_info->lifetime = READNU32(common_req + 4);
	pcp_msg_info->int_ip = (struct in6_addr *)(common_req + 8);
	pcp_msg_info->mapped_ip = (struct in6_addr *)(common_req + 8);


	if ( (pcp_msg_info->version > this_server_info.server_version) ) {
		pcp_msg_info->result_code = PCP_ERR_UNSUPP_VERSION;
		return 1;
	}

	if (pcp_msg_info->lifetime > max_lifetime ) {
		pcp_msg_info->lifetime = max_lifetime;
	}

	if ( (pcp_msg_info->lifetime < min_lifetime) && (pcp_msg_info->lifetime != 0) ) {
		pcp_msg_info->lifetime = min_lifetime;
	}

	return 0;
}

#ifdef DEBUG
static void printMAPOpcodeVersion1(const uint8_t *buf)
{
	char map_addr[INET6_ADDRSTRLEN];
	syslog(LOG_DEBUG, "PCP MAP: v1 Opcode specific information. \n");
	syslog(LOG_DEBUG, "MAP protocol: \t\t %d\n", (int)buf[0] );
	syslog(LOG_DEBUG, "MAP int port: \t\t %d\n", (int)READNU16(buf+4));
	syslog(LOG_DEBUG, "MAP ext port: \t\t %d\n", (int)READNU16(buf+6));
	syslog(LOG_DEBUG, "MAP Ext IP: \t\t %s\n", inet_ntop(AF_INET6,
	       buf+8, map_addr, INET6_ADDRSTRLEN));
}

static void printMAPOpcodeVersion2(const uint8_t *buf)
{
	char map_addr[INET6_ADDRSTRLEN];
	syslog(LOG_DEBUG, "PCP MAP: v2 Opcode specific information.");
	syslog(LOG_DEBUG, "MAP nonce:   \t%08x%08x%08x",
	       READNU32(buf), READNU32(buf+4), READNU32(buf+8));
	syslog(LOG_DEBUG, "MAP protocol:\t%d", (int)buf[12]);
	syslog(LOG_DEBUG, "MAP int port:\t%d", (int)READNU16(buf+16));
	syslog(LOG_DEBUG, "MAP ext port:\t%d", (int)READNU16(buf+18));
	syslog(LOG_DEBUG, "MAP Ext IP:  \t%s", inet_ntop(AF_INET6,
	       buf+20, map_addr, INET6_ADDRSTRLEN));
}
#endif /* DEBUG */

static void parsePCPMAP_version1(const uint8_t *map_v1,
		pcp_info_t *pcp_msg_info)
{
	pcp_msg_info->is_map_op = 1;
	pcp_msg_info->protocol = map_v1[0];
	pcp_msg_info->int_port = READNU16(map_v1 + 4);
	pcp_msg_info->ext_port = READNU16(map_v1 + 6);

	pcp_msg_info->ext_ip = (struct in6_addr *)(map_v1 + 8);
}

static void parsePCPMAP_version2(const uint8_t *map_v2,
		pcp_info_t *pcp_msg_info)
{
	pcp_msg_info->is_map_op = 1;
	memcpy(pcp_msg_info->nonce, map_v2, 12);
	pcp_msg_info->protocol = map_v2[12];
	pcp_msg_info->int_port = READNU16(map_v2 + 16);
	pcp_msg_info->ext_port = READNU16(map_v2 + 18);

	pcp_msg_info->ext_ip = (struct in6_addr *)(map_v2 + 20);
}

#ifdef PCP_PEER
#ifdef DEBUG
static void printPEEROpcodeVersion1(const uint8_t *buf)
{
	char ext_addr[INET6_ADDRSTRLEN];
	char peer_addr[INET6_ADDRSTRLEN];
	syslog(LOG_DEBUG, "PCP PEER: v1 Opcode specific information. \n");
	syslog(LOG_DEBUG, "Protocol: \t\t %d\n", (int)buf[0]);
	syslog(LOG_DEBUG, "Internal port: \t\t %d\n", READNU16(buf + 4));
	syslog(LOG_DEBUG, "External IP: \t\t %s\n", inet_ntop(AF_INET6, buf + 8,
	       ext_addr,INET6_ADDRSTRLEN));
	syslog(LOG_DEBUG, "External port port: \t\t %d\n", READNU16(buf + 6));
	syslog(LOG_DEBUG, "PEER IP: \t\t %s\n", inet_ntop(AF_INET6, buf + 28,
	       peer_addr,INET6_ADDRSTRLEN));
	syslog(LOG_DEBUG, "PEER port port: \t\t %d\n", READNU16(buf + 24));
}

static void printPEEROpcodeVersion2(const uint8_t *buf)
{
	char ext_addr[INET6_ADDRSTRLEN];
	char peer_addr[INET6_ADDRSTRLEN];

	syslog(LOG_DEBUG, "PCP PEER: v2 Opcode specific information.");
	syslog(LOG_DEBUG, "nonce:        \t%08x%08x%08x",
	       READNU32(buf), READNU32(buf+4), READNU32(buf+8));
	syslog(LOG_DEBUG, "Protocol:     \t%d", buf[12]);
	syslog(LOG_DEBUG, "Internal port:\t%d", READNU16(buf + 16));
	syslog(LOG_DEBUG, "External IP:  \t%s", inet_ntop(AF_INET6, buf + 20,
	       ext_addr, INET6_ADDRSTRLEN));
	syslog(LOG_DEBUG, "External port:\t%d", READNU16(buf + 18));
	syslog(LOG_DEBUG, "PEER IP:      \t%s", inet_ntop(AF_INET6, buf + 40,
	       peer_addr, INET6_ADDRSTRLEN));
	syslog(LOG_DEBUG, "PEER port:    \t%d", READNU16(buf + 36));
}
#endif /* DEBUG */

/*
 * Function extracting information from peer_buf to pcp_msg_info
 * @return : when no problem occurred 0 is returned, 1 otherwise
 */
static void parsePCPPEER_version1(const uint8_t *buf,
		pcp_info_t *pcp_msg_info)
{
	pcp_msg_info->is_peer_op = 1;
	pcp_msg_info->protocol = buf[0];
	pcp_msg_info->int_port = READNU16(buf + 4);
	pcp_msg_info->ext_port = READNU16(buf + 6);
	pcp_msg_info->peer_port = READNU16(buf + 24);

	pcp_msg_info->ext_ip = (struct in6_addr *)(buf + 8);
	pcp_msg_info->peer_ip = (struct in6_addr *)(buf + 28);
}

/*
 * Function extracting information from peer_buf to pcp_msg_info
 * @return : when no problem occurred 0 is returned, 1 otherwise
 */
static void parsePCPPEER_version2(const uint8_t *buf, pcp_info_t *pcp_msg_info)
{
	pcp_msg_info->is_peer_op = 1;
	memcpy(pcp_msg_info->nonce, buf, 12);
	pcp_msg_info->protocol = buf[12];
	pcp_msg_info->int_port = READNU16(buf + 16);
	pcp_msg_info->ext_port = READNU16(buf + 18);
	pcp_msg_info->peer_port = READNU16(buf + 36);

	pcp_msg_info->ext_ip = (struct in6_addr *)(buf + 20);
	pcp_msg_info->peer_ip = (struct in6_addr *)(buf + 40);
}
#endif /* PCP_PEER */

#ifdef PCP_SADSCP
#ifdef DEBUG
static void printSADSCPOpcode(const uint8_t *buf)
{
	unsigned char sadscp_tol;
	sadscp_tol = buf[12];	/* tolerance_fields */

	syslog(LOG_DEBUG, "PCP SADSCP: Opcode specific information.\n");
	syslog(LOG_DEBUG, "Delay tolerance %d \n", (sadscp_tol>>6)&3);
	syslog(LOG_DEBUG, "Loss tolerance %d \n",  (sadscp_tol>>4)&3);
	syslog(LOG_DEBUG, "Jitter tolerance %d \n",  (sadscp_tol>>2)&3);
	syslog(LOG_DEBUG, "RRR %d \n", sadscp_tol&3);
	syslog(LOG_DEBUG, "AppName Length %d \n", buf[13]);
	syslog(LOG_DEBUG, "Application name %.*s \n", buf[13], buf + 14);
}
#endif //DEBUG

static int parseSADSCP(const uint8_t *buf, pcp_info_t *pcp_msg_info)
{
	pcp_msg_info->delay_tolerance = (buf[12]>>6)&3;
	pcp_msg_info->loss_tolerance = (buf[12]>>4)&3;
	pcp_msg_info->jitter_tolerance = (buf[12]>>2)&3;

	if (pcp_msg_info->delay_tolerance == 3 ||
	    pcp_msg_info->loss_tolerance == 3 ||
	    pcp_msg_info->jitter_tolerance == 3 ) {
		pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
		return 1;
	}

	pcp_msg_info->app_name = (const char *)(buf + 14);
	pcp_msg_info->app_name_len = buf[13];

	return 0;
}
#endif	/* PCP_SADSCP */


static int parsePCPOption(uint8_t* pcp_buf, int remain, pcp_info_t *pcp_msg_info)
{
#ifdef DEBUG
	char third_addr[INET6_ADDRSTRLEN];
#endif /* DEBUG */
	unsigned short option_length;

	/* Do centralized option sanity checks here. */

	if (remain < (int)PCP_OPTION_HDR_SIZE) {
		pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
		return 0;
	}

	option_length = READNU16(pcp_buf + 2) + 4;	/* len */

	if (remain < option_length) {
		pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
		return 0;
	}

	switch (pcp_buf[0]) { /* code */

	case PCP_OPTION_3RD_PARTY:

		if (option_length != PCP_3RD_PARTY_OPTION_SIZE) {
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
			return 0;
		}
#ifdef DEBUG
		syslog(LOG_DEBUG, "PCP OPTION: \t Third party\n");
		syslog(LOG_DEBUG, "Third PARTY IP: \t %s\n", inet_ntop(AF_INET6,
		       pcp_buf + 4, third_addr, INET6_ADDRSTRLEN));
#endif
		if (pcp_msg_info->thirdp_ip ) {
			syslog(LOG_ERR, "PCP: THIRD PARTY OPTION was already present. \n");
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
			return 0;
		} else {
			pcp_msg_info->thirdp_ip = (struct in6_addr *)(pcp_buf + 4);
			pcp_msg_info->mapped_ip = (struct in6_addr *)(pcp_buf + 4);
		}
		break;

	case PCP_OPTION_PREF_FAIL:

		if (option_length != PCP_PREFER_FAIL_OPTION_SIZE) {
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
			return 0;
		}
#ifdef DEBUG
		syslog(LOG_DEBUG, "PCP OPTION: \t Prefer failure \n");
#endif
		if (pcp_msg_info->opcode != PCP_OPCODE_MAP) {
			syslog(LOG_DEBUG, "PCP: Unsupported OPTION for given OPCODE.\n");
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
		}
		if (pcp_msg_info->pfailure_present != 0 ) {
			syslog(LOG_DEBUG, "PCP: PREFER FAILURE OPTION was already present.\n");
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
		} else {
			pcp_msg_info->pfailure_present = 1;
		}
		break;

	case PCP_OPTION_FILTER:
		/* TODO fully implement filter */
		if (option_length != PCP_FILTER_OPTION_SIZE) {
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
			return 0;
		}
#ifdef DEBUG
		syslog(LOG_DEBUG, "PCP OPTION: \t Filter\n");
#endif
		if (pcp_msg_info->opcode != PCP_OPCODE_MAP) {
			syslog(LOG_ERR, "PCP: Unsupported OPTION for given OPCODE.\n");
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
			return 0;
		}
		break;

#ifdef PCP_FLOWP
	case PCP_OPTION_FLOW_PRIORITY:

#ifdef DEBUG
		syslog(LOG_DEBUG, "PCP OPTION: \t Flow priority\n");
#endif
		if (option_length != PCP_FLOW_PRIORITY_OPTION_SIZE) {
			syslog(LOG_ERR, "PCP: Error processing DSCP. sizeof %d and remaining %d. flow len %d \n",
			       PCP_FLOW_PRIORITY_OPTION_SIZE, remain, READNU16(pcp_buf + 2));
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
			return 0;
		}

#ifdef DEBUG
		syslog(LOG_DEBUG, "DSCP UP: \t %d\n", pcp_buf[4]);
		syslog(LOG_DEBUG, "DSCP DOWN: \t %d\n", pcp_buf[5]);
#endif
		pcp_msg_info->dscp_up = pcp_buf[4];
		pcp_msg_info->dscp_down = pcp_buf[5];
		pcp_msg_info->flowp_present = 1;

		break;
#endif
	default:
		if (pcp_buf[0] < 128) {
			syslog(LOG_ERR, "PCP: Unrecognized mandatory PCP OPTION: %d \n", (int)pcp_buf[0]);
			/* Mandatory to understand */
			pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPTION;
			remain = 0;
			break;
		}
		/* TODO - log optional not understood options? */
		break;
	}
	return option_length;
}


static void parsePCPOptions(void* pcp_buf, int remain, pcp_info_t *pcp_msg_info)
{
	int option_length;

	while (remain > 0) {
		option_length = parsePCPOption(pcp_buf, remain, pcp_msg_info);
		if (!option_length)
			break;
		remain -= option_length;
		pcp_buf += option_length;
	}
	if (remain > 0) {
		syslog(LOG_WARNING, "%s: remain=%d", "parsePCPOptions", remain);
	}
}


/* CheckExternalAddress()
 * Check that suggested external address in request match a real external
 * IP address.
 * Suggested address can also be 0 IPv4 or IPv6 address.
 *  (see http://tools.ietf.org/html/rfc6887#section-10 )
 * return values :
 *   0 : check is OK
 *  -1 : check failed */
static int CheckExternalAddress(pcp_info_t* pcp_msg_info)
{
	/* can contain a IPv4-mapped IPv6 address */
	static struct in6_addr external_addr;
	int af;

	af = IN6_IS_ADDR_V4MAPPED(pcp_msg_info->mapped_ip)
		? AF_INET : AF_INET6;

	pcp_msg_info->is_fw = af == AF_INET6;

	if (pcp_msg_info->is_fw) {
		external_addr = *pcp_msg_info->mapped_ip;
	} else {
		/* TODO : be able to handle case with multiple
		 * external addresses */
		if(use_ext_ip_addr) {
			if (inet_pton(AF_INET, use_ext_ip_addr,
				      ((uint32_t*)external_addr.s6_addr)+3) == 1) {
				((uint32_t*)external_addr.s6_addr)[0] = 0;
				((uint32_t*)external_addr.s6_addr)[1] = 0;
				((uint32_t*)external_addr.s6_addr)[2] = htonl(0xFFFF);
			} else if (inet_pton(AF_INET6, use_ext_ip_addr, external_addr.s6_addr)
				   != 1) {
				pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE;
				return -1;
			}
#ifdef ENABLE_IPV6
		} else if ((af == AF_INET6) && (ext_if_name6 != ext_if_name)) {
			if(!ext_if_name6 || ext_if_name6[0]=='\0') {
				pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE;
				return -1;
			}
			if(getifaddr_in6(ext_if_name6, af, &external_addr) < 0) {
				pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE;
				return -1;
			}
#endif
		} else {
			if(!ext_if_name || ext_if_name[0]=='\0') {
				pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE;
				return -1;
			}
			if(getifaddr_in6(ext_if_name, af, &external_addr) < 0) {
				pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE;
				return -1;
			}
		}
	}
	if (pcp_msg_info->ext_ip == NULL ||
	    IN6_IS_ADDR_UNSPECIFIED(pcp_msg_info->ext_ip) ||
	    (IN6_IS_ADDR_V4MAPPED(pcp_msg_info->ext_ip)
	      && ((uint32_t *)pcp_msg_info->ext_ip->s6_addr)[3] == INADDR_ANY)) {
		/* no suggested external address : use real external address */
		pcp_msg_info->ext_ip = &external_addr;
		return 0;
	}

	if (!IN6_ARE_ADDR_EQUAL(pcp_msg_info->ext_ip, &external_addr)) {
		syslog(LOG_ERR,
		       "PCP: External IP in request didn't match interface IP \n");
#ifdef DEBUG
		{
			char s[INET6_ADDRSTRLEN];
			syslog(LOG_DEBUG, "Interface IP %s \n",
			       inet_ntop(AF_INET6, &external_addr.s6_addr, s, sizeof(s)));
			syslog(LOG_DEBUG, "IP in the PCP request %s \n",
			       inet_ntop(AF_INET6, pcp_msg_info->ext_ip, s, sizeof(s)));
		}
#endif

		if (pcp_msg_info->pfailure_present) {
			pcp_msg_info->result_code = PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
			return -1;
		} else {
			pcp_msg_info->ext_ip = &external_addr;
		}

	}

	return 0;
}


static const char* inet_n46top(const struct in6_addr* in,
			       char* buf, size_t buf_len)
{
	if (IN6_IS_ADDR_V4MAPPED(in)) {
		return inet_ntop(AF_INET, ((uint32_t*)(in->s6_addr))+3, buf, buf_len);
	} else {
		return inet_ntop(AF_INET6, in, buf, buf_len);
	}
}

#ifdef PCP_PEER
static void FillSA(struct sockaddr *sa, const struct in6_addr *in6,
		uint16_t port)
{
	if (IN6_IS_ADDR_V4MAPPED(in6)) {
		struct sockaddr_in *sa4 = (struct sockaddr_in *)sa;
		sa4->sin_family = AF_INET;
		sa4->sin_addr.s_addr = ((uint32_t*)(in6)->s6_addr)[3];
		sa4->sin_port = htons(port);
	} else {
		struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa;
		sa6->sin6_family = AF_INET6;
		sa6->sin6_addr = *in6;
		sa6->sin6_port = htons(port);
	}
}

static const char* inet_satop(struct sockaddr* sa, char* buf, size_t buf_len)
{
	if (sa->sa_family == AF_INET) {
		return inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr), buf, buf_len);
	} else {
		return inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr), buf, buf_len);
	}
}

static int CreatePCPPeer_NAT(pcp_info_t *pcp_msg_info)
{
	struct sockaddr_storage intip;
	struct sockaddr_storage peerip;
	struct sockaddr_storage extip;
	struct sockaddr_storage ret_extip;

	uint8_t  proto = pcp_msg_info->protocol;

	uint16_t eport = pcp_msg_info->ext_port;  /* public port */

	char peerip_s[INET6_ADDRSTRLEN], extip_s[INET6_ADDRSTRLEN];
	time_t timestamp = upnp_time() + pcp_msg_info->lifetime;
	int r;
	const char * ext_if = ext_if_name;

	FillSA((struct sockaddr*)&intip, pcp_msg_info->mapped_ip,
	       pcp_msg_info->int_port);
	FillSA((struct sockaddr*)&peerip, pcp_msg_info->peer_ip,
	       pcp_msg_info->peer_port);
	FillSA((struct sockaddr*)&extip, pcp_msg_info->ext_ip,
	       eport);

	inet_satop((struct sockaddr*)&peerip, peerip_s, sizeof(peerip_s));
	inet_satop((struct sockaddr*)&extip, extip_s, sizeof(extip_s));

	/* check if connection with given peer exists, if it was */
	/* already established use this external port */
	if (get_nat_ext_addr( (struct sockaddr*)&intip, (struct sockaddr*)&peerip,
			      proto, (struct sockaddr*)&ret_extip) == 1) {
		if (ret_extip.ss_family == AF_INET) {
			struct sockaddr_in* ret_ext4 = (struct sockaddr_in*)&ret_extip;
			uint16_t ret_eport = ntohs(ret_ext4->sin_port);
			eport = ret_eport;
		} else if (ret_extip.ss_family == AF_INET6) {
			struct sockaddr_in6* ret_ext6 = (struct sockaddr_in6*)&ret_extip;
			uint16_t ret_eport = ntohs(ret_ext6->sin6_port);
			eport = ret_eport;
		} else {
			return PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
		}
	}
	/* Create Peer Mapping */
	if (eport == 0) {
		eport = pcp_msg_info->int_port;
	}

#ifdef ENABLE_IPV6
	if (ret_extip.ss_family == AF_INET6) {
		ext_if = ext_if_name6;
	}
#endif
#ifdef PCP_FLOWP
	if (pcp_msg_info->flowp_present && pcp_msg_info->dscp_up) {
		if (add_peer_dscp_rule2(ext_if, peerip_s,
					pcp_msg_info->peer_port, pcp_msg_info->dscp_up,
					pcp_msg_info->mapped_str, pcp_msg_info->int_port,
					proto, pcp_msg_info->desc, timestamp) < 0 ) {
			syslog(LOG_ERR, "PCP: failed to add flowp upstream mapping %s:%hu->%s:%hu '%s'",
			       pcp_msg_info->mapped_str,
			       pcp_msg_info->int_port,
			       peerip_s,
			       pcp_msg_info->peer_port,
			       pcp_msg_info->desc);
			return PCP_ERR_NO_RESOURCES;
		}
	}

	if (pcp_msg_info->flowp_present && pcp_msg_info->dscp_down) {
		if (add_peer_dscp_rule2(ext_if,  pcp_msg_info->mapped_str,
					pcp_msg_info->int_port, pcp_msg_info->dscp_down,
					peerip_s, pcp_msg_info->peer_port, proto, pcp_msg_info->desc, timestamp)
		    < 0 ) {
			syslog(LOG_ERR, "PCP: failed to add flowp downstream mapping %s:%hu->%s:%hu '%s'",
			       pcp_msg_info->mapped_str,
			       pcp_msg_info->int_port,
			       peerip_s,
			       pcp_msg_info->peer_port,
			       pcp_msg_info->desc);
			pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
			return PCP_ERR_NO_RESOURCES;
		}
	}
#endif

	r = add_peer_redirect_rule2(ext_if,
				    peerip_s,
				    pcp_msg_info->peer_port,
				    extip_s,
				    eport,
				    pcp_msg_info->mapped_str,
				    pcp_msg_info->int_port,
				    pcp_msg_info->protocol,
				    pcp_msg_info->desc,
				    timestamp);
	if (r < 0)
		return PCP_ERR_NO_RESOURCES;
	pcp_msg_info->ext_port = eport;
	return PCP_SUCCESS;
}

static void CreatePCPPeer(pcp_info_t *pcp_msg_info)
{
	char peerip_s[INET6_ADDRSTRLEN];
	int r = -1;

	if (!inet_n46top(pcp_msg_info->peer_ip, peerip_s, sizeof(peerip_s))) {
		syslog(LOG_ERR, "inet_n46top(peer_ip): %m");
		return;
	}

	if (pcp_msg_info->is_fw) {
#if 0
		/* Someday, something like this is available.. and we're ready! */
#ifdef ENABLE_UPNPPINHOLE
		pcp_msg_info->ext_port = pcp_msg_info->int_port;
		r = upnp_add_outbound_pinhole(peerip_s,
					      pcp_msg_info->peer_port,
					      pcp_msg_info->mapped_str,
					      pcp_msg_info->int_port,
					      pcp_msg_info->protocol,
					      pcp_msg_info->desc,
					      pcp_msg_info->lifetime, NULL);
#endif /* ENABLE_UPNPPINHOLE */
#else
		r = PCP_ERR_UNSUPP_OPCODE;
#endif /* 0 */
	} else {
		r = CreatePCPPeer_NAT(pcp_msg_info);
	}
	/* TODO: add upnp function for PI */
	pcp_msg_info->result_code = r;
	syslog(r == PCP_SUCCESS ? LOG_INFO : LOG_ERR,
	       "PCP PEER: %s peer mapping %s %s:%hu(%hu)->%s:%hu '%s'",
	       r == PCP_SUCCESS ? "added" : "failed to add",
	       (pcp_msg_info->protocol==IPPROTO_TCP)?"TCP":"UDP",
	       pcp_msg_info->mapped_str,
	       pcp_msg_info->int_port,
	       pcp_msg_info->ext_port,
	       peerip_s,
	       pcp_msg_info->peer_port,
	       pcp_msg_info->desc);
}

static void DeletePCPPeer(pcp_info_t *pcp_msg_info)
{
	uint16_t iport = pcp_msg_info->int_port;  /* private port */
	uint16_t rport = pcp_msg_info->peer_port;  /* private port */
	uint8_t  proto = pcp_msg_info->protocol;
	char rhost[INET6_ADDRSTRLEN];
	int r = -1;

	/* remove requested mappings for this client */
	int index = 0;
	unsigned short eport2, iport2, rport2;
	char iaddr2[INET6_ADDRSTRLEN], rhost2[INET6_ADDRSTRLEN];
	int proto2;
	char desc[64];
	unsigned int timestamp;
#if 0
	int uid;
#endif /* 0 */

	if (pcp_msg_info->is_fw) {
		pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPCODE;
		return;
	}

	inet_n46top((struct in6_addr*)pcp_msg_info->peer_ip, rhost, sizeof(rhost));

	for (index = 0 ;
	     (!pcp_msg_info->is_fw &&
	      get_peer_rule_by_index(index, 0,
				     &eport2, iaddr2, sizeof(iaddr2),
				     &iport2, &proto2,
				     desc, sizeof(desc),
				     rhost2, sizeof(rhost2), &rport2,
				     &timestamp, 0, 0) >= 0)
#if 0
		     /* Some day if outbound pinholes are supported.. */
		     ||
		     (pcp_msg_info->is_fw &&
		      (uid=upnp_get_pinhole_uid_by_index(index))>=0 &&
		      upnp_get_pinhole_info((unsigned short)uid,
					    rhost2, sizeof(rhost2), &rport2,
					    iaddr2, sizeof(iaddr2), &iport2,
					    &proto2, desc, sizeof(desc),
					    &timestamp, NULL) >= 0)
#endif /* 0 */
		     ;
	     index++)
		if((0 == strcmp(iaddr2, pcp_msg_info->mapped_str))
		   && (0 == strcmp(rhost2, rhost))
		   && (proto2==proto)
		   && 0 == strcmp(desc, pcp_msg_info->desc)
		   && (iport2==iport) && (rport2==rport)) {
			if (!pcp_msg_info->is_fw)
				r = _upnp_delete_redir(eport2, proto2);
#if 0
			else
				r = upnp_delete_outboundpinhole(uid);
#endif /* 0 */
			if(r<0) {
				syslog(LOG_ERR, "PCP PEER: failed to remove peer mapping");
			} else {
				syslog(LOG_INFO, "PCP PEER: %s port %hu peer mapping removed",
				       proto2==IPPROTO_TCP?"TCP":"UDP", eport2);
			}
			return;
		}
	if (r==-1) {
		syslog(LOG_ERR, "PCP PEER: Failed to find PCP mapping internal port %hu, protocol %s",
		       iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP");
		pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
	}
}
#endif /* PCP_PEER */

static int CreatePCPMap_NAT(pcp_info_t *pcp_msg_info)
{
	int r = 0;
	char iaddr_old[INET6_ADDRSTRLEN];
	uint16_t iport_old, eport_first = 0;
	int any_eport_allowed = 0;
	unsigned int timestamp = upnp_time() + pcp_msg_info->lifetime;

	if (pcp_msg_info->ext_port == 0) {
		pcp_msg_info->ext_port = pcp_msg_info->int_port;
	}

	/* TODO: Support non-TCP/UDP */
	if (pcp_msg_info->ext_port == 0) {
		return PCP_ERR_MALFORMED_REQUEST;
	}

	do {
		if (eport_first == 0) { /* first time in loop */
			eport_first = pcp_msg_info->ext_port;
		} else if (pcp_msg_info->ext_port == eport_first) { /* no eport available */
                        /* all eports rejected by permissions? */
			if (any_eport_allowed == 0)
				return PCP_ERR_NOT_AUTHORIZED;
			/* at least one eport allowed (but none available) */
			return PCP_ERR_NO_RESOURCES;
		}
		if ((IN6_IS_ADDR_V4MAPPED(pcp_msg_info->mapped_ip) &&
		     (!check_upnp_rule_against_permissions(upnppermlist,
							   num_upnpperm, pcp_msg_info->ext_port,
							   ((struct in_addr*)pcp_msg_info->mapped_ip->s6_addr)[3],
							   pcp_msg_info->int_port)))) {
			if (pcp_msg_info->pfailure_present) {
				return PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
			}
			pcp_msg_info->ext_port++;
			if (pcp_msg_info->ext_port == 0) { /* skip port zero */
				pcp_msg_info->ext_port++;
			}
			continue;
		}
		any_eport_allowed = 1;
#ifdef CHECK_PORTINUSE
		if (port_in_use(ext_if_name, pcp_msg_info->ext_port, pcp_msg_info->protocol,
				pcp_msg_info->mapped_str, pcp_msg_info->int_port) > 0) {
			syslog(LOG_INFO, "port %hu protocol %s already in use",
			       pcp_msg_info->ext_port,
			       (pcp_msg_info->protocol==IPPROTO_TCP)?"tcp":"udp");
			pcp_msg_info->ext_port++;
			if (pcp_msg_info->ext_port == 0) { /* skip port zero */
				pcp_msg_info->ext_port++;
			}
			continue;
		}
#endif
		r = get_redirect_rule(ext_if_name,
				      pcp_msg_info->ext_port,
				      pcp_msg_info->protocol,
				      iaddr_old, sizeof(iaddr_old),
				      &iport_old, 0, 0, 0, 0,
				      NULL/*&timestamp*/, 0, 0);

		if(r==0) {
			if((strcmp(pcp_msg_info->mapped_str, iaddr_old)!=0)
			   || (pcp_msg_info->int_port != iport_old)) {
				/* redirection already existing */
				if (pcp_msg_info->pfailure_present) {
					return PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
				}
			} else {
				syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing",
				       pcp_msg_info->ext_port, (pcp_msg_info->protocol==IPPROTO_TCP)?"tcp":"udp",
				       iaddr_old, iport_old);
				/* remove and then add again */
				if (_upnp_delete_redir(pcp_msg_info->ext_port,
						       pcp_msg_info->protocol)==0) {
					break;
				} else if (pcp_msg_info->pfailure_present) {
					return PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
				}
			}
			pcp_msg_info->ext_port++;
			if (pcp_msg_info->ext_port == 0) { /* skip port zero */
				pcp_msg_info->ext_port++;
			}
		}
	} while (r==0);

	r = upnp_redirect_internal(NULL,
				   pcp_msg_info->ext_port,
				   pcp_msg_info->mapped_str,
				   pcp_msg_info->int_port,
				   pcp_msg_info->protocol,
				   pcp_msg_info->desc,
				   timestamp);
	if (r < 0)
		return PCP_ERR_NO_RESOURCES;
	return PCP_SUCCESS;
}

static int CreatePCPMap_FW(pcp_info_t *pcp_msg_info)
{
#ifdef ENABLE_UPNPPINHOLE
	int uid;
	int r;
	/* first check if pinhole already exists */
	uid = upnp_find_inboundpinhole(NULL, 0,
					pcp_msg_info->mapped_str,
					pcp_msg_info->int_port,
					pcp_msg_info->protocol,
					NULL, 0, /* desc */
					NULL /* lifetime */);
	if(uid >= 0) {
		/* pinhole already exists, updating */
		syslog(LOG_INFO, "updating pinhole to %s:%hu %s",
		       pcp_msg_info->mapped_str, pcp_msg_info->int_port,
		       (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP");
		r = upnp_update_inboundpinhole((unsigned short)uid, pcp_msg_info->lifetime);
		return r >= 0 ? PCP_SUCCESS : PCP_ERR_NO_RESOURCES;
	} else {
		r = upnp_add_inboundpinhole(NULL, 0,
						pcp_msg_info->mapped_str,
						pcp_msg_info->int_port,
						pcp_msg_info->protocol,
						pcp_msg_info->desc,
						pcp_msg_info->lifetime,
						&uid);
		if (r < 0)
			return PCP_ERR_NO_RESOURCES;
		pcp_msg_info->ext_port = pcp_msg_info->int_port;
		return PCP_SUCCESS;
	}
#else
	UNUSED(pcp_msg_info);
	return PCP_ERR_NO_RESOURCES;
#endif /* ENABLE_UPNPPINHOLE */
}


/*                internal  external  PCP remote peer  actual remote peer
 *                --------  -------   ---------------  ------------------
 * IPv4 firewall   IPv4      IPv4         IPv4              IPv4
 * IPv6 firewall   IPv6      IPv6         IPv6              IPv6
 *         NAT44   IPv4      IPv4         IPv4              IPv4
 *         NAT46   IPv4      IPv6         IPv4              IPv6
 *         NAT64   IPv6      IPv4         IPv6              IPv4
 *         NPTv6   IPv6      IPv6         IPv6              IPv6
 *
 *             Address Families with MAP and PEER
 *
 * The 'internal' address is implicitly the same as the source IP
 * address of the PCP request, except when the THIRD_PARTY option is
 * used.
 *
 * The 'external' address is the Suggested External Address field of the
 * MAP or PEER request, and its address family is usually the same as
 * the 'internal' address family, except when technologies like NAT64
 * are used.
 *
 * The 'remote peer' address is the remote peer IP address of the PEER
 * request or the FILTER option of the MAP request, and is always the
 * same address family as the 'internal' address, even when NAT64 is
 * used.  In NAT64, the IPv6 PCP client is not necessarily aware of the
 * NAT64 or aware of the actual IPv4 address of the remote peer, so it
 * expresses the IPv6 address from its perspective.     */

/* TODO: Support more than basic NAT44 / IPv6 firewall cases. */
static void CreatePCPMap(pcp_info_t *pcp_msg_info)
{
	int r;

	if (pcp_msg_info->is_fw)
		r = CreatePCPMap_FW(pcp_msg_info);
	else
		r = CreatePCPMap_NAT(pcp_msg_info);
	pcp_msg_info->result_code = r;
	syslog(r == PCP_SUCCESS ? LOG_INFO : LOG_ERR,
	      "PCP MAP: %s mapping %s %hu->%s:%hu '%s'",
	       r == PCP_SUCCESS ? "added" : "failed to add",
	       (pcp_msg_info->protocol==IPPROTO_TCP)?"TCP":"UDP",
	       pcp_msg_info->ext_port,
	       pcp_msg_info->mapped_str,
	       pcp_msg_info->int_port,
	       pcp_msg_info->desc);
}

static void DeletePCPMap(pcp_info_t *pcp_msg_info)
{
	uint16_t iport = pcp_msg_info->int_port;  /* private port */
	uint8_t  proto = pcp_msg_info->protocol;
	int r=-1;
	/* remove the mapping */
	/* remove all the mappings for this client */
	int index;
	unsigned short eport2, iport2;
	char iaddr2[INET6_ADDRSTRLEN];
	int proto2;
	char desc[64];
	unsigned int timestamp;
#ifdef ENABLE_UPNPPINHOLE
	int uid = -1;
#endif /* ENABLE_UPNPPINHOLE */

	/* iterate through all rules and delete the requested ones */
	for (index = 0 ;
	     (!pcp_msg_info->is_fw &&
	      get_redirect_rule_by_index(index, 0,
					 &eport2, iaddr2, sizeof(iaddr2),
					 &iport2, &proto2,
					 desc, sizeof(desc),
					 0, 0, &timestamp, 0, 0) >= 0)
#ifdef ENABLE_UPNPPINHOLE
	       ||
	     (pcp_msg_info->is_fw &&
	      (uid=upnp_get_pinhole_uid_by_index(index))>=0 &&
	      upnp_get_pinhole_info((unsigned short)uid,
				    NULL, 0, NULL,
				    iaddr2, sizeof(iaddr2), &iport2,
				    &proto2, desc, sizeof(desc),
				    &timestamp, NULL) >= 0)
#endif /* ENABLE_UPNPPINHOLE */
		     ;
	     index++)
		if(0 == strcmp(iaddr2, pcp_msg_info->mapped_str)
		   && (proto2==proto)
		   && ((iport2==iport) || (iport==0))) {
			if(0 != strcmp(desc, pcp_msg_info->desc)) {
				/* nonce does not match */
				pcp_msg_info->result_code = PCP_ERR_NOT_AUTHORIZED;
				syslog(LOG_ERR, "Unauthorized to remove PCP mapping internal port %hu, protocol %s",
				       iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP");
				return;
			} else if (!pcp_msg_info->is_fw) {
				r = _upnp_delete_redir(eport2, proto2);
			} else {
#ifdef ENABLE_UPNPPINHOLE
				r = upnp_delete_inboundpinhole(uid);
#endif /* ENABLE_UPNPPINHOLE */
			}
			break;
		}
	if (r >= 0) {
		syslog(LOG_INFO, "PCP: %s port %hu mapping removed",
		       proto2==IPPROTO_TCP?"TCP":"UDP", eport2);
	} else {
		syslog(LOG_ERR, "Failed to remove PCP mapping internal port %hu, protocol %s",
		       iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP");
		pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
	}
}

static int ValidatePCPMsg(pcp_info_t *pcp_msg_info)
{
	if (pcp_msg_info->result_code) {
		return 0;
	}

	/* RFC 6887, section 8.2: MUST return address mismatch if NAT
	 * in middle. */
	if (memcmp(pcp_msg_info->int_ip,
		   &pcp_msg_info->sender_ip,
		   sizeof(pcp_msg_info->sender_ip)) != 0) {
		pcp_msg_info->result_code = PCP_ERR_ADDRESS_MISMATCH;
		return 0;
	}

	if (pcp_msg_info->thirdp_ip) {
		if (!GETFLAG(PCP_ALLOWTHIRDPARTYMASK)) {
			pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPTION;
			return 0;
		}

		/* RFC687, section 13.1 - if sender ip == THIRD_PARTY,
		 * it's an error. */
		if (memcmp(pcp_msg_info->thirdp_ip,
			   &pcp_msg_info->sender_ip,
			   sizeof(pcp_msg_info->sender_ip)) == 0) {
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
			return 0;
		}
	}

	/* Produce mapped_str for future use. */
	if (!inet_n46top(pcp_msg_info->mapped_ip, pcp_msg_info->mapped_str,
		         sizeof(pcp_msg_info->mapped_str))) {
		syslog(LOG_ERR, "inet_ntop(pcpserver): %m");
		return 0;
	}

	/* protocol zero means 'all protocols' : internal port MUST be zero */
	if (pcp_msg_info->protocol == 0 && pcp_msg_info->int_port != 0) {
		syslog(LOG_ERR, "PCP %s: Protocol was ZERO, but internal port "
		       "has non-ZERO value.", getPCPOpCodeStr(pcp_msg_info->opcode));
		pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
		return 0;
	}

	if (pcp_msg_info->pfailure_present) {
		if ( (IN6_IS_ADDR_UNSPECIFIED(pcp_msg_info->ext_ip) ||
		      ((IN6_IS_ADDR_V4MAPPED(pcp_msg_info->ext_ip)) &&
		       (((uint32_t*)pcp_msg_info->ext_ip->s6_addr)[3] == 0))) &&
		     (pcp_msg_info->ext_port == 0)
		   )
		{
			pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
			return 0;
		}
	}

	if (CheckExternalAddress(pcp_msg_info)) {
		return 0;
	}

	/* Fill in the desc that describes uniquely what flow we're
	 * dealing with (same code used in both create + delete of
	 * MAP/PEER) */
	switch (pcp_msg_info->opcode) {
	case PCP_OPCODE_MAP:
	case PCP_OPCODE_PEER:
		snprintf(pcp_msg_info->desc, sizeof(pcp_msg_info->desc),
			 "PCP %s %08x%08x%08x",
			 getPCPOpCodeStr(pcp_msg_info->opcode),
			 pcp_msg_info->nonce[0],
			 pcp_msg_info->nonce[1], pcp_msg_info->nonce[2]);
		break;
	}
	return 1;
}

/*
 * return value indicates whether the request is valid or not.
 * Based on the return value simple response can be formed.
 */
static int processPCPRequest(void * req, int req_size, pcp_info_t *pcp_msg_info)
{
	int remainingSize;

	/* start with PCP_SUCCESS as result code,
	 * if everything is OK value will be unchanged */
	pcp_msg_info->result_code = PCP_SUCCESS;

	remainingSize = req_size;

	/* discard request that exceeds maximal length,
	   or that is shorter than PCP_MIN_LEN (=24)
	   or that is not the multiple of 4 */
	if (req_size < 3)
		return 0; /* ignore msg */

	if (req_size < PCP_MIN_LEN) {
		pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
		return 1; /* send response */
	}

	if ( (req_size > PCP_MAX_LEN) || ( (req_size & 3) != 0)) {
		syslog(LOG_ERR, "PCP: Size of PCP packet(%d) is larger than %d bytes or "
		       "the size is not multiple of 4.\n", req_size, PCP_MAX_LEN);
		pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
		return 1; /* send response */
	}

	/* first parse request header */
	if (parseCommonRequestHeader(req, pcp_msg_info) ) {
		return 1;
	}

	remainingSize -= PCP_COMMON_REQUEST_SIZE;
	req += PCP_COMMON_REQUEST_SIZE;

	if (pcp_msg_info->version == 1) {
		/* legacy PCP version 1 support */
		switch (pcp_msg_info->opcode) {
		case PCP_OPCODE_MAP:

			remainingSize -= PCP_MAP_V1_SIZE;
			if (remainingSize < 0) {
				pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
				return pcp_msg_info->result_code;
			}

#ifdef DEBUG
			printMAPOpcodeVersion1(req);
#endif /* DEBUG */
			parsePCPMAP_version1(req, pcp_msg_info);

			req += PCP_MAP_V1_SIZE;

			parsePCPOptions(req, remainingSize, pcp_msg_info);
			if (ValidatePCPMsg(pcp_msg_info)) {
				if (pcp_msg_info->lifetime == 0) {
					DeletePCPMap(pcp_msg_info);
				} else {
					CreatePCPMap(pcp_msg_info);
				}
			} else {
				syslog(LOG_ERR, "PCP: Invalid PCP v1 MAP message.");
				return pcp_msg_info->result_code;
			}
			break;

#ifdef PCP_PEER
		case PCP_OPCODE_PEER:

			remainingSize -= PCP_PEER_V1_SIZE;
			if (remainingSize < 0) {
				pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
				return pcp_msg_info->result_code;
			}

#ifdef DEBUG
			printPEEROpcodeVersion1(req);
#endif /* DEBUG */
			parsePCPPEER_version1(req, pcp_msg_info);

			req += PCP_PEER_V1_SIZE;

			parsePCPOptions(req, remainingSize, pcp_msg_info);

			if (ValidatePCPMsg(pcp_msg_info)) {
				if (pcp_msg_info->lifetime == 0) {
					DeletePCPPeer(pcp_msg_info);
				} else {
					CreatePCPPeer(pcp_msg_info);
				}
			} else {
				syslog(LOG_ERR, "PCP: Invalid PCP v1 PEER message.");
				 return pcp_msg_info->result_code;
			}


			break;
#endif /* PCP_PEER */
		default:
			pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPCODE;
			break;
		}

	} else if (pcp_msg_info->version == 2) {
		/* RFC 6887 PCP support
		 * http://tools.ietf.org/html/rfc6887 */
		switch (pcp_msg_info->opcode) {
		case PCP_OPCODE_ANNOUNCE:
			/* should check PCP Client's IP Address in request */
			/* see http://tools.ietf.org/html/rfc6887#section-14.1 */
			break;
		case PCP_OPCODE_MAP:

			remainingSize -= PCP_MAP_V2_SIZE;
			if (remainingSize < 0) {
				pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
				return pcp_msg_info->result_code;
			}

#ifdef DEBUG
			printMAPOpcodeVersion2(req);
#endif /* DEBUG */
			parsePCPMAP_version2(req, pcp_msg_info);
			req += PCP_MAP_V2_SIZE;

			parsePCPOptions(req, remainingSize, pcp_msg_info);

			if (ValidatePCPMsg(pcp_msg_info)) {
				if (pcp_msg_info->lifetime == 0) {
					DeletePCPMap(pcp_msg_info);
				} else {
					CreatePCPMap(pcp_msg_info);
				}
			} else {
				syslog(LOG_ERR, "PCP: Invalid PCP v2 MAP message.");
				return pcp_msg_info->result_code;
			}


			break;

#ifdef PCP_PEER
		case PCP_OPCODE_PEER:

			remainingSize -= PCP_PEER_V2_SIZE;
			if (remainingSize < 0) {
				pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
				return pcp_msg_info->result_code;
			}

#ifdef DEBUG
			printPEEROpcodeVersion2(req);
#endif /* DEBUG */
			parsePCPPEER_version2(req, pcp_msg_info);
			req += PCP_PEER_V2_SIZE;

			if (pcp_msg_info->result_code != 0) {
				return pcp_msg_info->result_code;
			}

			parsePCPOptions(req, remainingSize, pcp_msg_info);

			if (ValidatePCPMsg(pcp_msg_info)) {
				if (pcp_msg_info->lifetime == 0) {
					DeletePCPPeer(pcp_msg_info);
				} else {
					CreatePCPPeer(pcp_msg_info);
				}
			} else {
				syslog(LOG_ERR, "PCP: Invalid PCP v2 PEER message.");
			}

		break;
#endif /* PCP_PEER */

#ifdef PCP_SADSCP
		case PCP_OPCODE_SADSCP:
			remainingSize -= PCP_SADSCP_REQ_SIZE;
			if (remainingSize < 0) {
				pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
				return pcp_msg_info->result_code;
			}

			remainingSize -= ((uint8_t *)req)[13];	/* app_name_length */
			if (remainingSize < 0) {
				pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
				return pcp_msg_info->result_code;
			}

#ifdef DEBUG
			printSADSCPOpcode(req);
#endif
			parseSADSCP(req, pcp_msg_info);
			req += PCP_SADSCP_REQ_SIZE;
			if (pcp_msg_info->result_code != 0) {
				return pcp_msg_info->result_code;
			}
			req += pcp_msg_info->app_name_len;

			get_dscp_value(pcp_msg_info);


			break;
#endif
		default:
			pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPCODE;
			break;
		}
	} else {
		pcp_msg_info->result_code = PCP_ERR_UNSUPP_VERSION;
		return pcp_msg_info->result_code;
	}
	return 1;
}


static void createPCPResponse(unsigned char *response, const pcp_info_t *pcp_msg_info)
{
	response[2] = 0;	/* reserved */
	memset(response + 12, 0, 12);	/* reserved */
	if (pcp_msg_info->result_code == PCP_ERR_UNSUPP_VERSION ) {
		/* highest supported version */
		response[0] = this_server_info.server_version;
	} else {
		response[0] = pcp_msg_info->version;
	}

	response[1] = pcp_msg_info->opcode | 0x80;	/* r_opcode */
	response[3] = pcp_msg_info->result_code;
	if(epoch_origin == 0) {
		epoch_origin = startup_time;
	}
	WRITENU32(response + 8, upnp_time() - epoch_origin); /* epochtime */
	switch (pcp_msg_info->result_code) {
	/*long lifetime errors*/
	case PCP_ERR_UNSUPP_VERSION:
	case PCP_ERR_NOT_AUTHORIZED:
	case PCP_ERR_MALFORMED_REQUEST:
	case PCP_ERR_UNSUPP_OPCODE:
	case PCP_ERR_UNSUPP_OPTION:
	case PCP_ERR_MALFORMED_OPTION:
	case PCP_ERR_UNSUPP_PROTOCOL:
	case PCP_ERR_ADDRESS_MISMATCH:
	case PCP_ERR_CANNOT_PROVIDE_EXTERNAL:
	case PCP_ERR_EXCESSIVE_REMOTE_PEERS:
		WRITENU32(response + 4, 0);	/* lifetime */
		break;

	case PCP_ERR_NETWORK_FAILURE:
	case PCP_ERR_NO_RESOURCES:
	case PCP_ERR_USER_EX_QUOTA:
		WRITENU32(response + 4, 30);	/* lifetime */
		break;

	case PCP_SUCCESS:
	default:
		WRITENU32(response + 4, pcp_msg_info->lifetime);	/* lifetime */
		break;
	}

	if (response[1] == 0x81) { /* MAP response */
		if (response[0] == 1) {	/* version */
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 4, pcp_msg_info->int_port);
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 6, pcp_msg_info->ext_port);
			copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 8,
			                    pcp_msg_info->ext_ip);
		}
		else if (response[0] == 2) {
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 16, pcp_msg_info->int_port);
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 18, pcp_msg_info->ext_port);
			copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 20,
			                    pcp_msg_info->ext_ip);
		}
	}
#ifdef PCP_PEER
	else if (response[1] == 0x82) { /* PEER response */
		if (response[0] == 1) {
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 4, pcp_msg_info->int_port);
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 6, pcp_msg_info->ext_port);
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 24, pcp_msg_info->peer_port);
			copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 8,
			                    pcp_msg_info->ext_ip);
		}
		else if (response[0] == 2) {
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 16, pcp_msg_info->int_port);
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 18, pcp_msg_info->ext_port);
			WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 36, pcp_msg_info->peer_port);
			copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 20,
			                    pcp_msg_info->ext_ip);
		}
	}
#endif /* PCP_PEER */

#ifdef PCP_SADSCP
	else if (response[1] == 0x83) { /*SADSCP response*/
		response[PCP_COMMON_RESPONSE_SIZE + 12]
			= ((pcp_msg_info->matched_name<<7) & ~(1<<6)) |
			  (pcp_msg_info->sadscp_dscp & PCP_SADSCP_MASK);
		memset(response + PCP_COMMON_RESPONSE_SIZE + 13, 0, 3);
	}
#endif /* PCP_SADSCP */
}

int ProcessIncomingPCPPacket(int s, unsigned char *buff, int len,
                             const struct sockaddr *senderaddr,
                             const struct sockaddr_in6 *receiveraddr)
{
	pcp_info_t pcp_msg_info;
	struct lan_addr_s * lan_addr;
	char addr_str[64];

	memset(&pcp_msg_info, 0, sizeof(pcp_info_t));

	if(senderaddr->sa_family == AF_INET) {
		const struct sockaddr_in * senderaddr_v4 =
			(const struct sockaddr_in *)senderaddr;
		pcp_msg_info.sender_ip.s6_addr[11] = 0xff;
		pcp_msg_info.sender_ip.s6_addr[10] = 0xff;
		memcpy(pcp_msg_info.sender_ip.s6_addr+12,
		       &senderaddr_v4->sin_addr, 4);
	} else if(senderaddr->sa_family == AF_INET6) {
		const struct sockaddr_in6 * senderaddr_v6 =
			(const struct sockaddr_in6 *)senderaddr;
		pcp_msg_info.sender_ip = senderaddr_v6->sin6_addr;
	} else {
		syslog(LOG_WARNING, "unknown PCP packet sender address family %d",
		       senderaddr->sa_family);
		return 0;
	}

	if(sockaddr_to_string(senderaddr, addr_str, sizeof(addr_str)))
		syslog(LOG_DEBUG, "PCP request received from %s %dbytes",
		       addr_str, len);

	if(buff[1] & 128) {
		/* discarding PCP responses silently */
		return 0;
	}

	/* If we're in allow third party-mode, we probably don't care
	 * about locality either. Let's hope firewall is ok. */
	if (!GETFLAG(PCP_ALLOWTHIRDPARTYMASK)) {
		lan_addr = get_lan_for_peer(senderaddr);
		if(lan_addr == NULL) {
			syslog(LOG_WARNING, "PCP packet sender %s not from a LAN, ignoring",
			       addr_str);
			return 0;
		}
	}

	if (processPCPRequest(buff, len, &pcp_msg_info) ) {

		createPCPResponse(buff, &pcp_msg_info);

		if(len < PCP_MIN_LEN)
			len = PCP_MIN_LEN;
		else
			len = (len + 3) & ~3;	/* round up resp. length to multiple of 4 */
		len = sendto_or_schedule2(s, buff, len, 0, senderaddr,
		           (senderaddr->sa_family == AF_INET) ?
		                  sizeof(struct sockaddr_in) :
		                  sizeof(struct sockaddr_in6),
		           receiveraddr);
		if( len < 0 ) {
			syslog(LOG_ERR, "sendto(pcpserver): %m");
		}
	}

	return 0;
}

#ifdef ENABLE_IPV6
int OpenAndConfPCPv6Socket(void)
{
	int s;
	int i = 1;
	struct sockaddr_in6 addr;
	s = socket(PF_INET6, SOCK_DGRAM, 0/*IPPROTO_UDP*/);
	if(s < 0) {
		syslog(LOG_ERR, "%s: socket(): %m", "OpenAndConfPCPv6Socket");
		return -1;
	}
	if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) {
		syslog(LOG_WARNING, "%s: setsockopt(SO_REUSEADDR): %m",
		       "OpenAndConfPCPv6Socket");
	}
#ifdef IPV6_V6ONLY
	/* force IPV6 only for IPV6 socket.
	 * see http://www.ietf.org/rfc/rfc3493.txt section 5.3 */
	if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &i, sizeof(i)) < 0) {
		syslog(LOG_WARNING, "%s: setsockopt(IPV6_V6ONLY): %m",
		       "OpenAndConfPCPv6Socket");
	}
#endif
#ifdef IPV6_RECVPKTINFO
	/* see RFC3542 */
	if(setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &i, sizeof(i)) < 0) {
		syslog(LOG_WARNING, "%s: setsockopt(IPV6_RECVPKTINFO): %m",
		       "OpenAndConfPCPv6Socket");
	}
#endif
	if(!set_non_blocking(s)) {
		syslog(LOG_WARNING, "%s: set_non_blocking(): %m",
		       "OpenAndConfPCPv6Socket");
	}
	memset(&addr, 0, sizeof(addr));
	addr.sin6_family = AF_INET6;
	addr.sin6_port = htons(NATPMP_PORT);
	addr.sin6_addr = ipv6_bind_addr;
	if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		syslog(LOG_ERR, "%s: bind(): %m", "OpenAndConfPCPv6Socket");
		close(s);
		return -1;
	}
	return s;
}
#endif /*ENABLE_IPV6*/

#ifdef ENABLE_IPV6
void PCPSendUnsolicitedAnnounce(int * sockets, int n_sockets, int socket6)
#else /* IPv4 only */
void PCPSendUnsolicitedAnnounce(int * sockets, int n_sockets)
#endif
{
	int i;
	unsigned char buff[PCP_MIN_LEN];
	pcp_info_t info;
	ssize_t len;
	struct sockaddr_in addr;
#ifdef ENABLE_IPV6
	struct sockaddr_in6 addr6;
#endif /* ENABLE_IPV6 */
	/* this is an Unsolicited ANNOUNCE response */

	info.version = this_server_info.server_version;
	info.opcode = PCP_OPCODE_ANNOUNCE;
	info.result_code = PCP_SUCCESS;
	info.lifetime = 0;
	createPCPResponse(buff, &info);
	/* Multicast PCP restart announcements are sent to
	 * 224.0.0.1:5350 and/or [ff02::1]:5350 */
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr("224.0.0.1");
	addr.sin_port = htons(5350);
	for(i = 0; i < n_sockets; i++) {
		len = sendto_or_schedule(sockets[i], buff, PCP_MIN_LEN, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
		if( len < 0 ) {
			syslog(LOG_ERR, "PCPSendUnsolicitedAnnounce() sendto(): %m");
		}
	}
#ifdef ENABLE_IPV6
	memset(&addr6, 0, sizeof(struct sockaddr_in6));
	addr6.sin6_family = AF_INET6;
	inet_pton(AF_INET6, "FF02::1", &(addr6.sin6_addr));
	addr6.sin6_port = htons(5350);
	len = sendto_or_schedule(socket6, buff, PCP_MIN_LEN, 0, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6));
	if( len < 0 ) {
		syslog(LOG_ERR, "PCPSendUnsolicitedAnnounce() IPv6 sendto(): %m");
	}
#endif /* ENABLE_IPV6 */
}

#ifdef ENABLE_IPV6
void PCPPublicAddressChanged(int * sockets, int n_sockets, int socket6)
#else /* IPv4 only */
void PCPPublicAddressChanged(int * sockets, int n_sockets)
#endif
{
	/* according to RFC 6887  8.5 :
	 *   if the external IP address(es) of the NAT (controlled by
	 *   the PCP server) changes, the Epoch time MUST be reset. */
	epoch_origin = upnp_time();
#ifdef ENABLE_IPV6
	PCPSendUnsolicitedAnnounce(sockets, n_sockets, socket6);
#else /* IPv4 Only */
	PCPSendUnsolicitedAnnounce(sockets, n_sockets);
#endif
}
#endif /*ENABLE_PCP*/
