-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use UPnP to punch holes in NATs #142
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,13 +17,17 @@ static const char rcsid_ZInitialize_c[] = | |
|
||
#include <internal.h> | ||
|
||
#include <arpa/inet.h> | ||
#include <sys/socket.h> | ||
#ifdef HAVE_KRB4 | ||
#include <krb_err.h> | ||
#endif | ||
#ifdef HAVE_KRB5 | ||
#include <krb5.h> | ||
#endif | ||
#if defined(__APPLE__) && defined(__MACH__) | ||
#include "nwi/network_state_information_priv.h" | ||
#endif | ||
|
||
#ifndef INADDR_NONE | ||
#define INADDR_NONE 0xffffffff | ||
|
@@ -55,6 +59,8 @@ ZInitialize(void) | |
int s; | ||
Code_t code; | ||
ZNotice_t notice; | ||
int nf; | ||
char* mp; | ||
#ifdef HAVE_KRB5 | ||
char **krealms = NULL; | ||
#else | ||
|
@@ -112,6 +118,8 @@ ZInitialize(void) | |
code will fall back to something which might not be "right", | ||
but this is is ok, since none of the servers call krb_rd_req. */ | ||
|
||
__My_addr_internal.s_addr = INADDR_NONE; | ||
__My_addr.s_addr = INADDR_NONE; | ||
servaddr.s_addr = INADDR_NONE; | ||
if (! __Zephyr_server) { | ||
if ((code = ZOpenPort(NULL)) != ZERR_NONE) | ||
|
@@ -143,6 +151,21 @@ ZInitialize(void) | |
if (hostent && hostent->h_addrtype == AF_INET) | ||
memcpy(&servaddr, hostent->h_addr, sizeof(servaddr)); | ||
|
||
// Field 10 contains our external IP. | ||
mp = notice.z_message; | ||
*(notice.z_message+notice.z_message_len-1) = 0; | ||
for (nf=0; mp<notice.z_message+notice.z_message_len && nf<10; nf++) { | ||
mp += strlen(mp)+1; | ||
} | ||
if (nf==10 && mp<notice.z_message+notice.z_message_len) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we not have a kConstant or CONSTANT for this 10? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not that I know of. |
||
inet_aton(mp, &__My_addr); | ||
} | ||
// Field 11 contains the IGD root URL (if UPnP is enabled) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's an IGD root URL? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Internet Gateway Device. It's the UPnP endpoint that you have to do UDP-HTTP to... (!) |
||
mp += strlen(mp)+1; | ||
if (mp<notice.z_message+notice.z_message_len) { | ||
__UPnP_rooturl = strdup(mp); | ||
} | ||
|
||
ZFreeNotice(¬ice); | ||
} | ||
|
||
|
@@ -177,7 +200,6 @@ ZInitialize(void) | |
#endif | ||
#endif | ||
|
||
__My_addr.s_addr = INADDR_NONE; | ||
if (servaddr.s_addr != INADDR_NONE) { | ||
/* Try to get the local interface address by connecting a UDP | ||
* socket to the server address and getting the local address. | ||
|
@@ -192,25 +214,41 @@ ZInitialize(void) | |
if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) == 0 | ||
&& getsockname(s, (struct sockaddr *) &sin, &sinsize) == 0 | ||
&& sin.sin_addr.s_addr != 0) | ||
memcpy(&__My_addr, &sin.sin_addr, sizeof(__My_addr)); | ||
memcpy(&__My_addr_internal, &sin.sin_addr, sizeof(__My_addr_internal)); | ||
close(s); | ||
} | ||
} | ||
if (__My_addr.s_addr == INADDR_NONE) { | ||
#if defined(__APPLE__) && defined(__MACH__) | ||
if (__My_addr_internal.s_addr == INADDR_NONE) { | ||
nwi_state_t state = nwi_state_copy(); | ||
nwi_ifstate_t ifstate = nwi_state_get_first_ifstate(state, AF_INET); | ||
if (ifstate != NULL) { | ||
memcpy(&__My_addr_internal, &ifstate->iaddr, sizeof(__My_addr_internal)); | ||
} | ||
if (state != NULL) { | ||
nwi_state_release(state); | ||
} | ||
} | ||
#endif | ||
if (__My_addr_internal.s_addr == INADDR_NONE) { | ||
/* We couldn't figure out the local interface address by the | ||
* above method. Try by resolving the local hostname. (This | ||
* is a pretty broken thing to do, and unfortunately what we | ||
* always do on server machines.) */ | ||
if (gethostname(hostname, sizeof(hostname)) == 0) { | ||
hostent = gethostbyname(hostname); | ||
if (hostent && hostent->h_addrtype == AF_INET) | ||
memcpy(&__My_addr, hostent->h_addr, sizeof(__My_addr)); | ||
memcpy(&__My_addr_internal, hostent->h_addr, sizeof(__My_addr_internal)); | ||
} | ||
} | ||
/* If the above methods failed, zero out __My_addr so things will | ||
* sort of kind of work. */ | ||
if (__My_addr_internal.s_addr == INADDR_NONE) | ||
__My_addr_internal.s_addr = 0; | ||
|
||
/* If ZHM didn't give us an external address, use the internal one */ | ||
if (__My_addr.s_addr == INADDR_NONE) | ||
__My_addr.s_addr = 0; | ||
__My_addr = __My_addr_internal; | ||
|
||
/* Get the sender so we can cache it */ | ||
(void) ZGetSender(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
#include <internal.h> | ||
|
||
#ifdef HAVE_UPNP | ||
#include <arpa/inet.h> | ||
#include <miniupnpc/miniupnpc.h> | ||
#include <miniupnpc/upnpcommands.h> | ||
|
||
static struct UPNPUrls __UPnP_urls = {.controlURL = NULL}; | ||
static struct IGDdatas __UPnP_data; | ||
static int __UPnP_attempted = 0; | ||
quentinmit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
static const char* __UPnP_name = "Zephyr Client"; | ||
|
||
void Z_InitUPnP_ZHM() { | ||
struct UPNPDev * devlist; | ||
int upnperror = 0; | ||
if (__UPnP_active) { | ||
// tried to initialize twice | ||
return; | ||
} | ||
devlist = upnpDiscover( | ||
2000, | ||
NULL/*multicast interface*/, | ||
NULL/*minissdpd socket path*/, | ||
UPNP_LOCAL_PORT_ANY/*sameport*/, | ||
0/*ipv6*/, | ||
2/*TTL*/, | ||
&upnperror); | ||
if (devlist) { | ||
int igdfound = UPNP_GetValidIGD(devlist, &__UPnP_urls, &__UPnP_data, NULL, 0); | ||
if (igdfound) { | ||
__UPnP_rooturl = __UPnP_urls.rootdescURL; | ||
char extIpAddr[16]; | ||
if (UPNP_GetExternalIPAddress(__UPnP_urls.controlURL, __UPnP_data.first.servicetype, extIpAddr) == 0) { | ||
struct in_addr ext_addr; | ||
if (inet_aton(extIpAddr, &ext_addr)) { | ||
__My_addr = ext_addr; | ||
__UPnP_active = 1; | ||
} | ||
} | ||
} | ||
freeUPNPDevlist(devlist); | ||
} | ||
__UPnP_name = "Zephyr Host Manager"; | ||
Z_InitUPnP(); | ||
} | ||
|
||
void Z_InitUPnP() { | ||
if (__UPnP_attempted) { | ||
return; | ||
} | ||
__UPnP_attempted = 1; | ||
if (__UPnP_rooturl && !__UPnP_active) { | ||
__UPnP_active = UPNP_GetIGDFromUrl(__UPnP_rooturl, &__UPnP_urls, &__UPnP_data, NULL, 0); | ||
} | ||
if (__UPnP_active) { | ||
char port_str[16]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why 16? Port are only 0..65535, so shouldn't [8] be sufficient? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is copied verbatim from the library. |
||
snprintf(port_str, 16, "%d", ntohs(__Zephyr_port)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error checking to make sure snprintf didn't overflow? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As is this. |
||
int ret = UPNP_AddPortMapping(__UPnP_urls.controlURL, | ||
__UPnP_data.first.servicetype, | ||
port_str, | ||
port_str, | ||
inet_ntoa(__My_addr_internal), | ||
__UPnP_name, "UDP", NULL, NULL); | ||
// TODO: Handle error 718 (ConflictInMappingEntry) by choosing a new random port. | ||
} | ||
} | ||
|
||
void Z_CloseUPnP() { | ||
if (__UPnP_active && __UPnP_attempted) { | ||
__UPnP_attempted = 0; | ||
char port_str[16]; | ||
snprintf(port_str, 16, "%d", ntohs(__Zephyr_port)); | ||
UPNP_DeletePortMapping(__UPnP_urls.controlURL, | ||
__UPnP_data.first.servicetype, | ||
port_str, | ||
"UDP", | ||
NULL); | ||
} | ||
} | ||
#else | ||
// Noop. | ||
void Z_InitUPnP_ZHM() {}; | ||
void Z_InitUPnP() {} | ||
void Z_CloseUPnP() {} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Is this just to close the UPnP session before we exit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, otherwise the UPnP port mapping will persist on the router until you reboot the router.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really making me want to have a proper zephyr_ctx that we can manage the lifecycle with. It's a bit annoying we can't easily test for these sorts of port leaks. Not something to address now, of course.