/*
* File: net.c
*
* Author: Ulli Horlacher (framstag@rus.uni-stuttgart.de)
*
* Contrib.: Heiko Schlichting (heiko@fu-berlin.de)
* Holger Berger (Holger.Berger@rus.uni-stuttgart.de)
* Andreas "Ako" Koppenhöfer (koppenhoefer@belwue.de)
*
* History:
*
* 11 Aug 95 Framstag initial version
* 10 Sep 95 Framstag some debugging
* 15 Nov 95 Framstag improved sock_getline
* 21 Dec 95 Framstag simplified sock_getline and getreply
* 17 Apr 96 Framstag new error handling in open_connection
* 14 May 96 Framstag included and modified send_data()
* 21 May 96 Framstag gettimeofday() fix for Solaris-2
* 20 Jun 96 Framstag always use gethostbyname()
* 3 Aug 96 Framstag corrected thruput value
* 4 Sep 96 Heiko some fixes for IRIX
* 24 Sep 96 Heiko added get_domainname()
* 19 Nov 96 Framstag fix for broken AIX include-files
* 29 Dec 96 Framstag moved get_domainname to reply.c
* 19 Jan 97 Framstag added "resuming at ..." output
* 25 Jan 97 Framstag send_data can now read from stdin, too
* 31 Jan 97 Framstag print final transfer statistic with quiet mode 1
* 24 Feb 97 Framstag sprintf() -> snprintf()
* 15 Jun 97 Framstag added file reading test mode
* corrected transfer statistics output
* 16 Jun 97 Hobel socket tuning
* 30 Jun 97 Framstag fixed bug in transfer statistics output
* 21 Aug 97 Framstag sendheader can handle better reply code 202
* 9 Dec 97 Framstag added sendcommand()
* 15 Dec 97 Framstag new parameter for send_data(): speed
* 27 Feb 98 Framstag more verbose transfer statistics
* 7 Mar 98 Framstag better detection of non-interactive mode
* added -i option for more transfer information
* 2 Apr 98 Framstag don't ask for server-reply on QUIT command
* 23 Jun 98 Framstag fixed byte counting bug in transfer statistics
* 4 Jul 98 Ako set tcp timeout option
* 7 Jul 98 Ako fixed fflush(NULL) bug for SunOS 4
* 21 Aug 98 Framstag added maximum thruput option for send_data
*
* Network routines for the the sendfile client of the sendfile package.
* Look at net.h for a list of the functions.
*
* Copyright © 1995-1998 Ulli Horlacher
* This file is covered by the GNU General Public License
*/
/*
#ifdef NEXT
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
typedef long daddr_t;
typedef char *caddr_t;
typedef long time_t;
#define _TIME_T
#endif
*/
#include "config.h" /* various definitions */
#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "string.h" /* extended string functions */
#include "message.h" /* information, warning and error messages */
#include "net.h" /* network stuff */
#include "io.h" /* socket read/write */
/* stupid AIX comes with no include files for networking and other stuff */
#if defined(AIX3) || defined(ULTRIX)
#include "bsd.h"
#endif
/*
#ifdef SOLARIS2
#ifdef _SVID_GETTOD
int gettimeofday(struct timeval *);
#else
int gettimeofday(struct timeval *, void *);
#endif
#endif
*/
#if defined(SOLARIS2)
#ifndef fileno
int fileno(FILE *);
#endif
#endif
/*
#ifdef IRIX
u_short htons(u_short hostshort);
#endif
*/
extern int client; /* client or server flag */
extern int verbose; /* flag for verbose mode */
extern char *prg; /* name of the game */
/*
* open_connection - open socket and connect to client
*
* INPUT: adr - ip address or name of server to connect to
* port - port number to connect to
*
* RETURN: socket file descriptor
* -1 if socket creation failed
* -2 if connection failed
* -3 if unknown host
*
* this function is derived from example code from
* "Unix Networking Programming" by W. R. Stevens
*/
int open_connection(char *adr, int port) {
int sockfd, /* socket file descriptor */
num=1; /* flag for numeric ip address */
struct sockaddr_in serv_addr; /* internet socket */
struct in_addr hostaddr;
struct hostent *hostp; /* host entity */
char *cp, /* character pointer */
hostip[17], /* server host ip address */
hostname[MAXLEN]; /* server host name */
/* split off port from hostname */
strcpy(hostname,adr);
if ((cp=strchr(hostname,':'))) *cp=0;
/* open socket */
sockfd=socket(AF_INET,SOCK_STREAM,0);
if (sockfd<0) return(-1);
#ifdef DEBUG
message(prg,'I',"socket ok");
#endif
/* initialisize serv_addr */
memset((char *) &serv_addr, 0, sizeof(serv_addr));
/* numeric oder symbolic ip address? */
for (cp=hostname; *cp>0; cp++) {
if (*cp>'@') {
num=0;
break;
}
}
/* quick hack: gethostbyname() does also work with numeric addresses */
num=0;
/* look for server host address */
if (num) {
hostaddr.s_addr=inet_addr(hostname);
hostp=gethostbyaddr((char *)&hostaddr,sizeof(hostaddr),AF_INET);
} else
hostp=gethostbyname(hostname);
if (hostp==NULL) return(-3);
/* convert binary structure to ASCII hostip */
strcpy(hostip,inet_ntoa(*(struct in_addr *) *hostp->h_addr_list));
#ifdef DEBUG
printf("host: %s\n",hostip);
#endif
/* fill out server address descriptor */
serv_addr.sin_family =AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(hostip);
serv_addr.sin_port =htons(port);
/* befor connecting, let's do some tuning :-) hobel */
{
int flag;
flag=1;
#ifdef SO_KEEPALIVE
if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
(void *)&flag, sizeof(flag))<0)
message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_NODELAY
flag=1;
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
(void *)&flag, sizeof(flag))<0)
message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_BUFFER
flag=TCP_BUFFER;
if(setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF,
(void *)&flag, sizeof(flag))<0)
message(prg,'W',"could not configure socket");
flag=TCP_BUFFER;
if(setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF,
(void *)&flag, sizeof(flag))<0)
message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_RFC1323
flag=1;
if(setsockopt(sockfd, IPPROTO_TCP,TCP_RFC1323,
(void *)&flag, sizeof(flag))<0)
message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_WINSHIFT
flag=1; /* this gives the power of two to be shifted */
if(setsockopt(sockfd, IPPROTO_TCP, TCP_WINSHIFT,
(void *)&flag, sizeof(flag))<0)
message(prg,'W',"could not configure socket");
#endif
}
/* connect to server */
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
return(-2);
#ifdef DEBUG
message(prg,'I',"connect ok");
#endif
return(sockfd);
}
/*
* sock_getline - get a (command) line from the network socket
*
* INPUT: fd - socket file descriptor
* line - empty string
*
* OUTPUT: line - read string
*
* RETURN: number of read bytes, -1 on error
*
* this function is derived from example code from
* "Unix Networking Programming" by W. R. Stevens
*/
int sock_getline(int fd, char *line) {
int n, rc;
unsigned char c;
char tmp[MAXLEN];
*line=0;
for (n=0; n<MAXLEN-1; n++) {
rc=read(fd,&c,1);
if (rc==1) {
line[n]=c;
if (c=='\n') break;
} else
return(-1);
}
/* too much input? */
if (n+1==MAXLEN && line[n] != '\n') {
if (client) {
errno=0;
snprintf(MAXS(tmp),"network socket data overrun (read bytes: %d)",n);
message("",'E',tmp);
message("",'F',line);
}
return(-1);
}
/* remove trailing cr or lf */
line[n]=0;
if (n>0 && line[n-1]=='\r') line[--n]=0;
/* on verbose mode show the whole line */
if (verbose) printf("%s\n",line);
return(n);
}
/*
* sock_putline - send a line to the network socket
*
* INPUT: fd - socket file descriptor
* line - string to send
*
* RETURN: number of send bytes
*/
int sock_putline(int fd, const char *line) {
int n; /* number of send bytes */
char cmd[MAXLEN]; /* command line to send */
/* prepare string */
strcpy(cmd,line);
strcat(cmd,"\r\n");
/* on verbose mode show what goes up */
if (verbose) printf("-> %s\n",line);
/* and up and away :-) */
if (fd)
n=writen(fd,cmd,strlen(cmd));
else
/* test mode, no real sending */
n=strlen(cmd);
return(n);
}
/*
* getreply - get the reply on a command from the server
*
* INPUT: fd - socket file descriptor
*
* RETURN: the reply line string
*/
char *getreply(int fd) {
int len; /* reply message length */
char msg[MAXLEN]; /* intermediate information/error message */
static char reply[MAXLEN]; /* reply string from server */
do {
if (fd) {
/* get the next reply line */
len=sock_getline(fd,reply);
/* link failure? */
if (len<0) {
errno=0;
strcpy(msg,"server has closed the connection");
if (*reply) snprintf(MAXS(msg),"%s, last data: \"%s\"",msg,reply);
if (client) {
errno=0;
message("",'F',msg);
}
return("");
}
/* reply message too short? */
if (len<4) {
errno=0;
snprintf(MAXS(msg),"corrupt reply: \"%s\"",reply);
if (client) {
errno=0;
message(prg,'F',msg);
}
return("");
}
} else
/* test mode without network connection */
strcpy(reply,"222 test mode");
} while (reply[3]=='-');
/* quit if there was a fatal server error */
if (reply[0]=='4') {
errno=0;
snprintf(MAXS(msg),"server error: %s",&reply[4]);
if (client) {
errno=0;
message(prg,'F',msg);
}
return("");
}
return(reply);
}
/*
* sendcommand - send a command line to the network socket and get the answer
*
* INPUT: fd - socket file descriptor
* command - string to send
*
* OUTPUT: answer - answer string
*
* RETURN: answer string
*
* If answer is the NULL-pointer, the answer will bis discared.
* See also sendheader()
* Hack: because some SAFT-servers hang on the QUIT command, we don't ask for
* the reply in this case.
*/
char *sendcommand(int fd, const char *command, char *answer) {
if (answer) *answer=0;
sock_putline(fd,command);
if (answer)
strcpy(answer,getreply(fd));
else if (!str_eq(command,"QUIT")) getreply(fd);
return(answer);
}
/*
* sendheader - send a headerline and check the reply code
*
* INPUT: fd - socket file descriptor
* line - header line
*
* RETURN: 0 on sucess, -1 on server fatal error, 1 on header ignored
*
* Return value -1 never occurs because the program will terminate before
* See also sendcommand()
*/
int sendheader(int fd, char *line) {
char msg[MAXLEN], /* intermediate information/error message */
*reply; /* reply string from server */
/* on test mode return */
if (!fd) return(0);
/* send the header line */
sock_putline(fd,line);
/* server reply ok? */
reply=getreply(fd);
if (str_beq(reply,"200")) return(0);
if (str_beq(reply,"202")) return(1);
errno=0;
snprintf(MAXS(msg),"server error: %s",&reply[4]);
message(prg,'F',msg);
return(-1);
}
/*
* send_data - send file data
*
* INPUT: sockfd - socket file descriptor
* size - bytes to send
* file - file to send ("" if read from stdin)
* tinfo - additional transfer information
* iso_name - name of the original file
* type - file type string
* mtp - maximum thruput
*
* OUTPUT: ttime - transfer time in ms
*
* RETURN: 0 if ok, 1 if already transfered, -1 if transfer failed
*/
int send_data(int sockfd, unsigned long size, const char *file,
const char *tinfo, const char *iso_name, const char *type,
float mtp, float *ttime) {
int
n, /* simple loop count */
nblocks, /* number of packets blocks */
bn, /* block number to read */
percent, /* what percentage of file has been transmitted */
ffd; /* file to send file descriptor */
unsigned long
msec, /* milliseconds of transfer */
bytes, /* bytes which has been sent */
offset; /* bytes already sent */
float
thruput; /* network thruput */
char
packet[OVERSIZE], /* data packet to send */
tmp[MAXLEN], /* temporary string */
fname[MAXLEN], /* file name and compress info */
*reply; /* reply string */
time_t
sec0,sec1,sec2; /* unix time */
struct timeval tv1,tv2;
#if !defined(SOLARIS2) && !defined(IRIX)
struct timezone tz;
#endif
extern int
quiet, /* quiet mode flag */
packet_size; /* size of a packet in bytes */
msec=0;
offset=0;
/* fallback */
if (packet_size<1) packet_size=512;
if (ttime) *ttime=0;
strcpy(fname,iso_name);
if (*type) {
strcat(fname," (");
strcat(fname,type);
strcat(fname,")");
}
if (!quiet) printf("sending... \r");
fflush(stdout);
/* real sending and no local testing? */
if (sockfd) {
/* real file to send? ==> ask for resend */
if (*file) {
sock_putline(sockfd,"RESEND");
reply=getreply(sockfd);
/* correct answer? */
if (!str_beq(reply,"500 ") && !str_beq(reply,"502 ")) {
/* error occured? */
if (!str_beq(reply,"230 ")) {
if (quiet<3) {
errno=0;
snprintf(MAXS(tmp),"server error: %s",&reply[4]);
message("",'F',tmp);
}
return(-1);
}
sscanf(&reply[4],"%ld",&offset);
}
}
/* prepare sending of data */
sock_putline(sockfd,"DATA");
reply=getreply(sockfd);
/* file already transmitted? */
if (str_beq(reply,"531 ")) {
snprintf(MAXS(tmp),
"file %s has been already transmitted - ignored.",iso_name);
if (quiet<2) message("",'W',tmp);
return(1);
}
/* server reply ok? */
if (!str_beq(reply,"302 ")) {
if (quiet<3) {
snprintf(MAXS(tmp),"corrupt server reply: %s",&reply[4]);
errno=0;
message("",'F',tmp);
}
return(-1);
}
}
/* open file */
if (*file) {
ffd=open(file,O_RDONLY,0);
if (ffd<0 || lseek(ffd,offset,SEEK_SET)<0) {
if (quiet<3) {
snprintf(MAXS(tmp),"error reading %s",iso_name);
message("",'E',tmp);
}
return(-1);
}
} else
ffd=fileno(stdin);
/* resend active? */
if (offset) {
snprintf(MAXS(tmp),"resuming %s at byte %ld",iso_name,offset);
if (quiet<2) message("",'I',tmp);
}
if (quiet==1) {
snprintf(MAXS(tmp),"begin transfer of %s with %lu bytes",fname,size);
message("",'I',tmp);
}
/* get time normal */
#if defined(SOLARIS2) || defined(IRIX)
#ifdef _SVID_GETTOD
gettimeofday(&tv1);
#else
gettimeofday(&tv1,NULL);
#endif
#else
gettimeofday(&tv1,&tz);
#endif
sec1=0;
sec0=time(0);
/* send the file data in packets */
bytes=0;
size=size-offset;
nblocks=size/packet_size;
for (bn=1; bn<=nblocks; bn++) {
if (readn(ffd,packet,packet_size)<packet_size) {
if (quiet<3) {
if (!quiet) printf("\n");
snprintf(MAXS(tmp),"error reading %s",iso_name);
message("",'E',tmp);
}
close(ffd);
return(-1);
}
if (sockfd && writen(sockfd,packet,packet_size)<packet_size) {
if (quiet<3) {
if (!quiet) printf("\n");
errno=0;
message("",'F',"error sending data");
}
close(ffd);
return(-1);
}
/* print transaction message once a second */
bytes+=packet_size;
percent=(bytes+offset)*100.0/(size+offset);
if (!quiet) {
sec2=time(0);
if (sec2>sec1) {
fprintf(stderr,"%s%s: %3d%% (%ld of %ld kB)\r",
tinfo,fname,percent,
(bytes+offset-1)/1024+1,(size+offset-1)/1024+1);
fflush(stderr);
sec1=sec2;
}
}
/* limit maximum thruput */
if (mtp) while (bytes/(time(0)-sec0+0.001)>mtp*1024) sleep(1);
}
/* send the last bytes */
if ((n=size-nblocks*packet_size) > 0) {
if (readn(ffd,packet,n)<n) {
if (quiet<3) {
snprintf(MAXS(tmp),"error reading %s",iso_name);
message("",'E',tmp);
}
close(ffd);
return(-1);
}
if (sockfd && writen(sockfd,packet,n)<n) {
if (quiet<3) {
errno=0;
message("",'F',"error sending data");
} else
return(-1);
}
bytes+=n;
}
close(ffd);
if (quiet<2) {
/* get time difference */
#if defined(SOLARIS2) || defined(IRIX)
#ifdef _SVID_GETTOD
gettimeofday(&tv2);
#else
gettimeofday(&tv2,NULL);
#endif
#else
gettimeofday(&tv2,&tz);
#endif
msec=(tv2.tv_usec-tv1.tv_usec)/1000+(tv2.tv_sec-tv1.tv_sec)*1000;
if (msec<1) msec=1;
thruput=bytes*1000.0/msec;
/* print transfer statistics */
if (quiet==1) {
if (thruput>9999)
snprintf(MAXS(tmp),
"transfer of %s completed: %.1f kB/s",fname,thruput/1024);
else
snprintf(MAXS(tmp),
"transfer of %s completed: %d byte/s",fname,(int)thruput);
message("",'I',tmp);
} else {
fprintf(stderr,"%s%s: 100%% (",tinfo,fname);
if (bytes>9999)
fprintf(stderr,"%ld kB, ",bytes/1024);
else
fprintf(stderr,"%ld byte, ",bytes);
if (thruput>9999)
fprintf(stderr,"%.1f kB/s) \n",thruput/1024);
else
fprintf(stderr,"%d byte/s) \n",(int)thruput);
fflush(stderr);
}
}
if (ttime) *ttime=(float)msec;
/* transfer ok? */
if (sockfd && !str_beq(getreply(sockfd),"201 ")) {
if (quiet<3) {
snprintf(MAXS(tmp),"transfer failed for %s",iso_name);
errno=0;
message("",'E',tmp);
}
return(-1);
}
return(0);
}
syntax highlighted by Code2HTML, v. 0.9.1