miniupnp/miniupnpd/pcpserver.c

1509 lines
46 KiB
C

/* $Id: pcpserver.c,v 1.26 2014/03/24 13:08:52 nanard Exp $ */
/* 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.
*/
#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 PCP_PEER
/* TODO make this platform independent */
#include "netfilter/iptcrdr.h"
#endif
/* server specific information */
struct pcp_server_info {
uint8_t server_version;
};
/* default server settings, highest version supported is the default */
static 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;
int pfailure_present;
char senderaddrstr[INET_ADDRSTRLEN]; /* only if IPv4 sender */
} pcp_info_t;
#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) &&
(!strncmp( dscp_values_list[ind].app_name,
pcp_msg_info->app_name, pcp_msg_info->app_name_len)) &&
(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 pcp_request_t *common_req, pcp_info_t *pcp_msg_info)
{
pcp_msg_info->version = common_req->ver ;
pcp_msg_info->opcode = common_req->r_opcode & 0x7f;
pcp_msg_info->lifetime = ntohl(common_req->req_lifetime);
pcp_msg_info->int_ip = &common_req->ip;
if ( (common_req->ver > 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 pcp_map_v1_t *map_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",map_buf->protocol );
syslog(LOG_DEBUG, "MAP int port: \t\t %d\n", ntohs(map_buf->int_port) );
syslog(LOG_DEBUG, "MAP ext port: \t\t %d\n", ntohs(map_buf->ext_port) );
syslog(LOG_DEBUG, "MAP Ext IP: \t\t %s\n", inet_ntop(AF_INET6,
&map_buf->ext_ip, map_addr, INET6_ADDRSTRLEN));
}
static void printMAPOpcodeVersion2(const pcp_map_v2_t *map_buf)
{
char map_addr[INET6_ADDRSTRLEN];
syslog(LOG_DEBUG, "PCP MAP: v2 Opcode specific information.");
syslog(LOG_DEBUG, "MAP nonce: \t%08x%08x%08x",
map_buf->nonce[0], map_buf->nonce[1], map_buf->nonce[2]);
syslog(LOG_DEBUG, "MAP protocol:\t%d", map_buf->protocol);
syslog(LOG_DEBUG, "MAP int port:\t%d", ntohs(map_buf->int_port));
syslog(LOG_DEBUG, "MAP ext port:\t%d", ntohs(map_buf->ext_port));
syslog(LOG_DEBUG, "MAP Ext IP: \t%s", inet_ntop(AF_INET6,
&map_buf->ext_ip, map_addr, INET6_ADDRSTRLEN));
}
#endif /* DEBUG */
static int parsePCPMAP_version1(const pcp_map_v1_t *map_v1,
pcp_info_t *pcp_msg_info)
{
pcp_msg_info->is_map_op = 1;
pcp_msg_info->protocol = map_v1->protocol;
pcp_msg_info->int_port = ntohs(map_v1->int_port);
pcp_msg_info->ext_port = ntohs(map_v1->ext_port);
pcp_msg_info->ext_ip = &(map_v1->ext_ip);
if (pcp_msg_info->protocol == 0 && pcp_msg_info->int_port !=0 ){
syslog(LOG_ERR, "PCP MAP: Protocol was ZERO, but internal port has non-ZERO value.");
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return 1;
}
return 0;
}
static int parsePCPMAP_version2(const pcp_map_v2_t *map_v2,
pcp_info_t *pcp_msg_info)
{
pcp_msg_info->is_map_op = 1;
memcpy(pcp_msg_info->nonce, map_v2->nonce, 12);
pcp_msg_info->protocol = map_v2->protocol;
pcp_msg_info->int_port = ntohs(map_v2->int_port);
pcp_msg_info->ext_port = ntohs(map_v2->ext_port);
pcp_msg_info->ext_ip = &(map_v2->ext_ip);
if (pcp_msg_info->protocol == 0 && pcp_msg_info->int_port !=0 ) {
syslog(LOG_ERR, "PCP MAP: Protocol was ZERO, but internal port has non-ZERO value.");
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return PCP_ERR_MALFORMED_REQUEST;
}
return 0;
}
#ifdef PCP_PEER
#ifdef DEBUG
static void printPEEROpcodeVersion1(pcp_peer_v1_t *peer_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",peer_buf->protocol );
syslog(LOG_DEBUG, "Internal port: \t\t %d\n", ntohs(peer_buf->int_port) );
syslog(LOG_DEBUG, "External IP: \t\t %s\n", inet_ntop(AF_INET6, &peer_buf->ext_ip,
ext_addr,INET6_ADDRSTRLEN));
syslog(LOG_DEBUG, "External port port: \t\t %d\n", ntohs(peer_buf->ext_port) );
syslog(LOG_DEBUG, "PEER IP: \t\t %s\n", inet_ntop(AF_INET6, &peer_buf->peer_ip,
peer_addr,INET6_ADDRSTRLEN));
syslog(LOG_DEBUG, "PEER port port: \t\t %d\n", ntohs(peer_buf->peer_port) );
}
static void printPEEROpcodeVersion2(pcp_peer_v2_t *peer_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",
map_buf->nonce[0], map_buf->nonce[1], map_buf->nonce[2]);
syslog(LOG_DEBUG, "Protocol: \t%d",peer_buf->protocol );
syslog(LOG_DEBUG, "Internal port:\t%d", ntohs(peer_buf->int_port) );
syslog(LOG_DEBUG, "External IP: \t%s", inet_ntop(AF_INET6, &peer_buf->ext_ip,
ext_addr,INET6_ADDRSTRLEN));
syslog(LOG_DEBUG, "External port:\t%d", ntohs(peer_buf->ext_port) );
syslog(LOG_DEBUG, "PEER IP: \t%s", inet_ntop(AF_INET6, &peer_buf->peer_ip,
peer_addr,INET6_ADDRSTRLEN));
syslog(LOG_DEBUG, "PEER port: \t%d", ntohs(peer_buf->peer_port) );
}
#endif /* DEBUG */
/*
* Function extracting information from peer_buf to pcp_msg_info
* @return : when no problem occurred 0 is returned, 1 otherwise
*/
static int parsePCPPEER_version1(pcp_peer_v1_t *peer_buf, \
pcp_info_t *pcp_msg_info)
{
pcp_msg_info->is_peer_op = 1;
pcp_msg_info->protocol = peer_buf->protocol;
pcp_msg_info->int_port = ntohs(peer_buf->int_port);
pcp_msg_info->ext_port = ntohs(peer_buf->ext_port);
pcp_msg_info->peer_port = ntohs(peer_buf->peer_port);
pcp_msg_info->ext_ip = &peer_buf->ext_ip;
pcp_msg_info->peer_ip = &peer_buf->peer_ip;
if (pcp_msg_info->protocol == 0 && pcp_msg_info->int_port !=0 ){
syslog(LOG_ERR, "PCP PEER: protocol was ZERO, but internal port has non-ZERO value.");
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return 1;
}
return 0;
}
/*
* Function extracting information from peer_buf to pcp_msg_info
* @return : when no problem occurred 0 is returned, 1 otherwise
*/
static int parsePCPPEER_version2(pcp_peer_v2_t *peer_buf, \
pcp_info_t *pcp_msg_info)
{
pcp_msg_info->is_peer_op = 1;
memcpy(pcp_msg_info->nonce, peer_buf->nonce, 12);
pcp_msg_info->protocol = peer_buf->protocol;
pcp_msg_info->int_port = ntohs(peer_buf->int_port);
pcp_msg_info->ext_port = ntohs(peer_buf->ext_port);
pcp_msg_info->peer_port = ntohs(peer_buf->peer_port);
pcp_msg_info->ext_ip = &peer_buf->ext_ip;
pcp_msg_info->peer_ip = &peer_buf->peer_ip;
if (pcp_msg_info->protocol == 0 && pcp_msg_info->int_port !=0 ){
syslog(LOG_ERR, "PCP PEER: protocol was ZERO, but internal port has non-ZERO value.");
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return 1;
}
return 0;
}
#endif /* PCP_PEER */
#ifdef PCP_SADSCP
#ifdef DEBUG
static void printSADSCPOpcode(pcp_sadscp_req_t *sadscp) {
unsigned char sadscp_tol;
sadscp_tol = sadscp->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", sadscp->app_name_length);
if (sadscp->app_name) {
syslog(LOG_DEBUG, "Application name %.*s \n", sadscp->app_name_length,
sadscp->app_name);
}
}
#endif //DEBUG
static int parseSADSCP(pcp_sadscp_req_t *sadscp, pcp_info_t *pcp_msg_info) {
pcp_msg_info->delay_tolerance = (sadscp->tolerance_fields>>6)&3;
pcp_msg_info->loss_tolerance = (sadscp->tolerance_fields>>4)&3;
pcp_msg_info->jitter_tolerance = (sadscp->tolerance_fields>>2)&3;
if (pcp_msg_info->delay_tolerance == 3 ) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return 1;
}
if ( pcp_msg_info->loss_tolerance == 3 ) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return 1;
}
if ( pcp_msg_info->jitter_tolerance == 3 ) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return 1;
}
pcp_msg_info->app_name = sadscp->app_name;
pcp_msg_info->app_name_len = sadscp->app_name_length;
return 0;
}
#endif
static int parsePCPOptions(void* pcp_buf, int* remainingSize,
int* processedSize, pcp_info_t *pcp_msg_info)
{
int remain = *remainingSize;
int processed = *processedSize;
#ifdef DEBUG
char third_addr[INET6_ADDRSTRLEN];
#endif
unsigned short option_length;
pcp_3rd_party_option_t* opt_3rd;
#ifdef PCP_FLOWP
pcp_flow_priority_option_t* opt_flp;
#endif
pcp_filter_option_t* opt_filter;
pcp_prefer_fail_option_t* opt_prefail;
pcp_options_hdr_t* opt_hdr;
opt_hdr = (pcp_options_hdr_t*)(pcp_buf + processed);
option_length = 0;
switch (opt_hdr->code){
case PCP_OPTION_3RD_PARTY:
opt_3rd = (pcp_3rd_party_option_t*) (pcp_buf + processed);
option_length = ntohs(opt_3rd->len);
if (option_length != (sizeof(pcp_3rd_party_option_t) - sizeof(pcp_options_hdr_t)) ||
(int)sizeof(pcp_3rd_party_option_t) > remain) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
remain = 0;
break;
}
#ifdef DEBUG
syslog(LOG_DEBUG, "PCP OPTION: \t Third party \n");
syslog(LOG_DEBUG, "Third PARTY IP: \t %s\n", inet_ntop(AF_INET6,
&(opt_3rd->ip), 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;
}
else {
pcp_msg_info->thirdp_ip = &opt_3rd -> ip;
}
processed += sizeof(pcp_3rd_party_option_t);
remain -= sizeof(pcp_3rd_party_option_t);
break;
case PCP_OPTION_PREF_FAIL:
opt_prefail = (pcp_prefer_fail_option_t*)(pcp_buf+processed);
option_length = ntohs(opt_prefail->len);
if ( option_length != ( sizeof(pcp_prefer_fail_option_t) - sizeof(pcp_options_hdr_t)) ||
(int)sizeof(pcp_prefer_fail_option_t) > remain) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
remain = 0;
break;
}
#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;
processed += sizeof(pcp_prefer_fail_option_t);
remain -= sizeof(pcp_prefer_fail_option_t);
}
break;
case PCP_OPTION_FILTER:
/* TODO fully implement filter */
opt_filter = (pcp_filter_option_t*) (pcp_buf + processed);
option_length = ntohs(opt_filter->len);
if ( option_length != ( sizeof(pcp_filter_option_t) - sizeof(pcp_options_hdr_t)) ||
(int)sizeof(pcp_filter_option_t) > remain) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
remain = 0;
break;
}
#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;
}
processed += sizeof(pcp_filter_option_t);
remain -= sizeof(pcp_filter_option_t);
break;
#ifdef PCP_FLOWP
case PCP_OPTION_FLOW_PRIORITY:
#ifdef DEBUG
syslog(LOG_DEBUG, "PCP OPTION: \t Flow priority\n");
#endif
opt_flp = (pcp_flow_priority_option_t*) (pcp_buf + processed);
option_length = ntohs(opt_flp->len);
if ( option_length != ( sizeof(pcp_flow_priority_option_t) - sizeof(pcp_options_hdr_t)) ||
((int)sizeof(pcp_flow_priority_option_t) > remain) ) {
syslog(LOG_ERR, "PCP: Error processing DSCP. sizeof %d and remaining %d . flow len %d \n",
(int)sizeof(pcp_flow_priority_option_t), remain, opt_flp->len);
pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
remain = 0;
break;
}
#ifdef DEBUG
syslog(LOG_DEBUG, "DSCP UP: \t %d \n", opt_flp->dscp_up);
syslog(LOG_DEBUG, "DSCP DOWN: \t %d \n", opt_flp->dscp_down);
#endif
pcp_msg_info->dscp_up = opt_flp->dscp_up;
pcp_msg_info->dscp_down = opt_flp->dscp_down;
pcp_msg_info->flowp_present = 1;
processed += sizeof(pcp_flow_priority_option_t);
remain -= sizeof(pcp_flow_priority_option_t);
break;
#endif
default:
syslog(LOG_ERR, "PCP: Unrecognized PCP OPTION: %d \n", opt_hdr->code);
remain = 0;
break;
}
/* shift processed and remaining values to new values */
*remainingSize = remain;
*processedSize = processed;
return pcp_msg_info->result_code;
}
/* 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;
/* TODO : 1) be able to handle case with multiple external addresses
* 2) handle correctly both IPv4 and IPv6 */
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;
}
} else {
if(!ext_if_name || ext_if_name[0]=='\0') {
pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE;
return -1;
}
/* how do we know which address we need ? IPv6 or IPv4 ? */
if(getifaddr_in6(ext_if_name, &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;
}
#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 const char* inet_n46top(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);
}
}
static int CreatePCPPeer(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 */
FillSA((struct sockaddr*)&intip, pcp_msg_info->int_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);
/* 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 {
pcp_msg_info->result_code = PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
return 0;
}
}
/* Create Peer Mapping */
{
char desc[64];
char proto_str[8];
char peerip_s[INET_ADDRSTRLEN], extip_s[INET_ADDRSTRLEN];
time_t timestamp = time(NULL) + pcp_msg_info->lifetime;
if (eport == 0) {
eport = pcp_msg_info->int_port;
}
switch(proto) {
case IPPROTO_TCP:
snprintf(proto_str, sizeof(proto_str), "TCP");
break;
case IPPROTO_UDP:
snprintf(proto_str, sizeof(proto_str), "UDP");
break;
default:
snprintf(proto_str, sizeof(proto_str), "%d", proto);
}
snprintf(desc, sizeof(desc), "PCP %hu %s %08x%08x%08x",
eport, proto_str,
pcp_msg_info->nonce[0], pcp_msg_info->nonce[1], pcp_msg_info->nonce[2]);
inet_satop((struct sockaddr*)&peerip, peerip_s, sizeof(peerip_s));
inet_satop((struct sockaddr*)&extip, extip_s, sizeof(extip_s));
#ifdef PCP_FLOWP
if (pcp_msg_info->flowp_present && pcp_msg_info->dscp_up) {
if (add_peer_dscp_rule2(ext_if_name, peerip_s,
pcp_msg_info->peer_port, pcp_msg_info->dscp_up,
pcp_msg_info->senderaddrstr, pcp_msg_info->int_port,
proto, desc, timestamp) < 0 ) {
syslog(LOG_ERR, "PCP: failed to add flowp upstream mapping %s %s:%hu->%s:%hu '%s'",
proto_str,
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
peerip_s,
pcp_msg_info->peer_port,
desc);
pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
}
}
if (pcp_msg_info->flowp_present && pcp_msg_info->dscp_down) {
if (add_peer_dscp_rule2(ext_if_name, pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port, pcp_msg_info->dscp_down,
peerip_s, pcp_msg_info->peer_port, proto, desc, timestamp)
< 0 ) {
syslog(LOG_ERR, "PCP: failed to add flowp downstream mapping %s %s:%hu->%s:%hu '%s'",
proto_str,
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
peerip_s,
pcp_msg_info->peer_port,
desc);
pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
}
}
#endif
/* TODO: add upnp function for PI */
if (add_peer_redirect_rule2(ext_if_name,
peerip_s,
pcp_msg_info->peer_port,
extip_s,
eport,
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
pcp_msg_info->protocol,
desc,
timestamp) < 0 ) {
syslog(LOG_ERR, "PCP PEER: failed to add peer mapping %s %s:%hu(%hu)->%s:%hu '%s'",
(pcp_msg_info->protocol==IPPROTO_TCP)?"TCP":"UDP",
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
eport,
peerip_s,
pcp_msg_info->peer_port,
desc);
pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
return 0;
} else {
pcp_msg_info->ext_port = eport;
syslog(LOG_INFO, "PCP PEER: added mapping %s %s:%hu(%hu)->%s:%hu '%s'",
(pcp_msg_info->protocol==IPPROTO_TCP)?"TCP":"UDP",
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
eport,
peerip_s,
pcp_msg_info->peer_port,
desc);
}
}
return 1;
}
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;
inet_n46top((struct in6_addr*)pcp_msg_info->peer_ip, rhost, sizeof(rhost));
while(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 == strncmp(iaddr2, pcp_msg_info->senderaddrstr, sizeof(iaddr2)))
&& (0 == strncmp(rhost2, rhost, sizeof(rhost2)))
&& (proto2==proto)
&& (0 == strncmp(desc, "PCP", sizeof("PCP")-1))
&& (iport2==iport) && (rport2==rport)) {
r = _upnp_delete_redir(eport2, proto2);
if(r<0) {
syslog(LOG_ERR, "PCP PEER: failed to remove peer mapping");
index++;
} else {
syslog(LOG_INFO, "PCP PEER: %s port %hu peer mapping removed",
proto2==IPPROTO_TCP?"TCP":"UDP", eport2);
}
} else {
index++;
}
}
if (r==-1) {
syslog(LOG_ERR, "PCP PEER: 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;
}
}
#endif /* PCP_PEER */
/* 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 scenarios than just NAT44 */
static void CreatePCPMap(pcp_info_t *pcp_msg_info)
{
char desc[64];
char proto_str[8];
char iaddr_old[INET_ADDRSTRLEN];
uint16_t iport_old;
uint16_t eport_first = 0;
int any_eport_allowed = 0;
unsigned int timestamp;
int r=0;
if (pcp_msg_info->ext_port == 0) {
pcp_msg_info->ext_port = pcp_msg_info->int_port;
}
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 */
if (any_eport_allowed == 0) { /* all eports rejected by permissions */
pcp_msg_info->result_code = PCP_ERR_NOT_AUTHORIZED;
} else { /* at least one eport allowed (but none available) */
pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
}
return;
}
if ((IN6_IS_ADDR_V4MAPPED(pcp_msg_info->int_ip) &&
(!check_upnp_rule_against_permissions(upnppermlist,
num_upnpperm, pcp_msg_info->ext_port,
((struct in_addr*)pcp_msg_info->int_ip->s6_addr)[3],
pcp_msg_info->int_port)))) {
if (pcp_msg_info->pfailure_present) {
pcp_msg_info->result_code = PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
return;
}
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->senderaddrstr, 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,
&timestamp, 0, 0);
if(r==0) {
if((strncmp(pcp_msg_info->senderaddrstr, iaddr_old,
sizeof(iaddr_old))!=0)
|| (pcp_msg_info->int_port != iport_old)) {
/* redirection already existing */
if (pcp_msg_info->pfailure_present) {
pcp_msg_info->result_code = PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
return;
}
} 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) {
pcp_msg_info->result_code = PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
return;
}
}
pcp_msg_info->ext_port++;
if (pcp_msg_info->ext_port == 0) { /* skip port zero */
pcp_msg_info->ext_port++;
}
}
} while (r==0);
timestamp = time(NULL) + pcp_msg_info->lifetime;
switch(pcp_msg_info->protocol) {
case IPPROTO_TCP:
snprintf(proto_str, sizeof(proto_str), "TCP");
break;
case IPPROTO_UDP:
snprintf(proto_str, sizeof(proto_str), "UDP");
break;
default:
snprintf(proto_str, sizeof(proto_str), "%d", pcp_msg_info->protocol);
}
snprintf(desc, sizeof(desc), "PCP %hu %s %08x%08x%08x",
pcp_msg_info->ext_port,
proto_str,
pcp_msg_info->nonce[0], pcp_msg_info->nonce[1], pcp_msg_info->nonce[2]);
if(upnp_redirect_internal(NULL,
pcp_msg_info->ext_port,
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
pcp_msg_info->protocol,
desc,
timestamp) < 0) {
syslog(LOG_ERR, "PCP MAP: Failed to add mapping %s %hu->%s:%hu '%s'",
proto_str,
pcp_msg_info->ext_port,
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
desc);
pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
} else {
syslog(LOG_INFO, "PCP MAP: added mapping %s %hu->%s:%hu '%s'",
proto_str,
pcp_msg_info->ext_port,
pcp_msg_info->senderaddrstr,
pcp_msg_info->int_port,
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 = 0;
unsigned short eport2, iport2;
char iaddr2[16];
int proto2;
char desc[64];
unsigned int timestamp;
/* iterate through all rules and delete the requested ones */
while(get_redirect_rule_by_index(index, 0,
&eport2, iaddr2, sizeof(iaddr2),
&iport2, &proto2,
desc, sizeof(desc),
0, 0, &timestamp, 0, 0) >= 0) {
if(0 == strncmp(iaddr2, pcp_msg_info->senderaddrstr, sizeof(iaddr2))
&& (proto2==proto)
&& (0 == strncmp(desc, "PCP", 3)) /* starts with PCP */
&& ((iport2==iport) || (iport==0))) {
r = _upnp_delete_redir(eport2, proto2);
if(r<0) {
syslog(LOG_ERR, "PCP: failed to remove port mapping");
index++;
} else {
syslog(LOG_INFO, "PCP: %s port %hu mapping removed",
proto2==IPPROTO_TCP?"TCP":"UDP", eport2);
}
} else {
index++;
}
}
if (r==-1) {
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;
}
/* protocol zero means 'all protocols' : internal port MUST be zero */
if (pcp_msg_info->protocol == 0 && pcp_msg_info->int_port != 0) {
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;
}
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;
int processedSize;
const pcp_map_v1_t* map_v1;
const pcp_map_v2_t* map_v2;
#ifdef PCP_PEER
pcp_peer_v1_t* peer_v1;
pcp_peer_v2_t* peer_v2;
#endif
#ifdef PCP_SADSCP
pcp_sadscp_req_t* sadscp;
#endif
/* 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;
processedSize = 0;
/* 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((pcp_request_t*)req, pcp_msg_info) ) {
return 1;
}
remainingSize -= sizeof(pcp_request_t);
processedSize += sizeof(pcp_request_t);
if (pcp_msg_info->version == 1) {
/* legacy PCP version 1 support */
switch (pcp_msg_info->opcode) {
case PCP_OPCODE_MAP:
remainingSize -= sizeof(pcp_map_v1_t);
if (remainingSize < 0) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return pcp_msg_info->result_code;
}
map_v1 = (pcp_map_v1_t*)(req + processedSize);
#ifdef DEBUG
printMAPOpcodeVersion1(map_v1);
#endif /* DEBUG */
if ( parsePCPMAP_version1(map_v1, pcp_msg_info) ) {
return pcp_msg_info->result_code;
}
processedSize += sizeof(pcp_map_v1_t);
while (remainingSize > 0) {
parsePCPOptions(req, &remainingSize, &processedSize, 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.");
}
break;
#ifdef PCP_PEER
case PCP_OPCODE_PEER:
remainingSize -= sizeof(pcp_peer_v1_t);
if (remainingSize < 0) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return pcp_msg_info->result_code;
}
peer_v1 = (pcp_peer_v1_t*)(req + processedSize);
#ifdef DEBUG
printPEEROpcodeVersion1(peer_v1);
#endif /* DEBUG */
if ( parsePCPPEER_version1(peer_v1, pcp_msg_info) ) {
return pcp_msg_info->result_code;
}
processedSize += sizeof(pcp_peer_v1_t);
while (remainingSize > 0) {
parsePCPOptions(req, &remainingSize, &processedSize, 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.");
}
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 -= sizeof(pcp_map_v2_t);
if (remainingSize < 0) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return pcp_msg_info->result_code;
}
map_v2 = (pcp_map_v2_t*)(req + processedSize);
#ifdef DEBUG
printMAPOpcodeVersion2(map_v2);
#endif /* DEBUG */
if (parsePCPMAP_version2(map_v2, pcp_msg_info) ) {
return pcp_msg_info->result_code;
}
processedSize += sizeof(pcp_map_v2_t);
while (remainingSize > 0) {
parsePCPOptions(req, &remainingSize, &processedSize, 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.");
}
break;
#ifdef PCP_PEER
case PCP_OPCODE_PEER:
remainingSize -= sizeof(pcp_peer_v2_t);
if (remainingSize < 0) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return pcp_msg_info->result_code;
}
peer_v2 = (pcp_peer_v2_t*)(req + processedSize);
#ifdef DEBUG
printPEEROpcodeVersion2(peer_v2);
#endif /* DEBUG */
parsePCPPEER_version2(peer_v2, pcp_msg_info);
processedSize += sizeof(pcp_peer_v2_t);
if (pcp_msg_info->result_code != 0) {
return pcp_msg_info->result_code;
}
while (remainingSize > 0) {
parsePCPOptions(req, &remainingSize, &processedSize, 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 -= sizeof(pcp_sadscp_req_t);
if (remainingSize < 0) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST;
return pcp_msg_info->result_code;
}
sadscp = (pcp_sadscp_req_t*)(req + processedSize);
if (sadscp->app_name_length > remainingSize) {
pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION;
}
#ifdef DEBUG
printSADSCPOpcode(sadscp);
#endif
if (parseSADSCP(sadscp, pcp_msg_info)) {
return pcp_msg_info->result_code;
}
get_dscp_value(pcp_msg_info);
processedSize += sizeof(pcp_sadscp_req_t);
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, pcp_info_t *pcp_msg_info)
{
pcp_response_t *resp = (pcp_response_t*)response;
resp->reserved = 0;
resp->reserved1[0]=0;
resp->reserved1[1]=0;
resp->reserved1[2]=0;
if (pcp_msg_info->result_code == PCP_ERR_UNSUPP_VERSION ) {
/* highest supported version */
resp->ver = this_server_info.server_version;
} else {
resp->ver = pcp_msg_info->version;
}
resp->r_opcode |= 0x80;
resp->result_code = pcp_msg_info->result_code;
resp->epochtime = htonl(time(NULL) - startup_time);
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:
resp->lifetime = 0;
break;
case PCP_ERR_NETWORK_FAILURE:
case PCP_ERR_NO_RESOURCES:
case PCP_ERR_USER_EX_QUOTA:
resp->lifetime = htonl(30);
break;
case PCP_SUCCESS:
default:
resp->lifetime = htonl(pcp_msg_info->lifetime);
break;
}
if (resp->r_opcode == 0x81) { /* MAP response */
if (resp->ver == 1 ) {
pcp_map_v1_t *mapr = (pcp_map_v1_t *)resp->next_data;
mapr->ext_ip = *pcp_msg_info->ext_ip;
mapr->ext_port = htons(pcp_msg_info->ext_port);
mapr->int_port = htons(pcp_msg_info->int_port);
}
else if (resp->ver == 2 ) {
pcp_map_v2_t *mapr = (pcp_map_v2_t *)resp->next_data;
mapr->ext_ip = *pcp_msg_info->ext_ip;
mapr->ext_port = htons(pcp_msg_info->ext_port);
mapr->int_port = htons(pcp_msg_info->int_port);
}
}
#ifdef PCP_PEER
else if (resp->r_opcode == 0x82) { /* PEER response */
if (resp->ver == 1 ){
pcp_peer_v1_t* peer_resp = (pcp_peer_v1_t*)resp->next_data;
peer_resp->ext_port = htons(pcp_msg_info->ext_port);
peer_resp->int_port = htons(pcp_msg_info->int_port);
peer_resp->peer_port = htons(pcp_msg_info->peer_port);
peer_resp->ext_ip = *pcp_msg_info->ext_ip;
}
else if (resp->ver == 2 ){
pcp_peer_v2_t* peer_resp = (pcp_peer_v2_t*)resp->next_data;
peer_resp->ext_port = htons(pcp_msg_info->ext_port);
peer_resp->int_port = htons(pcp_msg_info->int_port);
peer_resp->peer_port = htons(pcp_msg_info->peer_port);
peer_resp->ext_ip = *pcp_msg_info->ext_ip;
}
}
#endif /* PCP_PEER */
#ifdef PCP_SADSCP
else if (resp->r_opcode == 0x83) { /*SADSCP response*/
pcp_sadscp_resp_t *sadscp_resp = (pcp_sadscp_resp_t*)resp->next_data;
sadscp_resp->a_r_dscp_value = pcp_msg_info->matched_name<<7;
sadscp_resp->a_r_dscp_value &= ~(1<<6);
sadscp_resp->a_r_dscp_value |= (pcp_msg_info->sadscp_dscp & PCP_SADSCP_MASK);
memset(sadscp_resp->reserved, 0, sizeof(sadscp_resp->reserved));
}
#endif /* PCP_SADSCP */
}
int ProcessIncomingPCPPacket(int s, unsigned char *buff, int len,
const struct sockaddr * senderaddr)
{
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;
senderaddr_v4 = (const struct sockaddr_in *)senderaddr;
if(!inet_ntop(AF_INET, &senderaddr_v4->sin_addr,
pcp_msg_info.senderaddrstr,
sizeof(pcp_msg_info.senderaddrstr))) {
syslog(LOG_ERR, "inet_ntop(pcpserver): %m");
}
}
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;
}
lan_addr = get_lan_for_peer(senderaddr);
if(lan_addr == NULL) {
syslog(LOG_WARNING, "SSDP 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_schedule(s, buff, len, 0, senderaddr,
(senderaddr->sa_family == AF_INET) ?
sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6) );
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
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 = in6addr_any;
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*/
#endif /*ENABLE_PCP*/