Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/ByteBuffer.h>
28#include <LibCore/Notifier.h>
29#include <LibCore/Socket.h>
30#include <arpa/inet.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <netdb.h>
34#include <netinet/in.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <sys/socket.h>
38#include <unistd.h>
39
40//#define CSOCKET_DEBUG
41
42namespace Core {
43
44Socket::Socket(Type type, Object* parent)
45 : IODevice(parent)
46 , m_type(type)
47{
48}
49
50Socket::~Socket()
51{
52 close();
53}
54
55bool Socket::connect(const String& hostname, int port)
56{
57 auto* hostent = gethostbyname(hostname.characters());
58 if (!hostent) {
59 dbg() << "Socket::connect: Unable to resolve '" << hostname << "'";
60 return false;
61 }
62
63 IPv4Address host_address((const u8*)hostent->h_addr_list[0]);
64#ifdef CSOCKET_DEBUG
65 dbg() << "Socket::connect: Resolved '" << hostname << "' to " << host_address;
66#endif
67 return connect(host_address, port);
68}
69
70void Socket::set_blocking(bool blocking)
71{
72 int flags = fcntl(fd(), F_GETFL, 0);
73 ASSERT(flags >= 0);
74 if (blocking)
75 flags = fcntl(fd(), F_SETFL, flags & ~O_NONBLOCK);
76 else
77 flags = fcntl(fd(), F_SETFL, flags | O_NONBLOCK);
78 ASSERT(flags == 0);
79}
80
81bool Socket::connect(const SocketAddress& address, int port)
82{
83 ASSERT(!is_connected());
84 ASSERT(address.type() == SocketAddress::Type::IPv4);
85#ifdef CSOCKET_DEBUG
86 dbg() << *this << " connecting to " << address << "...";
87#endif
88
89 ASSERT(port > 0 && port <= 65535);
90
91 struct sockaddr_in addr;
92 memset(&addr, 0, sizeof(addr));
93 auto ipv4_address = address.ipv4_address();
94 memcpy(&addr.sin_addr.s_addr, &ipv4_address, sizeof(IPv4Address));
95 addr.sin_family = AF_INET;
96 addr.sin_port = htons(port);
97
98 m_destination_address = address;
99 m_destination_port = port;
100
101 return common_connect((struct sockaddr*)&addr, sizeof(addr));
102}
103
104bool Socket::connect(const SocketAddress& address)
105{
106 ASSERT(!is_connected());
107 ASSERT(address.type() == SocketAddress::Type::Local);
108#ifdef CSOCKET_DEBUG
109 dbg() << *this << " connecting to " << address << "...";
110#endif
111
112 sockaddr_un saddr;
113 saddr.sun_family = AF_LOCAL;
114 strcpy(saddr.sun_path, address.to_string().characters());
115
116 m_destination_address = address;
117
118 return common_connect((const sockaddr*)&saddr, sizeof(saddr));
119}
120
121bool Socket::common_connect(const struct sockaddr* addr, socklen_t addrlen)
122{
123 int rc = ::connect(fd(), addr, addrlen);
124 if (rc < 0) {
125 if (errno == EINPROGRESS) {
126#ifdef CSOCKET_DEBUG
127 dbg() << *this << " connection in progress (EINPROGRESS)";
128#endif
129 m_notifier = Notifier::construct(fd(), Notifier::Event::Write, this);
130 m_notifier->on_ready_to_write = [this] {
131#ifdef CSOCKET_DEBUG
132 dbg() << *this << " connected!";
133#endif
134 m_connected = true;
135 ensure_read_notifier();
136 m_notifier->set_event_mask(Notifier::Event::None);
137 if (on_connected)
138 on_connected();
139 };
140 return true;
141 }
142 int saved_errno = errno;
143 fprintf(stderr, "Core::Socket: Failed to connect() to %s: %s\n", destination_address().to_string().characters(), strerror(saved_errno));
144 errno = saved_errno;
145 return false;
146 }
147#ifdef CSOCKET_DEBUG
148 dbg() << *this << " connected ok!";
149#endif
150 m_connected = true;
151 ensure_read_notifier();
152 if (on_connected)
153 on_connected();
154 return true;
155}
156
157ByteBuffer Socket::receive(int max_size)
158{
159 auto buffer = read(max_size);
160 if (eof()) {
161 dbg() << *this << " connection appears to have closed in receive().";
162 m_connected = false;
163 }
164 return buffer;
165}
166
167bool Socket::send(const ByteBuffer& data)
168{
169 ssize_t nsent = ::send(fd(), data.data(), data.size(), 0);
170 if (nsent < 0) {
171 set_error(errno);
172 return false;
173 }
174 ASSERT(static_cast<size_t>(nsent) == data.size());
175 return true;
176}
177
178void Socket::did_update_fd(int fd)
179{
180 if (fd < 0) {
181 m_read_notifier = nullptr;
182 return;
183 }
184 if (m_connected) {
185 ensure_read_notifier();
186 } else {
187 // I don't think it would be right if we updated the fd while not connected *but* while having a notifier..
188 ASSERT(!m_read_notifier);
189 }
190}
191
192void Socket::ensure_read_notifier()
193{
194 ASSERT(m_connected);
195 m_read_notifier = Notifier::construct(fd(), Notifier::Event::Read, this);
196 m_read_notifier->on_ready_to_read = [this] {
197 if (on_ready_to_read)
198 on_ready_to_read();
199 };
200}
201
202}