henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 1 | /* |
| 2 | * libjingle |
| 3 | * Copyright 2004--2005, Google Inc. |
| 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, |
| 9 | * this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, |
| 11 | * this list of conditions and the following disclaimer in the documentation |
| 12 | * and/or other materials provided with the distribution. |
| 13 | * 3. The name of the author may not be used to endorse or promote products |
| 14 | * derived from this software without specific prior written permission. |
| 15 | * |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| 17 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| 18 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| 19 | * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 21 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| 22 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| 23 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| 24 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| 25 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | */ |
| 27 | |
| 28 | #include "talk/base/autodetectproxy.h" |
| 29 | #include "talk/base/httpcommon.h" |
| 30 | #include "talk/base/httpcommon-inl.h" |
| 31 | #include "talk/base/nethelpers.h" |
| 32 | |
| 33 | namespace talk_base { |
| 34 | |
| 35 | static const ProxyType TEST_ORDER[] = { |
| 36 | PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN |
| 37 | }; |
| 38 | |
| 39 | static const int kSavedStringLimit = 128; |
| 40 | |
| 41 | static void SaveStringToStack(char *dst, |
| 42 | const std::string &src, |
| 43 | size_t dst_size) { |
| 44 | strncpy(dst, src.c_str(), dst_size - 1); |
| 45 | dst[dst_size - 1] = '\0'; |
| 46 | } |
| 47 | |
| 48 | AutoDetectProxy::AutoDetectProxy(const std::string& user_agent) |
| 49 | : agent_(user_agent), resolver_(NULL), socket_(NULL), next_(0) { |
| 50 | } |
| 51 | |
| 52 | AutoDetectProxy::~AutoDetectProxy() { |
| 53 | if (resolver_) { |
| 54 | resolver_->Destroy(false); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | void AutoDetectProxy::DoWork() { |
| 59 | // TODO: Try connecting to server_url without proxy first here? |
| 60 | if (!server_url_.empty()) { |
| 61 | LOG(LS_INFO) << "GetProxySettingsForUrl(" << server_url_ << ") - start"; |
| 62 | GetProxyForUrl(agent_.c_str(), server_url_.c_str(), &proxy_); |
| 63 | LOG(LS_INFO) << "GetProxySettingsForUrl - stop"; |
| 64 | } |
| 65 | Url<char> url(proxy_.address.HostAsURIString()); |
| 66 | if (url.valid()) { |
| 67 | LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host"; |
| 68 | proxy_.address.SetIP(url.host()); |
| 69 | } |
| 70 | LOG(LS_INFO) << "AutoDetectProxy found proxy at " << proxy_.address; |
| 71 | if (proxy_.type == PROXY_UNKNOWN) { |
| 72 | LOG(LS_INFO) << "AutoDetectProxy initiating proxy classification"; |
| 73 | Next(); |
| 74 | // Process I/O until Stop() |
| 75 | Thread::Current()->ProcessMessages(kForever); |
| 76 | // Clean up the autodetect socket, from the thread that created it |
| 77 | delete socket_; |
| 78 | } |
| 79 | // TODO: If we found a proxy, try to use it to verify that it |
| 80 | // works by sending a request to server_url. This could either be |
| 81 | // done here or by the HttpPortAllocator. |
| 82 | } |
| 83 | |
| 84 | void AutoDetectProxy::OnMessage(Message *msg) { |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 85 | if (MSG_UNRESOLVABLE == msg->message_id) { |
| 86 | // If we can't resolve the proxy, skip straight to failure. |
| 87 | Complete(PROXY_UNKNOWN); |
| 88 | } else if (MSG_TIMEOUT == msg->message_id) { |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 89 | OnCloseEvent(socket_, ETIMEDOUT); |
| 90 | } else { |
| 91 | // This must be the ST_MSG_WORKER_DONE message that deletes the |
| 92 | // AutoDetectProxy object. We have observed crashes within this stack that |
| 93 | // seem to be highly reproducible for a small subset of users and thus are |
| 94 | // probably correlated with a specific proxy setting, so copy potentially |
| 95 | // relevant information onto the stack to make it available in Windows |
| 96 | // minidumps. |
| 97 | |
| 98 | // Save the user agent and the number of auto-detection passes that we |
| 99 | // needed. |
| 100 | char agent[kSavedStringLimit]; |
| 101 | SaveStringToStack(agent, agent_, sizeof agent); |
| 102 | |
| 103 | int next = next_; |
| 104 | |
| 105 | // Now the detected proxy config (minus the password field, which could be |
| 106 | // sensitive). |
| 107 | ProxyType type = proxy().type; |
| 108 | |
| 109 | char address_hostname[kSavedStringLimit]; |
| 110 | SaveStringToStack(address_hostname, |
| 111 | proxy().address.hostname(), |
| 112 | sizeof address_hostname); |
| 113 | |
| 114 | IPAddress address_ip = proxy().address.ipaddr(); |
| 115 | |
| 116 | uint16 address_port = proxy().address.port(); |
| 117 | |
| 118 | char autoconfig_url[kSavedStringLimit]; |
| 119 | SaveStringToStack(autoconfig_url, |
| 120 | proxy().autoconfig_url, |
| 121 | sizeof autoconfig_url); |
| 122 | |
| 123 | bool autodetect = proxy().autodetect; |
| 124 | |
| 125 | char bypass_list[kSavedStringLimit]; |
| 126 | SaveStringToStack(bypass_list, proxy().bypass_list, sizeof bypass_list); |
| 127 | |
| 128 | char username[kSavedStringLimit]; |
| 129 | SaveStringToStack(username, proxy().username, sizeof username); |
| 130 | |
| 131 | SignalThread::OnMessage(msg); |
| 132 | |
| 133 | // Log the gathered data at a log level that will never actually be enabled |
| 134 | // so that the compiler is forced to retain the data on the stack. |
| 135 | LOG(LS_SENSITIVE) << agent << " " << next << " " << type << " " |
| 136 | << address_hostname << " " << address_ip << " " |
| 137 | << address_port << " " << autoconfig_url << " " |
| 138 | << autodetect << " " << bypass_list << " " << username; |
| 139 | } |
| 140 | } |
| 141 | |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 142 | void AutoDetectProxy::OnResolveResult(AsyncResolverInterface* resolver) { |
| 143 | if (resolver != resolver_) { |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 144 | return; |
| 145 | } |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 146 | int error = resolver_->GetError(); |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 147 | if (error == 0) { |
| 148 | LOG(LS_VERBOSE) << "Resolved " << proxy_.address << " to " |
| 149 | << resolver_->address(); |
| 150 | proxy_.address = resolver_->address(); |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 151 | if (!DoConnect()) { |
| 152 | Thread::Current()->Post(this, MSG_TIMEOUT); |
| 153 | } |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 154 | } else { |
| 155 | LOG(LS_INFO) << "Failed to resolve " << resolver_->address(); |
| 156 | resolver_->Destroy(false); |
| 157 | resolver_ = NULL; |
| 158 | proxy_.address = SocketAddress(); |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 159 | Thread::Current()->Post(this, MSG_UNRESOLVABLE); |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 160 | } |
| 161 | } |
| 162 | |
| 163 | void AutoDetectProxy::Next() { |
| 164 | if (TEST_ORDER[next_] >= PROXY_UNKNOWN) { |
| 165 | Complete(PROXY_UNKNOWN); |
| 166 | return; |
| 167 | } |
| 168 | |
| 169 | LOG(LS_VERBOSE) << "AutoDetectProxy connecting to " |
| 170 | << proxy_.address.ToSensitiveString(); |
| 171 | |
| 172 | if (socket_) { |
| 173 | Thread::Current()->Clear(this, MSG_TIMEOUT); |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 174 | Thread::Current()->Clear(this, MSG_UNRESOLVABLE); |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 175 | socket_->Close(); |
| 176 | Thread::Current()->Dispose(socket_); |
| 177 | socket_ = NULL; |
| 178 | } |
| 179 | int timeout = 2000; |
| 180 | if (proxy_.address.IsUnresolvedIP()) { |
| 181 | // Launch an asyncresolver. This thread will spin waiting for it. |
| 182 | timeout += 2000; |
| 183 | if (!resolver_) { |
| 184 | resolver_ = new AsyncResolver(); |
| 185 | } |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 186 | resolver_->SignalDone.connect(this, &AutoDetectProxy::OnResolveResult); |
| 187 | resolver_->Start(proxy_.address); |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 188 | } else { |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 189 | if (!DoConnect()) { |
| 190 | Thread::Current()->Post(this, MSG_TIMEOUT); |
| 191 | return; |
| 192 | } |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 193 | } |
| 194 | Thread::Current()->PostDelayed(timeout, this, MSG_TIMEOUT); |
| 195 | } |
| 196 | |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 197 | bool AutoDetectProxy::DoConnect() { |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 198 | if (resolver_) { |
| 199 | resolver_->Destroy(false); |
| 200 | resolver_ = NULL; |
| 201 | } |
| 202 | socket_ = |
| 203 | Thread::Current()->socketserver()->CreateAsyncSocket( |
| 204 | proxy_.address.family(), SOCK_STREAM); |
| 205 | if (!socket_) { |
| 206 | LOG(LS_VERBOSE) << "Unable to create socket for " << proxy_.address; |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 207 | return false; |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 208 | } |
| 209 | socket_->SignalConnectEvent.connect(this, &AutoDetectProxy::OnConnectEvent); |
| 210 | socket_->SignalReadEvent.connect(this, &AutoDetectProxy::OnReadEvent); |
| 211 | socket_->SignalCloseEvent.connect(this, &AutoDetectProxy::OnCloseEvent); |
| 212 | socket_->Connect(proxy_.address); |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 213 | return true; |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 214 | } |
| 215 | |
| 216 | void AutoDetectProxy::Complete(ProxyType type) { |
| 217 | Thread::Current()->Clear(this, MSG_TIMEOUT); |
sergeyu@chromium.org | 19da465 | 2013-11-13 22:48:52 +0000 | [diff] [blame] | 218 | Thread::Current()->Clear(this, MSG_UNRESOLVABLE); |
henrike@webrtc.org | 0e118e7 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 219 | if (socket_) { |
| 220 | socket_->Close(); |
| 221 | } |
| 222 | |
| 223 | proxy_.type = type; |
| 224 | LoggingSeverity sev = (proxy_.type == PROXY_UNKNOWN) ? LS_ERROR : LS_INFO; |
| 225 | LOG_V(sev) << "AutoDetectProxy detected " |
| 226 | << proxy_.address.ToSensitiveString() |
| 227 | << " as type " << proxy_.type; |
| 228 | |
| 229 | Thread::Current()->Quit(); |
| 230 | } |
| 231 | |
| 232 | void AutoDetectProxy::OnConnectEvent(AsyncSocket * socket) { |
| 233 | std::string probe; |
| 234 | |
| 235 | switch (TEST_ORDER[next_]) { |
| 236 | case PROXY_HTTPS: |
| 237 | probe.assign("CONNECT www.google.com:443 HTTP/1.0\r\n" |
| 238 | "User-Agent: "); |
| 239 | probe.append(agent_); |
| 240 | probe.append("\r\n" |
| 241 | "Host: www.google.com\r\n" |
| 242 | "Content-Length: 0\r\n" |
| 243 | "Proxy-Connection: Keep-Alive\r\n" |
| 244 | "\r\n"); |
| 245 | break; |
| 246 | case PROXY_SOCKS5: |
| 247 | probe.assign("\005\001\000", 3); |
| 248 | break; |
| 249 | default: |
| 250 | ASSERT(false); |
| 251 | return; |
| 252 | } |
| 253 | |
| 254 | LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_] |
| 255 | << " sending " << probe.size() << " bytes"; |
| 256 | socket_->Send(probe.data(), probe.size()); |
| 257 | } |
| 258 | |
| 259 | void AutoDetectProxy::OnReadEvent(AsyncSocket * socket) { |
| 260 | char data[257]; |
| 261 | int len = socket_->Recv(data, 256); |
| 262 | if (len > 0) { |
| 263 | data[len] = 0; |
| 264 | LOG(LS_VERBOSE) << "AutoDetectProxy read " << len << " bytes"; |
| 265 | } |
| 266 | |
| 267 | switch (TEST_ORDER[next_]) { |
| 268 | case PROXY_HTTPS: |
| 269 | if ((len >= 2) && (data[0] == '\x05')) { |
| 270 | Complete(PROXY_SOCKS5); |
| 271 | return; |
| 272 | } |
| 273 | if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) { |
| 274 | Complete(PROXY_HTTPS); |
| 275 | return; |
| 276 | } |
| 277 | break; |
| 278 | case PROXY_SOCKS5: |
| 279 | if ((len >= 2) && (data[0] == '\x05')) { |
| 280 | Complete(PROXY_SOCKS5); |
| 281 | return; |
| 282 | } |
| 283 | break; |
| 284 | default: |
| 285 | ASSERT(false); |
| 286 | return; |
| 287 | } |
| 288 | |
| 289 | ++next_; |
| 290 | Next(); |
| 291 | } |
| 292 | |
| 293 | void AutoDetectProxy::OnCloseEvent(AsyncSocket * socket, int error) { |
| 294 | LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error; |
| 295 | ++next_; |
| 296 | Next(); |
| 297 | } |
| 298 | |
| 299 | } // namespace talk_base |