direwolf/sock.c

439 lines
12 KiB
C
Raw Permalink Normal View History

2017-09-12 01:53:38 +00:00
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2017 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: sock.c
*
* Purpose: Functions for TCP sockets.
*
* Description: These are used for connecting between different applications,
* possibly on different hosts.
*
* New in version 1.5:
* Duplicate code already exists in multiple places and I was about
* to add another one. Instead, we will gather the common code here
* instead of having yet another copy.
*
*---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include <winsock2.h>
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
//#include <termios.h>
#include <sys/errno.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include "textcolor.h"
#include "sock.h"
static void shuffle (struct addrinfo *host[], int nhosts);
/*-------------------------------------------------------------------
*
* Name: sock_init
*
* Purpose: Preparation before using socket interface.
*
* Inputs: none
*
* Returns: 0 for success, -1 for error.
*
* Errors: Message is printed. I've never seen it fail.
*
* Description: Doesn't do anything for Linux.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in kissutil.c
* TODO: Use this instead of own copy in server.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
int sock_init(void)
{
#if __WIN32__
WSADATA wsadata;
int err;
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("WSAStartup failed, error: %d\n", err);
return (-1);
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return (-1);
}
#endif
return (0);
} /* end sock_init */
/*-------------------------------------------------------------------
*
* Name: sock_connect
*
* Purpose: Connect to given host / port.
*
* Inputs: hostname - Host name or IP address.
*
* port - TCP port as text string.
*
* description - Description of the remote server to be used in error message.
* e.g. "APRS-IS (Igate) Server" or "TCP KISS TNC".
*
* allow_ipv6 - True to allow IPv6. Otherwise only IPv4.
*
* debug - Print debugging information.
*
* Outputs: ipaddr_str - The IP address, in text form, is placed here in case
* the caller wants it. Should be SOCK_IPADDR_LEN bytes.
*
* Returns: Socket Handle / file descriptor or -1 for error.
*
* Errors: (1) Can't find address for given host name.
*
* Print error and return -1.
*
* (2) Can't connect to one of the address(es).
*
* Silently try the next one.
*
* (3) Can't connect to any of the address(es).
*
* Nothing is printed for success. The caller might do that
* to provide confirmation on what is happening.
*
*--------------------------------------------------------------------*/
int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[SOCK_IPADDR_LEN])
{
#define MAX_HOSTS 50
struct addrinfo hints;
struct addrinfo *ai_head = NULL;
struct addrinfo *ai;
struct addrinfo *hosts[MAX_HOSTS];
int num_hosts, n;
int err;
int server_sock = -1;
strlcpy (ipaddr_str, "???", SOCK_IPADDR_LEN);
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
if ( ! allow_ipv6) {
hints.ai_family = AF_INET; /* IPv4 only. */
}
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
/*
* First, we need to look up the DNS name to get IP address.
* It is possible to have multiple addresses.
*/
ai_head = NULL;
err = getaddrinfo(hostname, port, &hints, &ai_head);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
#if __WIN32__
dw_printf ("Can't get address for %s, %s, err=%d\n",
description, hostname, WSAGetLastError());
#else
dw_printf ("Can't get address for %s, %s, %s\n",
description, hostname, gai_strerror(err));
#endif
freeaddrinfo(ai_head);
return (-1);
}
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("getaddrinfo returns:\n");
}
num_hosts = 0;
for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
if (debug) {
text_color_set(DW_COLOR_DEBUG);
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
hosts[num_hosts] = ai;
if (num_hosts < MAX_HOSTS) num_hosts++;
}
shuffle (hosts, num_hosts);
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("addresses for hostname:\n");
for (n=0; n<num_hosts; n++) {
sock_ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
}
/*
* Try each address until we find one that is successful.
*/
for (n = 0; n < num_hosts; n++) {
#if __WIN32__
SOCKET is;
#else
int is;
#endif
ai = hosts[n];
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
#if __WIN32__
if (is == INVALID_SOCKET) {
printf ("Socket creation failed, err=%d", WSAGetLastError());
WSACleanup();
is = -1;
continue;
}
#else
if (err != 0) {
printf ("Socket creation failed, err=%s", gai_strerror(err));
(void) close (is);
is = -1;
continue;
}
#endif
#ifndef DEBUG_DNS
err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
#if __WIN32__
if (err == SOCKET_ERROR) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description, hostname, ipaddr_str, port);
#endif
closesocket (is);
is = -1;
continue;
}
#else
if (err != 0) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description, hostname, ipaddr_str, port);
#endif
(void) close (is);
is = -1;
continue;
}
/* IGate documentation says to use no delay. */
/* Does it really make a difference? */
int flag = 1;
err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
if (err < 0) {
printf("setsockopt TCP_NODELAY failed.\n");
}
#endif
/* Success. */
server_sock = is;
#endif
break;
}
freeaddrinfo(ai_head);
// no, caller should handle this.
// function should be generally be silent unless debug option.
if (server_sock == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to connect to %s at %s (%s), port %s\n",
description, hostname, ipaddr_str, port );
}
return (server_sock);
} /* end sock_connect */
/*-------------------------------------------------------------------
*
* Name: sock_bind
*
* Purpose: We also have a bunch of duplicate code for the server side.
*
* Inputs:
*
* TODO: Use this instead of own copy in audio.c
* TODO: Use this instead of own copy in audio_portaudio.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in server.c
*
*--------------------------------------------------------------------*/
// Not implemented yet.
/*
* Addresses don't get mixed up very well.
* IPv6 always shows up last so we'd probably never
* end up using any of them for APRS-IS server.
* Add our own shuffle.
*/
static void shuffle (struct addrinfo *host[], int nhosts)
{
int j, k;
assert (RAND_MAX >= nhosts); /* for % to work right */
if (nhosts < 2) return;
srand (time(NULL));
for (j=0; j<nhosts; j++) {
k = rand() % nhosts;
assert (k >=0 && k<nhosts);
if (j != k) {
struct addrinfo *temp;
temp = host[j]; host[j] = host[k]; host[k] = temp;
}
}
}
/*-------------------------------------------------------------------
*
* Name: sock_ia_to_text
*
* Purpose: Convert binary IP Address to text form.
*
* Inputs: Family - AF_INET or AF_INET6.
*
* pAddr - Pointer to the IP Address storage location.
*
* StringBufSize - Number of bytes in pStringBuf.
*
* Outputs: pStringBuf - Text result is placed here.
*
* Returns: pStringBuf
*
* Description: Can't use InetNtop because it is supported only on Windows Vista and later.
* At one time Dire Wolf worked on Win XP. Haven't tried it for years.
* Maybe some other dependency on a newer OS version has crept in.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
{
struct sockaddr_in *sa4;
struct sockaddr_in6 *sa6;
switch (Family) {
case AF_INET:
sa4 = (struct sockaddr_in *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
sa4->sin_addr.S_un.S_un_b.s_b2,
sa4->sin_addr.S_un.S_un_b.s_b3,
sa4->sin_addr.S_un.S_un_b.s_b4);
#else
inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
#endif
break;
case AF_INET6:
sa6 = (struct sockaddr_in6 *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
#else
inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
#endif
break;
default:
snprintf (pStringBuf, StringBufSize, "Invalid address family!");
}
return pStringBuf;
} /* end sock_ia_to_text */
/* end sock.c */