Panda3D
Loading...
Searching...
No Matches
bioPtr.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file bioPtr.cxx
10 * @author drose
11 * @date 2002-10-15
12 */
13
14#include "bioPtr.h"
15
16#ifdef HAVE_OPENSSL
17
18#include "urlSpec.h"
19#include "config_downloader.h"
20
21#include "openSSLWrapper.h" // must be included before any other openssl.
22#include <openssl/ssl.h>
23
24#ifdef _WIN32
25#include <winsock2.h>
26#else
27#include <sys/socket.h>
28#include <netinet/in.h>
29#include <arpa/inet.h>
30#include <netdb.h>
31#include <fcntl.h>
32#endif
33
34using std::string;
35
36#ifdef _WIN32
37static string format_error() {
38 PVOID buffer;
39 DWORD len;
40 len = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
41 nullptr, WSAGetLastError(), 0, (LPTSTR)&buffer, 0, nullptr);
42 if (len == 0) {
43 return string("Unknown error message");
44 }
45
46 const char *text = (const char *)buffer;
47 while (len > 0 && isspace(text[len - 1])) {
48 --len;
49 }
50
51 string result(text, len);
52 LocalFree(buffer);
53 return result;
54}
55#else
56#define format_error() strerror(errno)
57#endif
58
59#if defined(_WIN32) && defined(LIBRESSL_VERSION_NUMBER)
60/**
61* This exists to work around an issue with LibreSSL's version of
62* BIO_sock_should_retry, which does not understand Windows error codes.
63* The implementation here matches the behaviour of OpenSSL on Windows.
64*/
65static int
66sock_should_retry(int i) {
67 if (i == 0 || i == -1) {
68 int err = WSAGetLastError();
69
70 switch (err) {
71 case WSAEWOULDBLOCK:
72 case ENOTCONN:
73 case EINPROGRESS:
74 case EALREADY:
75 return 1;
76 default:
77 break;
78 }
79 }
80 return 0;
81}
82#else
83#define sock_should_retry(err) BIO_sock_should_retry(err)
84#endif
85
86/**
87 * This flavor of the constructor automatically creates a socket BIO and feeds
88 * it the server and port name from the indicated URL. It doesn't call
89 * BIO_do_connect(), though.
90 */
91BioPtr::
92BioPtr(const URLSpec &url) : _connecting(false) {
93 if (url.get_scheme() == "file") {
94 // We're just reading a disk file.
95 string filename = URLSpec::unquote(url.get_path());
96#ifdef _WIN32
97 // On Windows, we have to munge the filename specially, because it's been
98 // URL-munged. It might begin with a leading slash as well as a drive
99 // letter. Clean up that nonsense.
100 if (!filename.empty()) {
101 if (filename[0] == '/' || filename[0] == '\\') {
102 Filename fname = Filename::from_os_specific(filename.substr(1));
103 if (fname.is_local()) {
104 // Put the slash back on.
105 fname = string("/") + fname.get_fullpath();
106 }
107 filename = fname.to_os_specific();
108 }
109 }
110#endif // _WIN32
111 _server_name = "";
112 _port = 0;
113 _bio = BIO_new_file(filename.c_str(), "rb");
114
115 } else {
116 // A normal network-based URL. We don't use BIO_new_connect since it
117 // doesn't handle IPv6 properly.
118 _server_name = url.get_server();
119 _port = url.get_port();
120 _bio = nullptr;
121
122 // These hints tell getaddrinfo what kind of address to return.
123 struct addrinfo hints, *res = nullptr;
124 memset(&hints, 0, sizeof(hints));
125 hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
126 hints.ai_family = support_ipv6 ? AF_UNSPEC : AF_INET;
127 hints.ai_socktype = SOCK_STREAM;
128
129 // Resolve the hostname or address string.
130 int result = getaddrinfo(_server_name.c_str(), nullptr, &hints, &res);
131 if (result != 0) {
132 const char *errmsg;
133#ifndef _WIN32
134 if (result == EAI_SYSTEM && errno != 0) {
135 errmsg = strerror(errno);
136 } else
137#endif
138 {
139 errmsg = gai_strerror(result);
140 }
141 downloader_cat.error()
142 << "Failed to resolve " << url.get_server() << ": " << errmsg << "\n";
143 return;
144 }
145 nassertv(res != nullptr && res->ai_addr != nullptr);
146
147 // Store the real resolved address.
148 char buf[48];
149 buf[0] = 0;
150#ifdef _WIN32
151 DWORD bufsize = sizeof(buf);
152 WSAAddressToStringA(res->ai_addr, res->ai_addrlen, nullptr, buf, &bufsize);
153#else
154 if (res->ai_addr->sa_family == AF_INET) {
155 inet_ntop(AF_INET, (char *)&((sockaddr_in *)res->ai_addr)->sin_addr, buf, sizeof(buf));
156
157 } else if (res->ai_addr->sa_family == AF_INET6) {
158 inet_ntop(AF_INET6, (char *)&((sockaddr_in6 *)res->ai_addr)->sin6_addr, buf, sizeof(buf));
159 }
160#endif
161
162 if (buf[0]) {
163 _server_name = buf;
164 }
165 if (downloader_cat.is_debug()) {
166 downloader_cat.debug()
167 << "Resolved " << url.get_server() << " to " << buf << "\n";
168 }
169
170 // Create the socket.
171 int fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP);
172 if (fd < 0) {
173 downloader_cat.error()
174 << "Failed to create socket: " << format_error() << "\n";
175 _bio = nullptr;
176 freeaddrinfo(res);
177 return;
178 }
179
180 // Store the address and length for later use in connect().
181 nassertv(res->ai_addrlen <= sizeof(_addr));
182 memcpy(&_addr, res->ai_addr, res->ai_addrlen);
183 _addrlen = res->ai_addrlen;
184 freeaddrinfo(res);
185
186 // Also set the port we'd like to connect to.
187 if (_addr.ss_family == AF_INET) {
188 ((sockaddr_in &)_addr).sin_port = htons(_port);
189 } else if (_addr.ss_family == AF_INET6) {
190 ((sockaddr_in6 &)_addr).sin6_port = htons(_port);
191 }
192
193 _bio = BIO_new_socket(fd, 1);
194 }
195}
196
197/**
198 * Sets the non-blocking flag on the socket.
199 */
200void BioPtr::
201set_nbio(bool nbio) {
202 if (_bio == nullptr) {
203 return;
204 }
205
206 int fd = -1;
207 BIO_get_fd(_bio, &fd);
208 nassertv_always(fd >= 0);
209
210 BIO_socket_nbio(fd, nbio);
211}
212
213/**
214 * Connects to the socket. Returns true on success.
215 */
216bool BioPtr::
217connect() {
218 if (_bio == nullptr) {
219 return false;
220 }
221
222 int fd = -1;
223 BIO_get_fd(_bio, &fd);
224 nassertr(fd >= 0, false);
225
226 int result;
227 if (_connecting) {
228 result = BIO_sock_error(fd);
229 } else {
230 result = ::connect(fd, (sockaddr *)&_addr, _addrlen);
231 if (result != 0 && sock_should_retry(-1)) {
232 // It's still in progress; we should retry later. This causes
233 // should_retry() to return true.
234 BIO_set_flags(_bio, BIO_FLAGS_SHOULD_RETRY);
235 _connecting = true;
236 return false;
237 }
238 }
239 BIO_clear_retry_flags(_bio);
240 _connecting = false;
241
242 if (result != 0) {
243 downloader_cat.warning()
244 << "Failed to connect to " << _server_name << " port " << _port
245 << ": " << format_error() << "\n";
246 return false;
247 }
248
249 return true;
250}
251
252/**
253 *
254 */
255bool BioPtr::
256should_retry() const {
257 return (_bio != nullptr) && BIO_should_retry(_bio);
258}
259
260/**
261 *
262 */
263BioPtr::
264~BioPtr() {
265 if (_bio != nullptr) {
266 if (downloader_cat.is_debug() && !_server_name.empty()) {
267 downloader_cat.debug()
268 << "Dropping connection to " << _server_name << " port " << _port << "\n";
269 }
270
271 BIO_free_all(_bio);
272 _bio = nullptr;
273 }
274}
275
276#endif // HAVE_OPENSSL
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
std::string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition filename.I:338
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes,...
Definition filename.cxx:328
bool is_local() const
Returns true if the filename is local, e.g.
Definition filename.I:549
A container for a URL, e.g.
Definition urlSpec.h:28
get_server
Returns the server name specified by the URL, if any.
Definition urlSpec.h:96
get_path
Returns the path specified by the URL, or "/" if no path is specified.
Definition urlSpec.h:99
get_scheme
Returns the scheme specified by the URL, or empty string if no scheme is specified.
Definition urlSpec.h:93
static std::string unquote(const std::string &source)
Reverses the operation of quote(): converts escaped characters of the form "%xx" to their ascii equiv...
Definition urlSpec.cxx:801
get_port
Returns the port number specified by the URL, or the default port if not specified.
Definition urlSpec.h:97
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.