// -*- c++ -*- //------------------------------------------------------------------------------ // IPv4Socket.cpp //------------------------------------------------------------------------------ // Copyright (C) 1997-2002 Vladislav Grinchenko // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. //------------------------------------------------------------------------------ #if defined(WIN32) # include # include #else # include #endif #include "assa/MemDump.h" #include "assa/IPv4Socket.h" using namespace ASSA; Streambuf* IPv4Socket:: rdbuf (Streambuf* sb_) { trace_with_mask("IPv4Socket::rdbuf(sb_)",SOCKTRACE); if (sb_ == 0 || sb_ == m_rdbuf) { return (sb_); } Streambuf* old = m_rdbuf; m_rdbuf = sb_; return (old); } bool IPv4Socket:: open (const int domain_) { trace_with_mask("IPv4Socket::open",SOCKTRACE); m_type = domain_; m_fd = ::socket(domain_, SOCK_STREAM, 0); if (!is_valid_handler (m_fd)) { EL((ASSAERR,"OS::socket() error: m_fd = %d\n", m_fd)); setstate (Socket::failbit); disable_handler (m_fd); return (false); } DL ((SOCK,"domain = %d, m_fd = %d\n", domain_, m_fd)); clear (); turnOptionOn (Socket::nonblocking); return (true); } bool IPv4Socket:: close() { trace_with_mask("IPv4Socket::close()",SOCKTRACE); if (is_valid_handler (m_fd)) { DL((SOCK,"Closed FD: %d\n",m_fd)); /*--- Flush data in output stream buffer ---*/ flush (); close_handler(m_fd); setstate (Socket::failbit); /*--- Socket can be re-opened in the future. If there is some bytes left in it since last read(2), clean them up. ---*/ if (m_rdbuf && m_rdbuf->in_avail ()) { for (int c; (c=m_rdbuf->sbumpc ()) != EOF;) { } } } return (true); } bool IPv4Socket:: connect (const Address& his_address_) { trace_with_mask("IPv4Socket::connect()",SOCKTRACE); if (!is_valid_handler (m_fd) && open (getDomain()) == false) { return false; } int ret = ::connect (m_fd, (SA*) his_address_.getAddress(), his_address_.getLength()); if (ret < 0) { int e = get_errno (); // is ASYNC connect in progress? if (e == EINPROGRESS || e == EWOULDBLOCK) { DL((SOCK,"FD: %d OS::connect() error\n",m_fd)); } else { EL((SOCK,"FD: %d OS::connect() error\n",m_fd)); } return (false); } clear (); DL((SOCK,"Connection opened on FD: %d\n", m_fd)); return (true); } bool IPv4Socket:: bind (const Address& addr_) { trace_with_mask("IPv4Socket::bind",SOCKTRACE); #if !defined(WIN32) /** * If UNIX domain, save the path */ if ( getDomain() == AF_UNIX ) { char* p = ((SA_UN *) addr_.getAddress())->sun_path; m_path = new char[strlen(p)+1]; strcpy(m_path, p); struct stat sb; if (stat (m_path, &sb) == 0) { if ( S_ISSOCK(sb.st_mode) || S_ISFIFO(sb.st_mode) ) { unlink(m_path); } } } #endif /*--- From Stevens, Ch 7.5 (p.196): "Set the SO_REUSEADDR socket option before calling bind(2) in all TCP servers." ---*/ Assure_return ( turnOptionOn (reuseaddr) ); int rt = ::bind(m_fd, addr_.getAddress(), addr_.getLength()); if ( rt < 0) { EL((SOCK,"::bind() FD: %d failed\n",m_fd)); setstate (Socket::failbit); return (false); } Assure_return ( (::listen(m_fd, 5) == 0) ); return (true); } /** Here's an interesting spot - because accept() suppose to work both for INET and UNIX domain socket addresses, we have to allocate enough space and pass exact size of the address type expected. Otherwise, if we use, for example, struct sockaddr_un as max. and accept returns struct sockaddr_in, we can cast back to struct sockaddr_in, but internally address data members are not guaranteed to be aligned correctly!!! */ IPv4Socket* IPv4Socket:: accept () { trace_with_mask("IPv4Socket::accept",SOCKTRACE); socklen_t length = 0; SA* remote_address = NULL; handler_t new_fd; disable_handler (new_fd); if ( getDomain() == AF_UNIX ) { length = sizeof(struct sockaddr_in); remote_address = (SA*) new SA_IN; } else /* AF_INET */ { remote_address = (SA*) new SA_UN; length = sizeof(struct sockaddr_un); } memset(remote_address, 0, length); #if !defined (_CYGWIN32__) new_fd = ::accept(m_fd, remote_address, &length); #else new_fd = ::accept(m_fd, remote_address, (int*)&length); #endif if (!is_valid_handler (new_fd)) { EL((ASSAERR,"::accept() failed (new_fd=%d)\n", new_fd)); close(); return NULL; } if (length == sizeof(SA_IN)) { SA_IN* sa_in = (SA_IN*) remote_address; DL((SOCK,"Accepted new TCP connection from Addr %s, port %d\n", inet_ntoa(sa_in->sin_addr), ntohs( sa_in->sin_port))); } else { #if !defined(WIN32) SA_UN* sa_un = (SA_UN*) remote_address; DL((SOCK,"Accepted new UNIX connection from %s\n", sa_un->sun_path)); #endif } delete remote_address; IPv4Socket* s = new IPv4Socket (new_fd); s->clear (); s->turnOptionOn (Socket::nonblocking); return s; } int IPv4Socket:: read (char* packet_, const unsigned int size_) { trace_with_mask("IPv4Socket::read",SOCKTRACE); register int len; register int sz = size_; char* tmp = packet_; if (!is_valid_handler (m_fd) < 0) { return -1; } len = 0; if (m_rdbuf->unbuffered ()) { /* --- This needs to be redesigned --- I should read a character at a time in loop, until I get all characters, or EWOULDBLOCK or EOF. If ::read() returns 0 or -1, it will be converted by sbumpc() into EOF. Otherwise, sbumpc() returns character read. Is this the right thing here to do? */ if ((len = m_rdbuf->sbumpc ()) >= 0) { *tmp = len; len = 1; } } else { len = m_rdbuf->sgetn (tmp, sz); } if (len == -1) { /** Non-blocking socket delivered partial packet. */ if (get_errno () != EWOULDBLOCK) { EL((ASSAERR,"::read (fd=%d) failed.\n",m_fd)); setstate (Socket::failbit); } return len; } tmp += len; sz -= len; if ((size_ - sz) == 0) { DL((SOCK,"Peer has dropped connection FD: %d\n",m_fd)); setstate (Socket::failbit | Socket::eofbit); return 0; } DL((SOCKTRACE,"==> FD: %d Received %d bytes\n", m_fd, size_ - sz)); MemDump::dump_to_log (SOCKTRACE, "Data received:", packet_, size_ - sz); /* Return number of bytes read. If all requested bytes have been read, then sz is 0 and size_ is returned. If sz != 0, then writer has sent us a partial packet. */ return (size_ - sz); } int IPv4Socket:: write(const char* packet_, const unsigned int size_) { trace_with_mask("IPv4Socket::write()",SOCKTRACE); int ret = 0; if (!is_valid_handler (m_fd)) { return -1; } if (m_rdbuf->unbuffered ()) { int wlen = size_; char* p = (char*) packet_; while (wlen-- > 0) { if (m_rdbuf->sputc (*p++) == EOF) { return (EOF); } } ret = p - packet_; } else { ret = m_rdbuf->sputn ((char*) packet_, size_); } if (ret > 0) { DL((SOCK,"<= FD: %d Wrote %d bytes (requested %d bytes)\n", m_fd, ret, size_)); MemDump::dump_to_log (SOCK, "Data written", (char*)packet_, ret); } return ret; } IPv4Socket* IPv4Socket:: clone () const { const char self[] = "IPv4Socket::clone"; trace_with_mask(self,SOCKTRACE); handler_t nfd = dup (m_fd); IPv4Socket* s = new IPv4Socket (nfd); DL((SOCK,"Original socket has %d bytes in its get_area\n", m_rdbuf->in_avail ())); if (!is_valid_handler (nfd) || !good ()) { s->setstate (Socket::failbit); } else { s->clear (); } return s; }