| /* |
| * |
| * 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. |
| * |
| */ |
| |
| #include "qpid/sys/posix/BSDSocket.h" |
| |
| #include "qpid/sys/SocketAddress.h" |
| #include "qpid/sys/posix/check.h" |
| #include "qpid/sys/posix/PrivatePosix.h" |
| |
| #include <fcntl.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/errno.h> |
| #include <unistd.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <netdb.h> |
| #include <cstdlib> |
| #include <string.h> |
| |
| namespace qpid { |
| namespace sys { |
| |
| namespace { |
| std::string getName(int fd, bool local) |
| { |
| ::sockaddr_storage name_s; // big enough for any socket address |
| ::sockaddr* name = (::sockaddr*)&name_s; |
| ::socklen_t namelen = sizeof(name_s); |
| |
| if (local) { |
| QPID_POSIX_CHECK( ::getsockname(fd, name, &namelen) ); |
| } else { |
| QPID_POSIX_CHECK( ::getpeername(fd, name, &namelen) ); |
| } |
| |
| return SocketAddress::asString(name, namelen); |
| } |
| |
| uint16_t getLocalPort(int fd) |
| { |
| ::sockaddr_storage name_s; // big enough for any socket address |
| ::sockaddr* name = (::sockaddr*)&name_s; |
| ::socklen_t namelen = sizeof(name_s); |
| |
| QPID_POSIX_CHECK( ::getsockname(fd, name, &namelen) ); |
| |
| return SocketAddress::getPort(name); |
| } |
| } |
| |
| BSDSocket::BSDSocket() : |
| fd(-1), |
| handle(new IOHandle), |
| nonblocking(false), |
| nodelay(false) |
| {} |
| |
| Socket* createSocket() |
| { |
| return new BSDSocket; |
| } |
| |
| BSDSocket::BSDSocket(int fd0) : |
| fd(fd0), |
| handle(new IOHandle(fd)), |
| nonblocking(false), |
| nodelay(false) |
| {} |
| |
| BSDSocket::~BSDSocket() |
| {} |
| |
| BSDSocket::operator const IOHandle&() const |
| { |
| return *handle; |
| } |
| |
| void BSDSocket::createSocket(const SocketAddress& sa) const |
| { |
| int& socket = fd; |
| if (socket != -1) BSDSocket::close(); |
| int s = ::socket(getAddrInfo(sa).ai_family, getAddrInfo(sa).ai_socktype, 0); |
| if (s < 0) throw QPID_POSIX_ERROR(errno); |
| socket = s; |
| *handle = IOHandle(s); |
| |
| try { |
| if (nonblocking) setNonblocking(); |
| if (nodelay) setTcpNoDelay(); |
| if (getAddrInfo(sa).ai_family == AF_INET6) { |
| int flag = 1; |
| int result = ::setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&flag, sizeof(flag)); |
| QPID_POSIX_CHECK(result); |
| } |
| } catch (std::exception&) { |
| ::close(s); |
| socket = -1; |
| *handle = IOHandle(); |
| throw; |
| } |
| } |
| |
| void BSDSocket::setNonblocking() const { |
| int& socket = fd; |
| nonblocking = true; |
| if (socket != -1) { |
| QPID_POSIX_CHECK(::fcntl(socket, F_SETFL, O_NONBLOCK)); |
| } |
| } |
| |
| void BSDSocket::setTcpNoDelay() const |
| { |
| int& socket = fd; |
| nodelay = true; |
| if (socket != -1) { |
| int flag = 1; |
| int result = ::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); |
| QPID_POSIX_CHECK(result); |
| } |
| } |
| |
| void BSDSocket::connect(const SocketAddress& addr) const |
| { |
| // The display name for an outbound connection needs to be the name that was specified |
| // for the address rather than a resolved IP address as we don't know which of |
| // the IP addresses is actually the one that will be connected to. |
| peername = addr.asString(false); |
| |
| // However the string we compare with the local port must be numeric or it might not |
| // match when it should as getLocalAddress() will always be numeric |
| std::string connectname = addr.asString(); |
| |
| createSocket(addr); |
| |
| const int& socket = fd; |
| // TODO the correct thing to do here is loop on failure until you've used all the returned addresses |
| if ((::connect(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) < 0) && |
| (errno != EINPROGRESS)) { |
| throw Exception(QPID_MSG(strError(errno) << ": " << peername)); |
| } |
| // When connecting to a port on the same host which no longer has |
| // a process associated with it, the OS occasionally chooses the |
| // remote port (which is unoccupied) as the port to bind the local |
| // end of the socket, resulting in a "circular" connection. |
| // |
| // Raise an error if we see such a connection, since we know there is |
| // no listener on the peer address. |
| // |
| if (getLocalAddress() == connectname) { |
| close(); |
| throw Exception(QPID_MSG("Connection refused: " << peername)); |
| } |
| } |
| |
| void BSDSocket::finishConnect(const SocketAddress&) const |
| { |
| } |
| |
| void |
| BSDSocket::close() const |
| { |
| int& socket = fd; |
| if (socket == -1) return; |
| if (::close(socket) < 0) throw QPID_POSIX_ERROR(errno); |
| socket = -1; |
| *handle = IOHandle(); |
| } |
| |
| int BSDSocket::listen(const SocketAddress& sa, int backlog) const |
| { |
| createSocket(sa); |
| |
| const int& socket = fd; |
| int yes=1; |
| QPID_POSIX_CHECK(::setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))); |
| |
| if (::bind(socket, getAddrInfo(sa).ai_addr, getAddrInfo(sa).ai_addrlen) < 0) |
| throw Exception(QPID_MSG("Can't bind to port " << sa.asString() << ": " << strError(errno))); |
| if (::listen(socket, backlog) < 0) |
| throw Exception(QPID_MSG("Can't listen on port " << sa.asString() << ": " << strError(errno))); |
| |
| return getLocalPort(socket); |
| } |
| |
| Socket* BSDSocket::accept() const |
| { |
| int afd = ::accept(fd, 0, 0); |
| if ( afd >= 0) { |
| BSDSocket* s = new BSDSocket(afd); |
| s->localname = localname; |
| return s; |
| } |
| else if (errno == EAGAIN) |
| return 0; |
| else throw QPID_POSIX_ERROR(errno); |
| } |
| |
| int BSDSocket::read(void *buf, size_t count) const |
| { |
| return ::read(fd, buf, count); |
| } |
| |
| int BSDSocket::write(const void *buf, size_t count) const |
| { |
| return ::write(fd, buf, count); |
| } |
| |
| std::string BSDSocket::getPeerAddress() const |
| { |
| if (peername.empty()) { |
| peername = getName(fd, false); |
| } |
| return peername; |
| } |
| |
| std::string BSDSocket::getLocalAddress() const |
| { |
| if (localname.empty()) { |
| localname = getName(fd, true); |
| } |
| return localname; |
| } |
| |
| int BSDSocket::getError() const |
| { |
| int result; |
| socklen_t rSize = sizeof (result); |
| |
| if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &rSize) < 0) |
| throw QPID_POSIX_ERROR(errno); |
| |
| return result; |
| } |
| |
| int BSDSocket::getKeyLen() const |
| { |
| return 0; |
| } |
| |
| std::string BSDSocket::getClientAuthId() const |
| { |
| return std::string(); |
| } |
| |
| }} // namespace qpid::sys |