1509 lines
46 KiB
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, ×tamp, 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,
|
|
×tamp, 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, ×tamp, 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*/
|