1738 lines
52 KiB
C
1738 lines
52 KiB
C
/* $Id: pcpserver.c,v 1.56 2022/10/16 06:03:56 nanard Exp $ */
|
|
/* vim: tabstop=4 shiftwidth=4 noexpandtab
|
|
* MiniUPnP project
|
|
* Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
|
|
* 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 "rw_unaligned.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,
|
|
×tamp, 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),
|
|
×tamp, 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, pcp_msg_info->desc)))) {
|
|
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/*×tamp*/, 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 */
|
|
unsigned short eport2, iport2;
|
|
char iaddr2[INET6_ADDRSTRLEN];
|
|
int proto2;
|
|
char desc[64];
|
|
unsigned int timestamp;
|
|
|
|
syslog(LOG_DEBUG, "is_fw=%d addr=%s iport=%hu proto=%d",
|
|
pcp_msg_info->is_fw, pcp_msg_info->mapped_str, iport, (int)proto);
|
|
if (!pcp_msg_info->is_fw) {
|
|
int index;
|
|
/* iterate through all rules and delete the requested ones */
|
|
for (index = 0;
|
|
get_redirect_rule_by_index(index, 0,
|
|
&eport2, iaddr2, sizeof(iaddr2),
|
|
&iport2, &proto2,
|
|
desc, sizeof(desc),
|
|
0, 0, ×tamp, 0, 0) >= 0;
|
|
index++) {
|
|
syslog(LOG_DEBUG, "%d: %s %hu %d", index, iaddr2, iport2, proto2);
|
|
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 {
|
|
r = _upnp_delete_redir(eport2, proto2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
#ifdef ENABLE_UPNPPINHOLE
|
|
int uid;
|
|
uid = upnp_find_inboundpinhole(NULL, 0,
|
|
pcp_msg_info->mapped_str, iport,
|
|
pcp_msg_info->protocol,
|
|
desc, sizeof(desc),
|
|
NULL /* lifetime */);
|
|
if (uid < 0) {
|
|
syslog(LOG_ERR, "Failed to find mapping to %s:%hu, protocol %s",
|
|
pcp_msg_info->mapped_str, iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP");
|
|
return;
|
|
} else {
|
|
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 {
|
|
r = upnp_delete_inboundpinhole(uid);
|
|
}
|
|
}
|
|
#else
|
|
syslog(LOG_WARNING, "ENABLE_UPNPPINHOLE was not enabled at compile time");
|
|
#endif /* ENABLE_UPNPPINHOLE */
|
|
}
|
|
if (r >= 0) {
|
|
syslog(LOG_INFO, "PCP: %s port %hu mapping removed",
|
|
proto==IPPROTO_TCP?"TCP":"UDP", (pcp_msg_info->is_fw ? iport : eport2));
|
|
} else {
|
|
syslog(LOG_ERR, "Failed to remove PCP mapping to %s:%hu %s",
|
|
pcp_msg_info->mapped_str, iport, (proto == 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++) {
|
|
if (sockets[i] < 0) {
|
|
continue;
|
|
}
|
|
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(sockets[%d]) sendto(): %m", i);
|
|
}
|
|
}
|
|
#ifdef ENABLE_IPV6
|
|
if (socket6 >= 0) {
|
|
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*/
|