From 8343091b9517021a7f4495476a0bb3ae21447e3a Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Thu, 6 Aug 2020 02:14:13 -0400 Subject: [PATCH 1/4] Use the IP address from SystemConfiguration on OS X --- lib/ZInit.c | 17 ++ lib/nwi/network_information.h | 286 ++++++++++++++++++ lib/nwi/network_state_information_priv.h | 355 +++++++++++++++++++++++ 3 files changed, 658 insertions(+) create mode 100644 lib/nwi/network_information.h create mode 100644 lib/nwi/network_state_information_priv.h diff --git a/lib/ZInit.c b/lib/ZInit.c index abaa2a4f..9457b8f2 100644 --- a/lib/ZInit.c +++ b/lib/ZInit.c @@ -24,6 +24,9 @@ static const char rcsid_ZInitialize_c[] = #ifdef HAVE_KRB5 #include #endif +#if defined(__APPLE__) && defined(__MACH__) +#include "nwi/network_state_information_priv.h" +#endif #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff @@ -196,6 +199,20 @@ ZInitialize(void) close(s); } } +#if defined(__APPLE__) && defined(__MACH__) + if (__My_addr.s_addr == INADDR_NONE) { + nwi_state_t state; + state = nwi_state_copy(); + nwi_ifstate_t ifstate; + ifstate = nwi_state_get_first_ifstate(state, AF_INET); + if (ifstate != NULL) { + memcpy(&__My_addr, &ifstate->iaddr, sizeof(__My_addr)); + } + if (state != NULL) { + nwi_state_release(state); + } + } +#endif if (__My_addr.s_addr == INADDR_NONE) { /* We couldn't figure out the local interface address by the * above method. Try by resolving the local hostname. (This diff --git a/lib/nwi/network_information.h b/lib/nwi/network_information.h new file mode 100644 index 00000000..3ca3d04f --- /dev/null +++ b/lib/nwi/network_information.h @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2011-2019 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +#ifndef _NETWORK_INFORMATION_H_ +#define _NETWORK_INFORMATION_H_ + +#include +#include +#include + +typedef struct _nwi_state * nwi_state_t; +typedef struct _nwi_ifstate * nwi_ifstate_t; + +__BEGIN_DECLS + +/* + * Function: nwi_state_copy + * Purpose: + * Returns the current network state information; NULL if no state + * information is currently available. + * Release after use by calling nwi_state_release(). + */ +nwi_state_t +nwi_state_copy(void); + +/* + * Function: nwi_state_release + * Purpose: + * Release the memory associated with the network state. + */ +void +nwi_state_release(nwi_state_t state); + +/* + * Function: nwi_state_get_notify_key + * Purpose: + * Returns the BSD notify key to use to monitor when the state changes. + * + * Note: + * The nwi_state_copy API uses this notify key to monitor when the state + * changes, so each invocation of nwi_state_copy returns the current + * information. + */ +const char * +nwi_state_get_notify_key(void); + +/* + * Function: nwi_state_get_first_ifstate + * Purpose: + * Returns the first and highest priority interface that has connectivity + * for the specified address family 'af'. 'af' is either AF_INET or AF_INET6. + * The connectivity provided is for general networking. To get information + * about an interface that isn't available for general networking, use + * nwi_state_get_ifstate(). + * + * Use nwi_ifstate_get_next() to get the next, lower priority interface + * in the list. + * + * Returns NULL if no connectivity for the specified address family is + * available. + */ +nwi_ifstate_t +nwi_state_get_first_ifstate(nwi_state_t state, int af); + +/* + * Function: nwi_state_get_generation + * Purpose: + * Returns the generation of the nwi_state data. + * Every time the data is updated due to changes + * in the network, this value will change. + */ +uint64_t +nwi_state_get_generation(nwi_state_t state); + +/* + * Function: nwi_ifstate_get_generation + * Purpose: + * Returns the generation of the nwi_ifstate data. + */ +uint64_t +nwi_ifstate_get_generation(nwi_ifstate_t ifstate); + +/* + * Function: nwi_state_get_ifstate + * Purpose: + * Return information for the specified interface 'ifname'. + * + * This API directly returns the ifstate for the specified interface. + * This is the only way to access information about an interface that isn't + * available for general networking. + * + * Returns NULL if no information is available for that interface. + */ +nwi_ifstate_t +nwi_state_get_ifstate(nwi_state_t state, const char * ifname); + +/* + * Function: nwi_ifstate_get_ifname + * Purpose: + * Return the interface name of the specified ifstate. + */ +const char * +nwi_ifstate_get_ifname(nwi_ifstate_t ifstate); + +/* + * Type: nwi_ifstate_flags + * Purpose: + * Provide information about the interface, including its IPv4 and IPv6 + * connectivity, and whether DNS is configured or not. + */ +#define NWI_IFSTATE_FLAGS_HAS_IPV4 0x1 /* has IPv4 connectivity */ +#define NWI_IFSTATE_FLAGS_HAS_IPV6 0x2 /* has IPv6 connectivity */ +#define NWI_IFSTATE_FLAGS_HAS_DNS 0x4 /* has DNS configured */ +#define NWI_IFSTATE_FLAGS_HAS_CLAT46 0x0040 /* has CLAT46 configured */ + +typedef uint64_t nwi_ifstate_flags; +/* + * Function: nwi_ifstate_get_flags + * Purpose: + * Return the flags for the given ifstate (see above for bit definitions). + */ +nwi_ifstate_flags +nwi_ifstate_get_flags(nwi_ifstate_t ifstate); + +/* + * Function: nwi_ifstate_get_next + * Purpose: + * Returns the next, lower priority nwi_ifstate_t after the specified + * 'ifstate' for the protocol family 'af'. + * + * Returns NULL when the end of the list is reached. + */ +nwi_ifstate_t +nwi_ifstate_get_next(nwi_ifstate_t ifstate, int af); + +/* + * Function: nwi_ifstate_compare_rank + * Purpose: + * Compare the relative rank of two nwi_ifstate_t objects. + * + * The "rank" indicates the importance of the underlying interface. + * + * Returns: + * 0 if ifstate1 and ifstate2 are ranked equally + * -1 if ifstate1 is ranked ahead of ifstate2 + * 1 if ifstate2 is ranked ahead of ifstate1 + */ +int +nwi_ifstate_compare_rank(nwi_ifstate_t ifstate1, nwi_ifstate_t ifstate2); + +/* + * Function: _nwi_state_ack + * Purpose: + * Acknowledge receipt and any changes associated with the [new or + * updated] network state. + */ +void +_nwi_state_ack(nwi_state_t state, const char *bundle_id) + API_AVAILABLE(macos(10.8), ios(6.0)); + +/* + * Function: nwi_state_get_reachability_flags + * Purpose: + * Returns the global reachability flags for a given address family. + * If no address family is passed in, it returns the global reachability + * flags for either families. + * + * The reachability flags returned follow the definition of + * SCNetworkReachabilityFlags. + * + * If the flags are zero (i.e. do not contain kSCNetworkReachabilityFlagsReachable), there is no connectivity. + * + * Otherwise, at least kSCNetworkReachabilityFlagsReachable is set: + * Reachable only + * No other connection flags are set. + * Reachable and no ConnectionRequired + * If we have connectivity for the specified address family (and we'd + * be returning the reachability flags associated with the default route) + * Reachable and ConnectionRequired + * If we do not currently have an active/primary network but we may + * be able to establish connectivity. + * Reachable and OnDemand + * If we do not currently have an active/primary network but we may + * be able to establish connective on demand. + * Reachable and TransientConnection + * This connection is transient. + * Reachable and WWAN + * This connection will be going over the cellular network. + */ +uint32_t +nwi_state_get_reachability_flags(nwi_state_t nwi_state, int af); + +/* + * Function: nwi_state_get_interface_names + * Purpose: + * Returns the list of network interface names that have connectivity. + * The list is sorted from highest priority to least, highest priority + * appearing at index 0. + * + * If 'names' is NULL or 'names_count' is zero, this function returns + * the number of elements that 'names' must contain to get the complete + * list of interface names. + * + * If 'names' is not NULL and 'names_count' is not zero, fills 'names' with + * the list of interface names not exceeding 'names_count'. Returns the + * number of elements that were actually populated. + * + * Notes: + * 1. The connectivity that an interface in this list provides may not be for + * general purpose use. + * 2. The string pointers remain valid only as long as 'state' remains + * valid. + */ +unsigned int +nwi_state_get_interface_names(nwi_state_t state, + const char * names[], + unsigned int names_count); + +/* + * nwi_ifstate_get_vpn_server + * + * returns a sockaddr representation of the vpn server address. + * NULL if PPP/VPN/IPSec server address does not exist. + */ +const struct sockaddr * +nwi_ifstate_get_vpn_server(nwi_ifstate_t ifstate); + +/* + * nwi_ifstate_get_reachability_flags + * + * returns the reachability flags for the interface given an address family. + * The flags returned are those determined outside of + * the routing table. [None, ConnectionRequired, OnDemand, + * Transient Connection, WWAN]. + */ +uint32_t +nwi_ifstate_get_reachability_flags(nwi_ifstate_t ifstate); + +/* + * nwi_ifstate_get_signature + * + * returns the signature and its length for an ifstate given an address family. + * If AF_UNSPEC is passed in, the signature for a given ifstate is returned. + * + * If the signature does not exist, NULL is returned. + */ +const uint8_t * +nwi_ifstate_get_signature(nwi_ifstate_t ifstate, int af, int * length); + + +/* + * nwi_ifstate_get_dns_signature + * + * returns the signature and its length for given + * ifstate with a valid dns configuration. + * + * If the signature does not exist, NULL is returned. + * + */ +const uint8_t * +nwi_ifstate_get_dns_signature(nwi_ifstate_t ifstate, int * length); + +__END_DECLS + +#endif diff --git a/lib/nwi/network_state_information_priv.h b/lib/nwi/network_state_information_priv.h new file mode 100644 index 00000000..06d31c4e --- /dev/null +++ b/lib/nwi/network_state_information_priv.h @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2011-2013, 2016-2019 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NETWORK_STATE_INFORMATION_PRIV_H_ +#define _NETWORK_STATE_INFORMATION_PRIV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "network_information.h" + +#define NWI_STATE_VERSION ((uint32_t)0x20170601) + + +#define NWI_IFSTATE_FLAGS_NOT_IN_LIST 0x0008 +#define NWI_IFSTATE_FLAGS_HAS_SIGNATURE 0x0010 +#define NWI_IFSTATE_FLAGS_NOT_IN_IFLIST 0x0020 + +/* + * NWI_IFSTATE_FLAGS_MASK + * - these are the bits that get preserved, all others are + * control (last item, diff) + */ +#define NWI_IFSTATE_FLAGS_MASK 0x00ff + + +#define NWI_IFSTATE_FLAGS_DIFF_MASK 0x0f00 +#define NWI_IFSTATE_FLAGS_LAST_ITEM 0x1000 + +typedef enum { + knwi_ifstate_difference_none = 0, + knwi_ifstate_difference_changed = 1, + knwi_ifstate_difference_removed = 2 +} nwi_ifstate_difference_t; + + +/* + * Type: Rank + * Purpose: + * A 32-bit value to encode the relative rank of a service. + * + * The top 8 bits are used to hold the rank assertion (first, default, last, + * never, scoped); + * + * The bottom 24 bits are used to store the service index (i.e. the + * position within the service order array). + */ +typedef uint32_t Rank; +#define RANK_ASSERTION_MAKE(r) ((Rank)(r) << 24) // rank assertion (top 8 bits) +#define kRankAssertionFirst RANK_ASSERTION_MAKE(0) +#define kRankAssertionDefault RANK_ASSERTION_MAKE(1) +#define kRankAssertionLast RANK_ASSERTION_MAKE(2) +#define kRankAssertionNever RANK_ASSERTION_MAKE(3) +#define kRankAssertionScoped RANK_ASSERTION_MAKE(4) +#define kRankAssertionMask RANK_ASSERTION_MAKE(0xff) +#define RANK_ASSERTION_MASK(r) ((Rank)(r) & kRankAssertionMask) +#define RANK_INDEX_MAKE(r) ((Rank)(r)) // rank index (bottom 24 bits) +#define kRankIndexMask RANK_INDEX_MAKE(0xffffff) +#define RANK_INDEX_MASK(r) ((Rank)(r) & kRankIndexMask) + +typedef int32_t nwi_ifindex_t; + +#define NWI_SIGNATURE_LENGTH 20 + +#pragma pack(4) +typedef struct _nwi_ifstate { + char ifname[IFNAMSIZ]; + uint64_t flags; + nwi_ifindex_t af_alias_offset; /* relative index to alias */ + Rank rank; + sa_family_t af; + union { + struct in_addr iaddr; + struct in6_addr iaddr6; + }; + uint64_t if_generation_count; + uint32_t reach_flags; + union { + struct sockaddr_in vpn_server_address4; + struct sockaddr_in6 vpn_server_address6; + } vpn_server_address; + unsigned char signature[NWI_SIGNATURE_LENGTH]; +} nwi_ifstate; +#pragma pack() + +#pragma pack(4) +typedef struct _nwi_state { + uint32_t version; /* NWI_STATE_VERSION */ + nwi_ifindex_t max_if_count; /* available slots per protocol */ + nwi_ifindex_t ipv4_count; /* # of v4 ifstates in use */ + nwi_ifindex_t ipv6_count; /* # of v6 ifstates in use */ + nwi_ifindex_t if_list_count; /* # of if_list[] slots in use */ + uint32_t ref; /* reference count */ + uint32_t reach_flags_v4; + uint32_t reach_flags_v6; + uint64_t generation_count; + nwi_ifstate ifstate_list[1];/* (max_if_count * 2) ifstates */ +/* nwi_ifindex_t if_list[0]; max_if_count indices */ +} nwi_state; +#pragma pack() + +static __inline__ int +nwi_other_af(int af) +{ + return ((af == AF_INET) ? (AF_INET6) : (AF_INET)); +} + +static __inline__ size_t +nwi_state_compute_size(unsigned int max_if_count) +{ + size_t size; + + size = offsetof(nwi_state, ifstate_list[max_if_count * 2]) + + sizeof(nwi_ifindex_t) * max_if_count; + return (size); +} + +static __inline__ size_t +nwi_state_size(nwi_state_t state) +{ + return (nwi_state_compute_size(state->max_if_count)); +} + +static __inline__ nwi_ifstate_t +nwi_state_ifstate_list(nwi_state_t state, int af) +{ + if (af == AF_INET) { + return (state->ifstate_list); + } + return (state->ifstate_list + state->max_if_count); +} + +static __inline__ nwi_ifindex_t * +nwi_state_if_list(nwi_state_t state) +{ + return ((nwi_ifindex_t *)&state->ifstate_list[state->max_if_count * 2]); +} + +static __inline__ int +uint32_cmp(uint32_t a, uint32_t b) +{ + int ret; + + if (a == b) { + ret = 0; + } + else if (a < b) { + ret = -1; + } + else { + ret = 1; + } + return (ret); +} + +static __inline__ int +RankCompare(Rank a, Rank b) +{ + return (uint32_cmp(a, b)); +} + +/* + * Function: nwi_state_get_ifstate_count + * Purpose: + * Return the number of ifstate elements for the specified address family + * 'af'. 'af' is either AF_INET or AF_INET6. + * + * Returns zero if there are no elements. + */ +static __inline__ +int +nwi_state_get_ifstate_count(nwi_state_t state, int af) +{ + return (af == AF_INET)?state->ipv4_count:state->ipv6_count; +} + +static __inline__ nwi_ifstate_t +nwi_ifstate_get_alias(nwi_ifstate_t ifstate, int af) +{ + if (ifstate->af == af) { + return (ifstate); + } + if (ifstate->af_alias_offset == 0) { + return (NULL); + } + return (ifstate + ifstate->af_alias_offset); +} + +/* + * The ifstate list is sorted in order of decreasing priority, with the + * highest priority element appearing at index zero. + * + * If 'idx' is outside of the bounds of the corresponding array, returns NULL. + */ +static __inline__ +nwi_ifstate_t +nwi_state_get_ifstate_with_index(nwi_state_t state, int af, int idx) +{ + int i_idx = idx; + + if (idx >= nwi_state_get_ifstate_count(state, af)) { + return (NULL); + } + + if (af == AF_INET6) { + i_idx = idx + state->max_if_count; + } + + return &state->ifstate_list[i_idx]; +} + +/* + * Function: nwi_state_get_ifstate_with_name + * Purpose: + * Return the ifstate for the specified ifstate for the specified address + * family 'af'. 'af' is either AF_INET or AF_INET6. + * + * Returns NULL if no such information exists. + */ +static __inline__ +nwi_ifstate_t +nwi_state_get_ifstate_with_name(nwi_state_t state, + int af, const char * name) +{ + int idx = 0; + int count; + nwi_ifstate_t ifstate = NULL; + + if (state == NULL) { + return NULL; + } + + count = (af == AF_INET) + ?state->ipv4_count:state->ipv6_count; + + + while (idx < count) { + ifstate = nwi_state_get_ifstate_with_index(state, af, idx); + if (ifstate == NULL) { + break; + } + if (strcmp(name, + nwi_ifstate_get_ifname(ifstate)) == 0) { + return (ifstate); + } + idx++; + } + return (NULL); +} + +static __inline__ +void +_nwi_ifstate_set_vpn_server(nwi_ifstate_t ifstate, struct sockaddr *serv_addr) +{ + size_t len; + + if (serv_addr == NULL) { + memset(&ifstate->vpn_server_address, 0, sizeof(ifstate->vpn_server_address)); + return; + } + + len = serv_addr->sa_len; + + if (len == 0 || len > sizeof(ifstate->vpn_server_address)) { + return; + } + + memcpy(&ifstate->vpn_server_address, + serv_addr, + len); + return; + +} + +static __inline__ +void +_nwi_state_set_reachability_flags(nwi_state_t state, uint32_t reach_flags_v4, uint32_t reach_flags_v6) +{ + state->reach_flags_v4 = reach_flags_v4; + state->reach_flags_v6 = reach_flags_v6; + return; +} + +nwi_state_t +nwi_state_new(nwi_state_t old_state, int elems); + +nwi_state_t +nwi_state_make_copy(nwi_state_t state); + +static __inline__ void +nwi_state_free(nwi_state_t state) +{ + free(state); + return; +} + +void +nwi_state_finalize(nwi_state_t state); + +nwi_ifstate_t +nwi_state_add_ifstate(nwi_state_t state, const char* ifname, int af, + uint64_t flags, Rank rank, + void * ifa, struct sockaddr * vpn_server_addr, uint32_t reach_flags); + +void +nwi_ifstate_set_signature(nwi_ifstate_t ifstate, uint8_t * signature); + +void +nwi_state_clear(nwi_state_t state, int af); + +nwi_state_t +nwi_state_diff(nwi_state_t old_state, nwi_state_t new_state); + +void * +nwi_ifstate_get_address(nwi_ifstate_t ifstate); + +const char * +nwi_ifstate_get_diff_str(nwi_ifstate_t ifstate); + +nwi_ifstate_difference_t +nwi_ifstate_get_difference(nwi_ifstate_t diff_ifstate); + +void +_nwi_state_update_interface_generations(nwi_state_t old_state, nwi_state_t state, nwi_state_t changes); + +void +_nwi_state_compute_sha256_hash(nwi_state_t state, + unsigned char hash[CC_SHA256_DIGEST_LENGTH]); + +#endif // _NETWORK_STATE_INFORMATION_PRIV_H_ From 4a9d94f62f830ca113fd6f11e9425528d1e8a8fc Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sun, 9 Aug 2020 05:39:09 -0400 Subject: [PATCH 2/4] Report zhm's external IP in zstat --- clients/zstat/zstat.c | 3 ++- zhm/zhm.c | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/clients/zstat/zstat.c b/clients/zstat/zstat.c index 68568938..38e24f82 100644 --- a/clients/zstat/zstat.c +++ b/clients/zstat/zstat.c @@ -34,7 +34,8 @@ const char *hm_head[] = { "Looking for a new server:", "Time running:", "Size:", - "Machine type:" + "Machine type:", + "External IP:", }; #define HM_SIZE (sizeof(hm_head) / sizeof (char *)) const char *srv_head[] = { diff --git a/zhm/zhm.c b/zhm/zhm.c index ec4696b7..a121a4e9 100644 --- a/zhm/zhm.c +++ b/zhm/zhm.c @@ -527,7 +527,7 @@ send_stats(ZNotice_t *notice, Code_t ret; char *bfr; char *list[20]; - int len, i, nitems = 10; + int len, i, nitems = 11; unsigned long size; extern int Zauthtype; /* XXX this may be changing in the future */ @@ -580,6 +580,10 @@ send_stats(ZNotice_t *notice, strncpy(list[9], MACHINE_TYPE, 32); list[9][31] = '\0'; + list[10] = stats_malloc(32); + strncpy(list[10], inet_ntoa(__My_addr), 32); + list[10][31] = '\0'; + /* Since ZFormatRaw* won't change the version number on notices, we need to set the version number explicitly. This code is taken from Zinternal.c, function Z_FormatHeader */ From 47acafbd1b32789a5a0db1af66dee2430578cd35 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sat, 8 Aug 2020 19:03:33 -0400 Subject: [PATCH 3/4] Add UPnP support for opening ports --- clients/zctl/zctl.c | 2 + clients/zstat/zstat.c | 1 + configure.ac | 7 +++- h/internal.h | 7 ++++ lib/Makefile.in | 2 +- lib/ZClosePort.c | 15 +++++--- lib/ZInit.c | 37 +++++++++++++++---- lib/ZLocations.c | 2 + lib/ZOpenPort.c | 6 ++- lib/ZRetSubs.c | 2 + lib/ZSubs.c | 6 +++ lib/ZUPnP.c | 85 +++++++++++++++++++++++++++++++++++++++++++ lib/Zinternal.c | 15 +++++--- zhm/Makefile.in | 3 +- zhm/zhm.c | 8 ++++ 15 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 lib/ZUPnP.c diff --git a/clients/zctl/zctl.c b/clients/zctl/zctl.c index f18df10a..cc3bb0ce 100644 --- a/clients/zctl/zctl.c +++ b/clients/zctl/zctl.c @@ -183,6 +183,7 @@ main(int argc, if (code) fprintf (stderr, "%s: %s: %s\n", argv[0], error_message (code), ssline); + ZClosePort(); exit((code != 0)); } @@ -194,6 +195,7 @@ main(int argc, #else run_command(argc-1, argv+1); #endif + ZClosePort(); exit(0); } diff --git a/clients/zstat/zstat.c b/clients/zstat/zstat.c index 38e24f82..f4c95e6b 100644 --- a/clients/zstat/zstat.c +++ b/clients/zstat/zstat.c @@ -36,6 +36,7 @@ const char *hm_head[] = { "Size:", "Machine type:", "External IP:", + "UPnP IGD Root URL:", }; #define HM_SIZE (sizeof(hm_head) / sizeof (char *)) const char *srv_head[] = { diff --git a/configure.ac b/configure.ac index dd0dc5aa..2f44fc58 100644 --- a/configure.ac +++ b/configure.ac @@ -264,7 +264,12 @@ if test "x$with_ares" != "xno"; then AC_MSG_ERROR(libcares not found))) fi AC_SUBST(ARES_LIBS) - + +PKG_CHECK_MODULES([UPNP], [miniupnpc], [AC_DEFINE([HAVE_UPNP], [1], [Use UPnP])], []) +CFLAGS="$UPNP_CFLAGS $CFLAGS" +LIBS="$UPNP_LIBS $LIBS" +LIBZEPHYR_LIBS="$LIBZEPHYR_LIBS $UPNP_LIBS" + AC_PROG_GCC_TRADITIONAL AC_FUNC_VPRINTF AC_FUNC_GETPGRP diff --git a/h/internal.h b/h/internal.h index b6e68048..040a6568 100644 --- a/h/internal.h +++ b/h/internal.h @@ -115,6 +115,9 @@ extern int __subscriptions_next; extern int __Zephyr_port; /* Port number */ extern struct in_addr __My_addr; +extern struct in_addr __My_addr_internal; +extern int __UPnP_active; +extern char* __UPnP_rooturl; extern int __Zephyr_fd; extern int __Q_CompleteLength; extern struct sockaddr_in __HM_addr; @@ -205,4 +208,8 @@ Code_t ZFormatAuthenticNotice(ZNotice_t*, char*, int, int*, C_Block); #define Z_tktprinc(tkt) ((tkt)->client) #endif +void Z_InitUPnP_ZHM(); +void Z_InitUPnP(); +void Z_CloseUPnP(); + #endif /* __INTERNAL_H__ */ diff --git a/lib/Makefile.in b/lib/Makefile.in index d76f2a90..d55f18b1 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -42,7 +42,7 @@ OBJS = zephyr_err.lo ZAsyncLocate.lo ZCkAuth.lo ZCkIfNot.lo ZClosePort.lo \ ZSendPkt.lo ZSendRaw.lo ZSendRLst.lo ZSetDest.lo ZSetFD.lo ZSetSrv.lo \ ZSubs.lo ZVariables.lo ZWait4Not.lo Zinternal.lo ZMakeZcode.lo \ ZReadZcode.lo ZCkZAut.lo quad_cksum.lo charset.lo ZExpnRlm.lo \ - ZDumpSession.lo + ZDumpSession.lo ZUPnP.lo .SUFFIXES: .lo diff --git a/lib/ZClosePort.c b/lib/ZClosePort.c index bd212b48..849131ea 100644 --- a/lib/ZClosePort.c +++ b/lib/ZClosePort.c @@ -19,11 +19,16 @@ static const char rcsid_ZClosePort_c[] = "$Id$"; Code_t ZClosePort(void) { - if (__Zephyr_fd >= 0 && __Zephyr_open) - (void) close(__Zephyr_fd); + if (__Zephyr_fd >= 0 && __Zephyr_open) { + (void) close(__Zephyr_fd); - __Zephyr_fd = -1; - __Zephyr_open = 0; +#ifdef Z_DEBUG + Z_debug_stderr("ZClosePort() closed port %d", ntohs(__Zephyr_port)); +#endif + } + Z_CloseUPnP(); + __Zephyr_fd = -1; + __Zephyr_open = 0; - return (ZERR_NONE); + return (ZERR_NONE); } diff --git a/lib/ZInit.c b/lib/ZInit.c index 9457b8f2..73584649 100644 --- a/lib/ZInit.c +++ b/lib/ZInit.c @@ -17,6 +17,7 @@ static const char rcsid_ZInitialize_c[] = #include +#include #include #ifdef HAVE_KRB4 #include @@ -58,6 +59,8 @@ ZInitialize(void) int s; Code_t code; ZNotice_t notice; + int nf; + char* mp; #ifdef HAVE_KRB5 char **krealms = NULL; #else @@ -115,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) @@ -146,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;mpiaddr, sizeof(__My_addr)); + memcpy(&__My_addr_internal, &ifstate->iaddr, sizeof(__My_addr_internal)); } if (state != NULL) { nwi_state_release(state); } } #endif - if (__My_addr.s_addr == INADDR_NONE) { + 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 @@ -221,13 +240,17 @@ ZInitialize(void) 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(); diff --git a/lib/ZLocations.c b/lib/ZLocations.c index e7537dae..03c26911 100644 --- a/lib/ZLocations.c +++ b/lib/ZLocations.c @@ -128,6 +128,8 @@ Z_SendLocation(char *class, if (!location_info_set) ZInitLocationInfo(NULL, NULL); + Z_InitUPnP(); + memset((char *)¬ice, 0, sizeof(notice)); notice.z_kind = ACKED; notice.z_port = (u_short) ((wg_port == -1) ? 0 : wg_port); diff --git a/lib/ZOpenPort.c b/lib/ZOpenPort.c index b15ee286..0b091f6e 100644 --- a/lib/ZOpenPort.c +++ b/lib/ZOpenPort.c @@ -66,8 +66,12 @@ ZOpenPort(u_short *port) __Zephyr_port = bindin.sin_port; __Zephyr_open = 1; +#ifdef Z_DEBUG + Z_debug_stderr("ZOpenPort() opened port %d", ntohs(__Zephyr_port)); +#endif + if (port) - *port = bindin.sin_port; + *port = __Zephyr_port; return ZERR_NONE; } diff --git a/lib/ZRetSubs.c b/lib/ZRetSubs.c index 9926171b..5757c0f0 100644 --- a/lib/ZRetSubs.c +++ b/lib/ZRetSubs.c @@ -78,6 +78,8 @@ Z_RetSubs(register ZNotice_t *notice, if ((retval = ZOpenPort((u_short *)0)) != ZERR_NONE) return (retval); + Z_InitUPnP(); + notice->z_kind = ACKED; notice->z_port = __Zephyr_port; notice->z_class = ZEPHYR_CTL_CLASS; diff --git a/lib/ZSubs.c b/lib/ZSubs.c index 535f3f99..ac27b185 100644 --- a/lib/ZSubs.c +++ b/lib/ZSubs.c @@ -93,6 +93,12 @@ ZSubscriptions(register ZSubscription_t *sublist, int size, start, numok; Z_AuthProc cert_routine; + if (ZGetFD() < 0) + if ((retval = ZOpenPort((u_short *)0)) != ZERR_NONE) + return (retval); + + Z_InitUPnP(); + /* nitems = 0 means cancel all subscriptions; still need to allocate a */ /* array for one item so we can cancel, however. */ diff --git a/lib/ZUPnP.c b/lib/ZUPnP.c new file mode 100644 index 00000000..a52a9ab0 --- /dev/null +++ b/lib/ZUPnP.c @@ -0,0 +1,85 @@ +#include + +#ifdef HAVE_UPNP +#include +#include +#include + +static struct UPNPUrls __UPnP_urls = {.controlURL = NULL}; +static struct IGDdatas __UPnP_data; +static int __UPnP_attempted = 0; +static 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]; + snprintf(port_str, 16, "%d", ntohs(__Zephyr_port)); + 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 diff --git a/lib/Zinternal.c b/lib/Zinternal.c index ecee122f..c2bb0d33 100644 --- a/lib/Zinternal.c +++ b/lib/Zinternal.c @@ -25,7 +25,10 @@ static const char copyright[] = int __Zephyr_fd = -1; int __Zephyr_open; int __Zephyr_port = -1; -struct in_addr __My_addr; +struct in_addr __My_addr = {.s_addr = INADDR_NONE}; +struct in_addr __My_addr_internal = {.s_addr = INADDR_NONE}; +int __UPnP_active = 0; +char* __UPnP_rooturl = NULL; int __Q_CompleteLength; int __Q_Size; struct _Z_InputQ *__Q_Head, *__Q_Tail; @@ -651,12 +654,12 @@ Z_FormatHeader(ZNotice_t *notice, if (!notice->z_sender) notice->z_sender = ZGetSender(); + if (ZGetFD() < 0) { + retval = ZOpenPort((u_short *)0); + if (retval != ZERR_NONE) + return (retval); + } if (notice->z_port == 0) { - if (ZGetFD() < 0) { - retval = ZOpenPort((u_short *)0); - if (retval != ZERR_NONE) - return (retval); - } notice->z_port = __Zephyr_port; } diff --git a/zhm/Makefile.in b/zhm/Makefile.in index 77bf2c0a..0f1f914c 100644 --- a/zhm/Makefile.in +++ b/zhm/Makefile.in @@ -32,6 +32,7 @@ CPPFLAGS=@CPPFLAGS@ CFLAGS=@CFLAGS@ ALL_CFLAGS=${CFLAGS} -I${top_srcdir}/h -I${BUILDTOP}/h ${CPPFLAGS} LDFLAGS=@LDFLAGS@ +LIBS=@LIBS@ HESIOD_LIBS=@HESIOD_LIBS@ OBJS= timer.o queue.o zhm.o zhm_client.o zhm_server.o @@ -39,7 +40,7 @@ OBJS= timer.o queue.o zhm.o zhm_client.o zhm_server.o all: zhm zhm.8 zhm: ${OBJS} ${LIBZEPHYR} - ${LIBTOOL} --mode=link ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LIBZEPHYR} ${HESIOD_LIBS} -lcom_err + ${LIBTOOL} --mode=link ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LIBZEPHYR} ${LIBS} ${HESIOD_LIBS} -lcom_err zhm.8: ${srcdir}/zhm.8.in Makefile ${editman} ${srcdir}/$@.in > $@.tmp diff --git a/zhm/zhm.c b/zhm/zhm.c index a121a4e9..d0bd0d11 100644 --- a/zhm/zhm.c +++ b/zhm/zhm.c @@ -407,6 +407,8 @@ init_hm(void) } cli_sin = ZGetDestAddr(); + Z_InitUPnP_ZHM(); + sp = getservbyname(SERVER_SVCNAME, "udp"); memset(&serv_sin, 0, sizeof(struct sockaddr_in)); serv_sin.sin_port = (sp) ? sp->s_port : SERVER_SVC_FALLBACK; @@ -584,6 +586,12 @@ send_stats(ZNotice_t *notice, strncpy(list[10], inet_ntoa(__My_addr), 32); list[10][31] = '\0'; + if (__UPnP_rooturl) { + list[11] = stats_malloc(strlen(__UPnP_rooturl)); + strcpy(list[11], __UPnP_rooturl); + nitems++; + } + /* Since ZFormatRaw* won't change the version number on notices, we need to set the version number explicitly. This code is taken from Zinternal.c, function Z_FormatHeader */ From 09c976b92197cc740d93479e5f54c6634cfb9410 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Tue, 25 Aug 2020 22:58:36 -0400 Subject: [PATCH 4/4] Address review comments --- lib/ZInit.c | 10 ++++------ lib/ZSubs.c | 8 +++++--- lib/ZUPnP.c | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/ZInit.c b/lib/ZInit.c index 73584649..d2d0620e 100644 --- a/lib/ZInit.c +++ b/lib/ZInit.c @@ -152,9 +152,9 @@ ZInitialize(void) memcpy(&servaddr, hostent->h_addr, sizeof(servaddr)); // Field 10 contains our external IP. - mp=notice.z_message; + mp = notice.z_message; *(notice.z_message+notice.z_message_len-1) = 0; - for (nf=0;mpiaddr, sizeof(__My_addr_internal)); } diff --git a/lib/ZSubs.c b/lib/ZSubs.c index ac27b185..31c797e4 100644 --- a/lib/ZSubs.c +++ b/lib/ZSubs.c @@ -93,9 +93,11 @@ ZSubscriptions(register ZSubscription_t *sublist, int size, start, numok; Z_AuthProc cert_routine; - if (ZGetFD() < 0) - if ((retval = ZOpenPort((u_short *)0)) != ZERR_NONE) - return (retval); + if (ZGetFD() < 0) { + if ((retval = ZOpenPort((u_short *)0)) != ZERR_NONE) { + return (retval); + } + } Z_InitUPnP(); diff --git a/lib/ZUPnP.c b/lib/ZUPnP.c index a52a9ab0..1a70dc41 100644 --- a/lib/ZUPnP.c +++ b/lib/ZUPnP.c @@ -8,7 +8,7 @@ static struct UPNPUrls __UPnP_urls = {.controlURL = NULL}; static struct IGDdatas __UPnP_data; static int __UPnP_attempted = 0; -static char* __UPnP_name = "Zephyr Client"; +static const char* __UPnP_name = "Zephyr Client"; void Z_InitUPnP_ZHM() { struct UPNPDev * devlist;