blob: ef0bff59d195f6c97c3f56c11a906700ca0d676a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*-------------------------------------------------------------------------
*
* netcheck.c
* Implementation of network checking utilities
*
*-------------------------------------------------------------------------
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "postgres.h"
#include "utils/netcheck.h"
#include "utils/simex.h"
#include "utils/timestamp.h"
#if defined(__darwin__) || defined(sun)
#include <sys/sockio.h>
#endif /* defined(__darwin__) || defined(sun) */
/*
* ifaddrs.h is not present in Solaris;
* need to provide partial implementation
*/
#if defined(__darwin__) || defined(__linux__)
#define HAVE_IFADDRS 1
#endif /* defined(__darwin__) || defined(linux) */
#ifdef HAVE_IFADDRS
#include <ifaddrs.h>
#endif /* HAVE_IFADDRS */
/*
* CONSTANTS
*/
#define NET_RETRIES (5)
#define IF_CONFIG_BUFFER_SIZE (32 * 1024)
/*
* STRUCTS
*/
#ifndef HAVE_IFADDRS
struct ifaddrs
{
struct ifaddrs *ifa_next;
char ifa_name[32];
uint64_t ifa_flags;
struct sockaddr *ifa_addr;
struct sockaddr *ifa_netmask;
union
{
struct sockaddr ifa_addr_alloc;
struct sockaddr_in ifa_addr_v4;
struct sockaddr_in6 ifa_addr_v6;
};
union
{
struct sockaddr ifa_netmask_alloc;
struct sockaddr_in ifa_netmask_v4;
struct sockaddr_in6 ifa_netmask_v6;
};
};
#endif /* HAVE_IFADDRS */
/*
* STATIC VARIABLES
*/
/* flag indicating if NIC is running */
static bool running = false;
/* host name to be matched to NIC */
static const char *hostTarget = NULL;
/* IPv4 and IPv6 addresses for host */
static struct sockaddr_in hostAddrV4;
static struct sockaddr_in6 hostAddrV6;
/* list of interface address descriptors */
static struct ifaddrs *ifAddresses;
#ifndef HAVE_IFADDRS
/* socket used for retrieving NIC state */
static int sock = -1;
/* interface configuration */
static struct ifconf ifconfig;
/* buffers used for storing configuration */
static char ifBuffer[IF_CONFIG_BUFFER_SIZE];
static char ifaBuffer[IF_CONFIG_BUFFER_SIZE];
/* iterator for interface address descriptors */
static struct ifaddrs *ifAddrIter;
#endif /* HAVE_IFADDRS */
/*
* FUNCTION PROTOTYPES
*/
static void Init(const char *hostname);
static bool GetHost(void);
static bool GetIfAddresses(void);
static bool CompareIP(char *ipaddr1, char *ipaddr2, char *mask, int length);
static bool CheckRunning(void);
static void FindMatchingIf(void);
static void Release(void);
#ifndef HAVE_IFADDRS
/* provide functions from ifaddrs.h */
static int getifaddrs(struct ifaddrs **ifAddrsPtr);
static void freeifaddrs(struct ifaddrs *ifAddrStruct);
static bool OpenSocket(bool useIPv6);
static bool CloseSocket(void);
static bool GetIFConfig(void);
static bool RetrieveIfInfo(bool useIPv6);
static bool CloseSocket();
#endif /* HAVE_IFADDRS */
/*
* check if the NIC used for routing to the given host is running
*/
bool
NetCheckNIC(const char *hostname)
{
/* open a socket */
for (int i = 0; i < NET_RETRIES; i++)
{
Init(hostname);
if (GetHost() && GetIfAddresses())
{
FindMatchingIf();
Release();
return CheckRunning();
}
elog(LOG, "Retry %d to check NIC status.", i + 1);
Release();
/* sleep for 1 second to avoid tight loops */
pg_usleep(USECS_PER_SEC);
}
elog(LOG, "Failed to check NIC status after trying %d times.", NET_RETRIES);
return false;
}
/*
* Initialize static variables.
*/
static void
Init(const char *hostname)
{
hostTarget = hostname;
running = false;
ifAddresses = NULL;
}
/*
* Retrieve IP address for the target host name.
*/
static bool
GetHost()
{
struct addrinfo *hostAddresses = NULL;
struct addrinfo *hostAddrIter = NULL;
struct addrinfo hints;
MemSet(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
bool foundV4 = false;
bool foundV6 = false;
int ret = getaddrinfo(hostTarget, NULL /*service*/, &hints, &hostAddresses);
if (0 != ret)
{
elog(LOG, "Failed to lookup host name to retrieve NIC configuration: %s",
gai_strerror(ret));
return false;
}
for (hostAddrIter = hostAddresses; hostAddrIter != NULL; hostAddrIter = hostAddrIter->ai_next)
{
if (AF_INET == hostAddrIter->ai_family)
{
Assert(!foundV4 && "expected no more than one IPv4 address for host");
memcpy(&hostAddrV4, hostAddrIter->ai_addr->sa_data, sizeof(hostAddrIter->ai_addr->sa_data)<sizeof(hostAddrV4)?sizeof(hostAddrIter->ai_addr->sa_data):sizeof(hostAddrV4));
foundV4 = true;
}
else if (AF_INET6 == hostAddrIter->ai_family)
{
Assert(!foundV6 && "expected no more than one IPv6 address for host");
memcpy(&hostAddrV6, hostAddrIter->ai_addr->sa_data, sizeof(hostAddrIter->ai_addr->sa_data)<sizeof(hostAddrV6)?sizeof(hostAddrIter->ai_addr->sa_data):sizeof(hostAddrV6));
foundV6 = true;
}
}
/* release allocated resources */
freeaddrinfo(hostAddresses);
return true;
}
/*
* Retrieve NIC configuration.
*/
static bool
GetIfAddresses()
{
int ret = getifaddrs(&ifAddresses);
if (0 != ret || NULL == ifAddresses)
{
elog(LOG, "Failed to retrieve NIC configuration.");
return false;
}
return true;
}
/*
* Iterate over interfaces to find the one used for routing to host address.
*/
static void
FindMatchingIf()
{
Assert(NULL != ifAddresses);
struct ifaddrs *ifa = NULL;
bool found = false;
/* iterate NICs */
for (ifa = ifAddresses; ifa != NULL; ifa = ifa->ifa_next)
{
/* check if the examined interface is a NIC */
if ((ifa->ifa_flags &
(IFF_UP | IFF_BROADCAST | IFF_POINTOPOINT | IFF_LOOPBACK | IFF_NOARP))
!= (IFF_UP | IFF_BROADCAST))
{
continue;
}
/* check if the examined interface supports IPv4 or IPv6 */
if ((AF_INET != ifa ->ifa_addr->sa_family &&
AF_INET6 != ifa ->ifa_addr->sa_family) ||
ifa->ifa_netmask == NULL)
{
continue;
}
if (ifa ->ifa_addr->sa_family == AF_INET)
{
char nicAddr[sizeof(hostAddrV4)];
char maskAddr[sizeof(hostAddrV4)];
(void) memcpy(nicAddr, ifa->ifa_addr->sa_data, sizeof(ifa->ifa_addr->sa_data)<sizeof(nicAddr)?sizeof(ifa->ifa_addr->sa_data):sizeof(nicAddr));
(void) memcpy(maskAddr, ifa->ifa_netmask->sa_data, sizeof(ifa->ifa_netmask->sa_data)<sizeof(maskAddr)?sizeof(ifa->ifa_netmask->sa_data):sizeof(maskAddr));
found = CompareIP((char*) &hostAddrV4, nicAddr, maskAddr, sizeof(ifa->ifa_addr->sa_data)<sizeof(hostAddrV4)?sizeof(ifa->ifa_addr->sa_data):sizeof(hostAddrV4));
}
else
{
Assert(ifa->ifa_addr->sa_family == AF_INET6);
char nicAddr[sizeof(hostAddrV6)];
char maskAddr[sizeof(hostAddrV6)];
(void) memcpy(nicAddr, ifa->ifa_addr->sa_data, sizeof(ifa->ifa_addr->sa_data)<sizeof(nicAddr)?sizeof(ifa->ifa_addr->sa_data):sizeof(nicAddr));
(void) memcpy(maskAddr, ifa->ifa_netmask->sa_data, sizeof(ifa->ifa_netmask->sa_data)<sizeof(maskAddr)?sizeof(ifa->ifa_netmask->sa_data):sizeof(maskAddr));
found = CompareIP((char*) &hostAddrV6, nicAddr, maskAddr, sizeof(ifa->ifa_addr->sa_data)<sizeof(hostAddrV6)?sizeof(ifa->ifa_addr->sa_data):sizeof(hostAddrV6));
}
if (found)
{
running = (ifa->ifa_flags & IFF_RUNNING) == IFF_RUNNING;
return;
}
}
Assert(!running);
elog(LOG, "No NIC matches host name %s.", hostTarget);
}
/*
* Compare IP addresses under mask.
*/
static bool
CompareIP(char *ipaddr1, char *ipaddr2, char *mask, int length)
{
int i = 0;
for (i = 0; i < length; i++)
{
if ((ipaddr1[i] & mask[i]) != (ipaddr2[i] & mask[i]))
{
return false;
}
}
return true;
}
/*
* Check if NIC is running.
*/
static bool
CheckRunning()
{
return running;
}
/*
* Release allocated resources.
*/
static void
Release()
{
if (NULL != ifAddresses)
{
freeifaddrs(ifAddresses);
ifAddresses = NULL;
}
}
#ifndef HAVE_IFADDRS
/*
* Build list of interface address descriptors
*/
int getifaddrs(struct ifaddrs **ifAddrsPtr)
{
MemSet(ifaBuffer, 0, sizeof(ifaBuffer));
ifAddrIter = (struct ifaddrs*) ifaBuffer;
*ifAddrsPtr = NULL;
if (OpenSocket(false /*useIPv6*/) &&
GetIFConfig() &&
RetrieveIfInfo(false /*useIPv6*/) &&
CloseSocket() &&
OpenSocket(true /*useIPv6*/) &&
GetIFConfig() &&
RetrieveIfInfo(true /*useIPv6*/) &&
CloseSocket())
{
*ifAddrsPtr = (struct ifaddrs*) ifaBuffer;
return 0;
}
CloseSocket();
return -1;
}
/*
* Release list of interface address descriptors;
* no-op since we use statically allocated buffers
*/
static void
freeifaddrs(struct ifaddrs *ifAddresses)
{}
/*
* Open socket.
*/
static bool
OpenSocket(bool useIPv6)
{
if (useIPv6)
{
sock = gp_socket(AF_INET6, SOCK_DGRAM, 0 /* protocol */);
}
else
{
sock = gp_socket(AF_INET, SOCK_DGRAM, 0 /* protocol */);
}
if (-1 == sock)
{
elog(LOG, "Failed to open socket to retrieve NIC configuration.");
return false;
}
return true;
}
/*
* Close socket.
*/
static bool
CloseSocket()
{
(void) close(sock);
sock = -1;
return true;
}
/*
* Retrieve interface configuration.
*/
static bool
GetIFConfig()
{
MemSet(ifBuffer, 0, sizeof(ifBuffer));
MemSet(&ifconfig, 0, sizeof(ifconfig));
ifconfig.ifc_buf = ifBuffer;
ifconfig.ifc_len = sizeof(ifBuffer);
if (-1 == ioctl(sock, SIOCGIFCONF, &ifconfig))
{
elog(LOG, "Failed to retrieve NIC configuration.");
return false;
}
return true;
}
/*
* Retrieve information for the available NICs.
*/
static bool
RetrieveIfInfo(bool useIPv6)
{
/*
* ifreq structures may be variable length;
* we have to step along the structure by the size of
* the previous structure
*/
struct ifreq *ifr = (struct ifreq *) ifconfig.ifc_req;
struct ifreq *end = (struct ifreq *) ((char *) ifr + ifconfig.ifc_len);
const char *ipvName = (useIPv6) ? "IPv6" : "IPv4";
if (ifr == end)
{
elog(LOG, "Failed to retrieve NIC configuration.");
return false;
}
while (ifr < end)
{
struct ifreq *ifrCurrent = ifr;
#ifdef __darwin__
/* step along the array by the size of the current structure */
ifr = (struct ifreq *)((char *)ifr + ifr->ifr_addr.sa_len + IFNAMSIZ);
#else
ifr++;
#endif /* __darwin__ */
if (AF_INET != ifrCurrent->ifr_addr.sa_family &&
AF_INET6 != ifrCurrent->ifr_addr.sa_family)
{
continue;
}
int addrLen = (ifrCurrent->ifr_addr.sa_family == AF_INET6) ?
sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
ifAddrIter->ifa_next = (ifAddrIter + 1);
(void) strncpy(ifAddrIter->ifa_name, ifrCurrent->ifr_name, sizeof(ifAddrIter->ifa_name));
(void) memcpy(&ifAddrIter->ifa_addr_alloc, &ifrCurrent->ifr_addr, addrLen);
ifAddrIter->ifa_addr = &ifAddrIter->ifa_addr_alloc;
/* retrieve configuration flags */
if (-1 == ioctl(sock, SIOCGIFFLAGS, ifrCurrent))
{
elog(LOG, "Failed to retrieve configuration flags for NIC %s using %s.",
ifrCurrent->ifr_name,
ipvName
);
continue;
}
ifAddrIter->ifa_flags = ifrCurrent->ifr_flags;
/* retrieve network mask */
if (-1 == ioctl(sock, SIOCGIFNETMASK, ifrCurrent))
{
elog(LOG, "Failed to retrieve network mask for NIC %s using %s.",
ifrCurrent->ifr_name,
ipvName);
continue;
}
(void) memcpy(&ifAddrIter->ifa_netmask_alloc, &ifrCurrent->ifr_addr, addrLen);
ifAddrIter->ifa_netmask = &ifAddrIter->ifa_netmask_alloc;
/* move to next descriptor */
ifAddrIter++;
if ((uintptr_t) sizeof(ifaBuffer) < (uintptr_t) ifAddrIter - (uintptr_t) ifaBuffer)
{
elog(LOG, "Failed to retrieve configuration for all NICs, buffer capacity reached.");
return false;
}
}
/* terminate address descriptor list */
(ifAddrIter - 1)->ifa_next = NULL;
return true;
}
#endif /* HAVE_IFADDRS */
/* EOF */