Panda3D
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/**
60 * This flavor of the constructor automatically creates a socket BIO and feeds
61 * it the server and port name from the indicated URL. It doesn't call
62 * BIO_do_connect(), though.
63 */
64BioPtr::
65BioPtr(const URLSpec &url) : _connecting(false) {
66 if (url.get_scheme() == "file") {
67 // We're just reading a disk file.
68 string filename = URLSpec::unquote(url.get_path());
69#ifdef _WIN32
70 // On Windows, we have to munge the filename specially, because it's been
71 // URL-munged. It might begin with a leading slash as well as a drive
72 // letter. Clean up that nonsense.
73 if (!filename.empty()) {
74 if (filename[0] == '/' || filename[0] == '\\') {
75 Filename fname = Filename::from_os_specific(filename.substr(1));
76 if (fname.is_local()) {
77 // Put the slash back on.
78 fname = string("/") + fname.get_fullpath();
79 }
80 filename = fname.to_os_specific();
81 }
82 }
83#endif // _WIN32
84 _server_name = "";
85 _port = 0;
86 _bio = BIO_new_file(filename.c_str(), "rb");
87
88 } else {
89 // A normal network-based URL. We don't use BIO_new_connect since it
90 // doesn't handle IPv6 properly.
91 _server_name = url.get_server();
92 _port = url.get_port();
93 _bio = nullptr;
94
95 // These hints tell getaddrinfo what kind of address to return.
96 struct addrinfo hints, *res = nullptr;
97 memset(&hints, 0, sizeof(hints));
98 hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
99 hints.ai_family = support_ipv6 ? AF_UNSPEC : AF_INET;
100 hints.ai_socktype = SOCK_STREAM;
101
102 // Resolve the hostname or address string.
103 int result = getaddrinfo(_server_name.c_str(), nullptr, &hints, &res);
104 if (result != 0) {
105 const char *errmsg;
106#ifndef _WIN32
107 if (result == EAI_SYSTEM && errno != 0) {
108 errmsg = strerror(errno);
109 } else
110#endif
111 {
112 errmsg = gai_strerror(result);
113 }
114 downloader_cat.error()
115 << "Failed to resolve " << url.get_server() << ": " << errmsg << "\n";
116 return;
117 }
118 nassertv(res != nullptr && res->ai_addr != nullptr);
119
120 // Store the real resolved address.
121 char buf[48];
122 buf[0] = 0;
123#ifdef _WIN32
124 DWORD bufsize = sizeof(buf);
125 WSAAddressToStringA(res->ai_addr, res->ai_addrlen, nullptr, buf, &bufsize);
126#else
127 if (res->ai_addr->sa_family == AF_INET) {
128 inet_ntop(AF_INET, (char *)&((sockaddr_in *)res->ai_addr)->sin_addr, buf, sizeof(buf));
129
130 } else if (res->ai_addr->sa_family == AF_INET6) {
131 inet_ntop(AF_INET6, (char *)&((sockaddr_in6 *)res->ai_addr)->sin6_addr, buf, sizeof(buf));
132 }
133#endif
134
135 if (buf[0]) {
136 _server_name = buf;
137 }
138 if (downloader_cat.is_debug()) {
139 downloader_cat.debug()
140 << "Resolved " << url.get_server() << " to " << buf << "\n";
141 }
142
143 // Create the socket.
144 int fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP);
145 if (fd < 0) {
146 downloader_cat.error()
147 << "Failed to create socket: " << format_error() << "\n";
148 _bio = nullptr;
149 freeaddrinfo(res);
150 return;
151 }
152
153 // Store the address and length for later use in connect().
154 nassertv(res->ai_addrlen <= sizeof(_addr));
155 memcpy(&_addr, res->ai_addr, res->ai_addrlen);
156 _addrlen = res->ai_addrlen;
157 freeaddrinfo(res);
158
159 // Also set the port we'd like to connect to.
160 if (_addr.ss_family == AF_INET) {
161 ((sockaddr_in &)_addr).sin_port = htons(_port);
162 } else if (_addr.ss_family == AF_INET6) {
163 ((sockaddr_in6 &)_addr).sin6_port = htons(_port);
164 }
165
166 _bio = BIO_new_socket(fd, 1);
167 }
168}
169
170/**
171 * Sets the non-blocking flag on the socket.
172 */
173void BioPtr::
174set_nbio(bool nbio) {
175 if (_bio == nullptr) {
176 return;
177 }
178
179 int fd = -1;
180 BIO_get_fd(_bio, &fd);
181 nassertv_always(fd >= 0);
182
183 BIO_socket_nbio(fd, nbio);
184}
185
186/**
187 * Connects to the socket. Returns true on success.
188 */
189bool BioPtr::
190connect() {
191 if (_bio == nullptr) {
192 return false;
193 }
194
195 int fd = -1;
196 BIO_get_fd(_bio, &fd);
197 nassertr(fd >= 0, false);
198
199 int result;
200 if (_connecting) {
201 result = BIO_sock_error(fd);
202 } else {
203 result = ::connect(fd, (sockaddr *)&_addr, _addrlen);
204
205 if (result != 0 && BIO_sock_should_retry(-1)) {
206 // It's still in progress; we should retry later. This causes
207 // should_retry() to return true.
208 BIO_set_flags(_bio, BIO_FLAGS_SHOULD_RETRY);
209 _connecting = true;
210 return false;
211 }
212 }
213 BIO_clear_retry_flags(_bio);
214 _connecting = false;
215
216 if (result != 0) {
217 downloader_cat.warning()
218 << "Failed to connect to " << _server_name << " port " << _port
219 << ": " << format_error() << "\n";
220 return false;
221 }
222
223 return true;
224}
225
226/**
227 *
228 */
229bool BioPtr::
230should_retry() const {
231 return (_bio != nullptr) && BIO_should_retry(_bio);
232}
233
234/**
235 *
236 */
237BioPtr::
238~BioPtr() {
239 if (_bio != nullptr) {
240 if (downloader_cat.is_debug() && !_server_name.empty()) {
241 downloader_cat.debug()
242 << "Dropping connection to " << _server_name << " port " << _port << "\n";
243 }
244
245 BIO_free_all(_bio);
246 _bio = nullptr;
247 }
248}
249
250#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:39
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1123
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.