/* * lftp - file transfer program * * Copyright (c) 1996-2020 by Alexander V. Lukyanov (lav@yars.free.net) * * 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 3 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 . */ #include #include #include #include #include #include #include #include #ifdef HAVE_NETINET_IN_SYSTM_H # include #endif #ifdef HAVE_NETINET_IP_H # include #endif #ifdef HAVE_NETINET_TCP_H # include #endif #ifdef HAVE_SYS_IOCTL_H # include #endif #ifdef HAVE_TERMIOS_H # include #endif #include "SMTask.h" #include "network.h" #include "ResMgr.h" #include "ProtoLog.h" #include "xstring.h" const char *sockaddr_u::address() const { #ifdef HAVE_GETNAMEINFO static char buf[NI_MAXHOST]; if(getnameinfo(&sa,addr_len(),buf,NI_MAXHOST,0,0,NI_NUMERICHOST)<0) return "????"; return buf; #else static char buf[16]; if(sa.sa_family!=AF_INET) return "????"; unsigned char *a=(unsigned char *)&in.sin_addr; snprintf(buf,16,"%u.%u.%u.%u",a[0],a[1],a[2],a[3]); return buf; #endif } int sockaddr_u::port() const { if(sa.sa_family==AF_INET) return ntohs(in.sin_port); #if INET6 if(sa.sa_family==AF_INET6) return ntohs(in6.sin6_port); #endif return 0; } void sockaddr_u::set_port(int port) { if(sa.sa_family==AF_INET) in.sin_port=htons(port); #if INET6 if(sa.sa_family==AF_INET6) in6.sin6_port=htons(port); #endif } const xstring& sockaddr_u::to_xstring() const { return xstring::format("[%s]:%d",address(),port()); } bool sockaddr_u::is_reserved() const { if(sa.sa_family==AF_INET) { unsigned char *a=(unsigned char *)&in.sin_addr; return (a[0]==0) || (a[0]==127 && !is_loopback()) || (a[0]>=240); } #if INET6 if(family()==AF_INET6) { return IN6_IS_ADDR_UNSPECIFIED(&in6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&in6.sin6_addr) || IN6_IS_ADDR_V4COMPAT(&in6.sin6_addr); } #endif return false; } bool sockaddr_u::is_multicast() const { if(sa.sa_family==AF_INET) { unsigned char *a=(unsigned char *)&in.sin_addr; return (a[0]>=224 && a[0]<240); } #if INET6 if(family()==AF_INET6) return IN6_IS_ADDR_MULTICAST(&in6.sin6_addr); #endif return false; } bool sockaddr_u::is_private() const { if(sa.sa_family==AF_INET) { unsigned char *a=(unsigned char *)&in.sin_addr; return (a[0]==10) || (a[0]==172 && a[1]>=16 && a[1]<32) || (a[0]==192 && a[1]==168) || (a[0]==169 && a[1]==254); // self-assigned } #if INET6 if(family()==AF_INET6) { return IN6_IS_ADDR_SITELOCAL(&in6.sin6_addr) || IN6_IS_ADDR_LINKLOCAL(&in6.sin6_addr); } #endif return false; } bool sockaddr_u::is_loopback() const { if(sa.sa_family==AF_INET) { unsigned char *a=(unsigned char *)&in.sin_addr; return (a[0]==127 && a[1]==0 && a[2]==0 && a[3]==1); } #if INET6 if(sa.sa_family==AF_INET6) return IN6_IS_ADDR_LOOPBACK(&in6.sin6_addr); #endif return false; } bool sockaddr_u::is_compatible(const sockaddr_u& o) const { return family()==o.family() && !is_multicast() && !o.is_multicast() && !is_reserved() && !o.is_reserved() && is_private()==o.is_private() && is_loopback()==o.is_loopback(); } bool sockaddr_u::set_compact(const char *c,size_t len) { if(len==4) { sa.sa_family=AF_INET; memcpy(&in.sin_addr,c,4); in.sin_port=0; return true; #if INET6 } else if(len==16) { sa.sa_family=AF_INET6; memcpy(&in6.sin6_addr,c,16); return true; #endif } else if(len==6) { sa.sa_family=AF_INET; memcpy(&in.sin_addr,c,4); in.sin_port=htons((c[5]&255)|((c[4]&255)<<8)); return true; #if INET6 } else if(len==18) { sa.sa_family=AF_INET6; memcpy(&in6.sin6_addr,c,16); in6.sin6_port=htons((c[17]&255)|((c[16]&255)<<8)); return true; #endif } return false; } const sockaddr_compact& sockaddr_u::compact() const { sockaddr_compact& c=compact_addr(); int p=port(); if(c.length() && p) { c.append(char(p>>8)); c.append(char(p&255)); } return c; } sockaddr_compact& sockaddr_u::compact_addr() const { sockaddr_compact& c=sockaddr_compact::get_tmp(); if(family()==AF_INET) c.append((const char*)&in.sin_addr,4); #if INET6 else if(family()==AF_INET6) c.append((const char*)&in6.sin6_addr,16); #endif return c; } void Networker::NonBlock(int fd) { int fl=fcntl(fd,F_GETFL); fcntl(fd,F_SETFL,fl|O_NONBLOCK); } void Networker::CloseOnExec(int fd) { fcntl(fd,F_SETFD,FD_CLOEXEC); } static int one=1; void Networker::KeepAlive(int sock) { setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,(char*)&one,sizeof(one)); } void Networker::MinimizeLatency(int sock) { #ifdef IP_TOS int tos = IPTOS_LOWDELAY; setsockopt(sock, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)); #endif } void Networker::MaximizeThroughput(int sock) { #ifdef IP_TOS int tos = IPTOS_THROUGHPUT; setsockopt(sock, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)); #endif } void Networker::ReuseAddress(int sock) { setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char*)&one,sizeof(one)); } void Networker::SetSocketBuffer(int sock,int socket_buffer) { if(socket_buffer==0) return; if(-1==setsockopt(sock,SOL_SOCKET,SO_SNDBUF,(char*)&socket_buffer,sizeof(socket_buffer))) ProtoLog::LogError(1,"setsockopt(SO_SNDBUF,%d): %s",socket_buffer,strerror(errno)); if(-1==setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(char*)&socket_buffer,sizeof(socket_buffer))) ProtoLog::LogError(1,"setsockopt(SO_RCVBUF,%d): %s",socket_buffer,strerror(errno)); } void Networker::SetSocketMaxseg(int sock,int socket_maxseg) { #ifndef SOL_TCP # define SOL_TCP IPPROTO_TCP #endif #ifdef TCP_MAXSEG if(socket_maxseg==0) return; if(-1==setsockopt(sock,SOL_TCP,TCP_MAXSEG,(char*)&socket_maxseg,sizeof(socket_maxseg))) ProtoLog::LogError(1,"setsockopt(TCP_MAXSEG,%d): %s",socket_maxseg,strerror(errno)); #endif } int Networker::SocketCreateUnbound(int af,int type,int proto,const char *hostname) { int s=socket(af,type,proto); if(s<0) return s; NonBlock(s); CloseOnExec(s); SetSocketBuffer(s,ResMgr::Query("net:socket-buffer",hostname)); return s; } bool sockaddr_u::set_defaults(int af,const char *hostname,int port) { memset(this,0,sizeof(*this)); sa.sa_family=af; const char *b=0; if(af==AF_INET) { b=ResMgr::Query("net:socket-bind-ipv4",hostname); if(!(b && b[0] && inet_pton(af,b,&in.sin_addr))) b=0; in.sin_port=htons(port); } #if INET6 else if(af==AF_INET6) { b=ResMgr::Query("net:socket-bind-ipv6",hostname); if(!(b && b[0] && inet_pton(af,b,&in6.sin6_addr))) b=0; in6.sin6_port=htons(port); } #endif return b || port; } void Networker::SocketBindStd(int s,int af,const char *hostname,int port) { sockaddr_u bind_addr; if(bind_addr.set_defaults(af,hostname,port)) { if(bind_addr.bind_to(s)==-1) ProtoLog::LogError(0,"bind(%s): %s",bind_addr.to_string(),strerror(errno)); } } int Networker::SocketCreate(int af,int type,int proto,const char *hostname) { int s=SocketCreateUnbound(af,type,proto,hostname); if(s<0) return s; SocketBindStd(s,af,hostname); return s; } void Networker::SocketTuneTCP(int s,const char *hostname) { KeepAlive(s); SetSocketMaxseg(s,ResMgr::Query("net:socket-maxseg",hostname)); } int Networker::SocketCreateTCP(int af,const char *hostname) { int s=SocketCreate(af,SOCK_STREAM,IPPROTO_TCP,hostname); if(s<0) return s; SocketTuneTCP(s,hostname); return s; } int Networker::SocketCreateUnboundTCP(int af,const char *hostname) { int s=SocketCreateUnbound(af,SOCK_STREAM,IPPROTO_TCP,hostname); if(s<0) return s; SocketTuneTCP(s,hostname); return s; } int Networker::SocketConnect(int fd,const sockaddr_u *u) { // some systems have wrong connect() prototype, so we have to cast off const. // in any case, connect does not alter the address. int res=connect(fd,(sockaddr*)&u->sa,SocketAddrLen(u)); if(res!=-1) SMTask::UpdateNow(); // if non-blocking doesn't work return res; } int Networker::SocketAccept(int fd,sockaddr_u *u,const char *hostname) { socklen_t len=sizeof(*u); int a=accept(fd,&u->sa,&len); if(a<0) return a; NonBlock(a); CloseOnExec(a); KeepAlive(a); SetSocketBuffer(a,ResMgr::Query("net:socket-buffer",hostname)); SetSocketMaxseg(a,ResMgr::Query("net:socket-maxseg",hostname)); return a; } void Networker::SocketSinglePF(int s,int pf) { #if INET6 && defined(IPV6_V6ONLY) if(pf==PF_INET6) { int on = 1; if(-1==setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on, sizeof(on))) ProtoLog::LogError(1,"setsockopt(IPV6_V6ONLY): %s",strerror(errno)); } #endif } #ifdef TIOCOUTQ static bool TIOCOUTQ_returns_free_space; static bool TIOCOUTQ_works; static bool TIOCOUTQ_tested; static void test_TIOCOUTQ() { int sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); if(sock==-1) return; TIOCOUTQ_tested=true; int avail=-1; socklen_t len=sizeof(avail); if(getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(char*)&avail,&len)==-1) avail=-1; int buf=-1; if(ioctl(sock,TIOCOUTQ,&buf)==-1) buf=-1; if(buf>=0 && avail>0 && (buf==0 || buf==avail)) { TIOCOUTQ_works=true; TIOCOUTQ_returns_free_space=(buf==avail); } close(sock); } #endif int Networker::SocketBuffered(int sock) { #ifdef TIOCOUTQ if(!TIOCOUTQ_tested) test_TIOCOUTQ(); if(!TIOCOUTQ_works) return 0; int buffer=0; if(TIOCOUTQ_returns_free_space) { socklen_t len=sizeof(buffer); if(getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(char*)&buffer,&len)==-1) return 0; int avail=buffer; if(ioctl(sock,TIOCOUTQ,&avail)==-1) return 0; if(avail>buffer) return 0; // something wrong buffer-=avail; buffer=buffer*3/4; // approx... } else { if(ioctl(sock,TIOCOUTQ,&buffer)==-1) return 0; } return buffer; #else return 0; #endif } #ifdef HAVE_IFADDRS_H #include #endif const char *Networker::FindGlobalIPv6Address() { #if INET6 && defined(HAVE_IFADDRS_H) struct ifaddrs *ifaddrs=0; getifaddrs(&ifaddrs); for(struct ifaddrs *ifa=ifaddrs; ifa; ifa=ifa->ifa_next) { if(ifa->ifa_addr && ifa->ifa_addr->sa_family==AF_INET6) { struct in6_addr *addr=&((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr; if(!IN6_IS_ADDR_UNSPECIFIED(addr) && !IN6_IS_ADDR_LOOPBACK(addr) && !IN6_IS_ADDR_LINKLOCAL(addr) && !IN6_IS_ADDR_SITELOCAL(addr) && !IN6_IS_ADDR_MULTICAST(addr)) { char *buf=xstring::tmp_buf(INET6_ADDRSTRLEN); inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN); freeifaddrs(ifaddrs); return buf; } } } freeifaddrs(ifaddrs); #endif return 0; } bool Networker::CanCreateIpv6Socket() { #if INET6 bool can=true; int s=socket(AF_INET6,SOCK_STREAM,IPPROTO_TCP); if(s==-1 && (errno==EINVAL #ifdef EAFNOSUPPORT || errno==EAFNOSUPPORT #endif )) can=false; if(s!=-1) close(s); return can; #else return false; #endif }