Chromium Code Reviews| Index: net/web2socket_proxy/web2socket_conn.cc |
| diff --git a/net/web2socket_proxy/web2socket_conn.cc b/net/web2socket_proxy/web2socket_conn.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b0c86b39de9aa5080b70841b7425a0b4457b173b |
| --- /dev/null |
| +++ b/net/web2socket_proxy/web2socket_conn.cc |
| @@ -0,0 +1,779 @@ |
| +// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "web2socket_conn.h" |
| + |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| + |
| +#include <algorithm> |
| +#include <limits> |
| +#include <vector> |
| + |
| +#include <arpa/inet.h> |
| +#include <errno.h> |
| +#include <netinet/in.h> |
| +#include <signal.h> |
| +#include <sys/types.h> |
| +#include <sys/wait.h> |
| + |
| +#include "base/base64.h" |
| +#include "base/logging.h" |
| +#include "third_party/libevent/evdns.h" |
| + |
| +static int CountSpaces(const std::string& s) { |
| + int rv = 0; |
| + for (size_t i = 0; i < s.size(); ++i) { |
| + rv += (isascii(s[i]) && isspace(s[i])); |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
If you don't have to keep this code independent fr
Denis Lagno
2010/12/03 16:28:50
actually we don't need to be locale-independent he
tyoshino (SeeGerritForStatus)
2010/12/06 10:02:51
e.g. isdigit may recognize some characters other t
|
| + } |
| + return rv; |
| +} |
| + |
| +static std::string FetchLowerCasedASCIISnippet(uint8_t* begin, uint8_t* end) { |
| + std::string rv; |
| + for (; begin < end; ++begin) { |
| + if (!isascii(*begin)) { |
| + return rv; |
| + } |
| + rv += tolower(*begin); |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
ditto. ToLowerASCII
Denis Lagno
2010/12/03 16:28:50
Done.
|
| + } |
| + return rv; |
| +} |
| + |
| +// Returns true on success. |
| +static bool FetchDecimalDigits(const std::string& s, uint32_t* result) { |
| + *result = 0; |
| + bool got_something = false; |
| + for (size_t i = 0; i < s.size(); ++i) { |
| + if (isascii(s[i]) && isdigit(s[i])) { |
| + got_something = true; |
| + if (*result > std::numeric_limits<uint32_t>::max() / 10) { |
| + return false; |
| + } |
| + *result *= 10; |
| + int digit = s[i] - '0'; |
| + if (*result > std::numeric_limits<uint32_t>::max() - digit) { |
| + return false; |
| + } |
| + *result += digit; |
| + } |
| + } |
| + return got_something; |
| +} |
| + |
| +// Returns true on success. |
| +static bool FetchNamePort(uint8_t* begin, uint8_t* end, |
| + std::string* name, uint32_t* port) { |
| + *name = std::string(); |
| + *port = 0; |
| + if (begin == end) { |
| + return false; |
| + } |
| + for (uint8_t* pos = end; --pos != begin;) { |
| + if (!isascii(*pos) || !isdigit(*pos)) { |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
How about isascii(*pos) && isdigit(*pos)?
But if s
Denis Lagno
2010/12/03 16:28:50
actually !x || !y is !(x && y), not just (x && y).
tyoshino (SeeGerritForStatus)
2010/12/06 10:02:51
Oops, I put wrong comment. You're right. Anyway, t
|
| + if (*pos == ':') { |
| + name->assign(begin, pos); |
| + if (end - pos <= 1 || |
| + !FetchDecimalDigits(std::string(pos + 1, end), port)) { |
| + *port = 0; |
| + } |
| + } |
| + break; |
| + } |
| + } |
| + if (*port <= 0 || *port >= (2 << 16)) { |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
Up to your design, but how about handling erroneou
Denis Lagno
2010/12/03 16:28:50
Done.
|
| + name->assign(begin, end); |
| + *port = 22; |
| + } |
| + return !name->empty(); |
| +} |
| + |
| +Conn::Conn(Web2SocketServ* master) |
| + : master_(master), |
| + phase_(PHASE_WAIT_HANDSHAKE), |
| + destresolution_ipv4_failure_(false), |
| + destresolution_ipv6_failure_(false) { |
| + while (token_map_.find(last_token_) != token_map_.end()) { |
| + token_ = last_token_ = |
| + reinterpret_cast<Token>(reinterpret_cast<size_t>(last_token_) + 1); |
| + } |
| + token_map_[token_] = this; |
| + // Schedule timeout for initial phase of connection. |
| + evtimer_set(&destconnect_timeout_event_, |
| + &OnDestConnectTimeout, token_); |
| + event_base_set(master_->evbase_, |
| + &destconnect_timeout_event_); |
| + struct timeval tv; |
| + tv.tv_sec = 30; |
| + tv.tv_usec = 0; |
| + evtimer_add(&destconnect_timeout_event_, &tv); |
| +} |
| + |
| +Conn::~Conn() { |
| + phase_ = PHASE_DEFUNCT; |
| + event_del(&destconnect_timeout_event_); |
| + if (token_map_[token_] == this) { |
| + token_map_.erase(token_); |
| + } |
| +} |
| + |
| +Conn* Conn::Get(Token token) { |
| + TokenMap::iterator it = token_map_.find(token); |
| + if (it == token_map_.end()) { |
| + return NULL; |
| + } |
| + Conn* cs = it->second; |
| + if (cs == NULL || |
| + cs->token_ != token || |
| + cs->master_ == NULL || |
| + cs->phase_ < 0 || |
| + cs->phase_ > PHASE_SHUT || |
| + !cs->master_->IsConnSane(cs)) { |
| + return NULL; |
| + } |
| + return cs; |
| +} |
| + |
| +void Conn::Shut() { |
| + if (phase_ >= PHASE_SHUT) { |
| + return; |
| + } |
| + master_->MarkConnImportance(this, false); |
| + if (primchan_.sock_ >= 0 && primchan_.bev_ != NULL) { |
| + static const uint8_t closing_handshake[9] = { 0 }; |
| + bufferevent_write(primchan_.bev_, |
| + closing_handshake, sizeof(closing_handshake)); |
| + } |
| + phase_ = PHASE_SHUT; |
| +} |
| + |
| +// For our purposes equivalent to strlen(reinterpret_cast<const char*>(X)). |
| +#define RAWSIZEOF(X) (sizeof(X) - 1) |
| + |
| +Conn::Status Conn::ConsumeHeader(struct evbuffer* evb) { |
| + uint8_t* buf = EVBUFFER_DATA(evb); |
| + size_t buf_size = EVBUFFER_LENGTH(evb); |
| + |
| + static const uint8_t kCRLF[] = "\r\n"; |
| + static const uint8_t kCRLFCRLF[] = "\r\n\r\n"; |
| + static const uint8_t kGetMagic[] = "GET /echo "; |
| + static const uint8_t kDelimiter[] = ": "; |
| + |
| + if (buf_size <= 0) { |
| + return STATUS_INCOMPLETE; |
| + } |
| + if (!buf) { |
| + return STATUS_ABORT; |
| + } |
| + if (!std::equal(buf, buf + std::min(buf_size, RAWSIZEOF(kGetMagic)), |
| + kGetMagic)) { |
| + // Data head does not match what is expected. |
| + return STATUS_ABORT; |
| + } |
| + |
| + if (buf_size >= Web2SocketServ::kReadBufferLimit) { |
| + return STATUS_ABORT; |
| + } |
| + uint8_t* buf_end = buf + buf_size; |
| + uint8_t* term_pos = std::search(buf, buf_end, kCRLFCRLF, |
| + kCRLFCRLF + RAWSIZEOF(kCRLFCRLF)); |
| + if (term_pos == buf_end) { |
| + return STATUS_INCOMPLETE; |
| + } |
| + uint8_t key3[8]; // Notation (key3) matches websocket RFC. |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
Two space before inline comment
Denis Lagno
2010/12/03 16:28:50
Done.
|
| + if (buf_end - term_pos < |
| + static_cast<ptrdiff_t>(RAWSIZEOF(kCRLFCRLF) + sizeof(key3))) { |
| + return STATUS_INCOMPLETE; |
| + } |
| + term_pos += RAWSIZEOF(kCRLFCRLF) + sizeof(key3); |
| + memcpy(key3, term_pos - sizeof(key3), sizeof(key3)); |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
how about swapping these lines to remove "- sizeof
Denis Lagno
2010/12/03 16:28:50
Done.
|
| + // First line is "GET /echo" line, so we skip it. |
| + uint8_t* pos = std::search(buf, term_pos, kCRLF, kCRLF + RAWSIZEOF(kCRLF)); |
| + if (pos == term_pos) { |
| + return STATUS_ABORT; |
| + } |
| + for (;;) { |
| + pos += RAWSIZEOF(kCRLF); |
| + if (term_pos - pos < |
| + static_cast<ptrdiff_t>(sizeof(key3) + RAWSIZEOF(kCRLF))) { |
| + return STATUS_ABORT; |
| + } |
| + if (term_pos - pos == |
| + static_cast<ptrdiff_t>(sizeof(key3) + RAWSIZEOF(kCRLF))) { |
| + break; |
| + } |
| + uint8_t* npos = std::search(pos, term_pos, kDelimiter, |
| + kDelimiter + RAWSIZEOF(kDelimiter)); |
| + if (npos == term_pos) { |
| + return STATUS_ABORT; |
| + } |
| + std::string key = FetchLowerCasedASCIISnippet(pos, npos); |
| + pos = std::search(npos += RAWSIZEOF(kDelimiter), term_pos, |
| + kCRLF, kCRLF + RAWSIZEOF(kCRLF)); |
| + if (pos == term_pos) { |
| + return STATUS_ABORT; |
| + } |
| + if (!key.empty()) { |
| + header_fields_[key] = FetchLowerCasedASCIISnippet(npos, pos); |
| + } |
| + } |
| + |
| + // Values of Upgrade and Connection fields are hardcoded in the protocol. |
| + if (header_fields_["upgrade"] != "websocket" || |
| + header_fields_["connection"] != "upgrade") { |
| + return STATUS_ABORT; |
| + } |
| + |
| + static const std::string kAllowedHost = "127.0.0.1:"; |
| + if (header_fields_["host"].substr(0, kAllowedHost.size()) != |
| + kAllowedHost) { |
| + return STATUS_ABORT; |
| + } |
| + |
| + if (!master_->origin_.empty() && |
| + master_->origin_ != "any") { |
| + if (header_fields_["origin"] != master_->origin_) { |
| + return STATUS_ABORT; |
| + } |
| + } |
| + |
| + static const std::string kSecKey1 = "sec-websocket-key1"; |
| + static const std::string kSecKey2 = "sec-websocket-key2"; |
| + uint32_t key_number1, key_number2; |
| + if (!FetchDecimalDigits(header_fields_[kSecKey1], |
| + &key_number1) || |
| + !FetchDecimalDigits(header_fields_[kSecKey2], |
| + &key_number2)) { |
| + return STATUS_ABORT; |
| + } |
| + |
| + // We limit incoming header size so following numbers shall not be too high. |
| + int spaces1 = CountSpaces(header_fields_[kSecKey1]); |
| + int spaces2 = CountSpaces(header_fields_[kSecKey2]); |
| + if (spaces1 == 0 || |
| + spaces2 == 0 || |
| + key_number1 % spaces1 != 0 || |
| + key_number2 % spaces2 != 0) { |
| + return STATUS_ABORT; |
| + } |
| + |
| + uint8_t challenge[4 + 4 + sizeof(key3)]; |
| + uint32_t part1 = htonl(key_number1 / spaces1); |
| + uint32_t part2 = htonl(key_number2 / spaces2); |
| + memcpy(challenge, &part1, 4); |
| + memcpy(challenge + 4, &part2, 4); |
| + memcpy(challenge + sizeof(challenge) - sizeof(key3), key3, sizeof(key3)); |
| + MD5Sum(challenge, sizeof(challenge), &handshake_response_); |
| + |
| + evbuffer_drain(evb, term_pos - buf); |
| + return STATUS_OK; |
| +} |
| + |
| +bool Conn::EmitHandshake(struct bufferevent* bev) { |
| + std::vector<std::string> boilerplate; |
| + boilerplate.push_back("HTTP/1.1 101 WebSocket Protocol Handshake"); |
| + boilerplate.push_back("Upgrade: WebSocket"); |
| + boilerplate.push_back("Connection: Upgrade"); |
| + |
| + { |
| + // Take care of Location field. |
| + char buf[128]; |
| + snprintf(buf, sizeof(buf), |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
check return value?
Denis Lagno
2010/12/03 16:28:50
Done.
|
| + "Sec-WebSocket-Location: ws://%s/echo", |
| + header_fields_["host"].c_str()); |
| + boilerplate.push_back(buf); |
| + } |
| + { |
| + // Take care of Origin field. |
| + if (header_fields_.find("origin") != header_fields_.end()) { |
| + char buf[128]; |
| + snprintf(buf, sizeof(buf), |
| + "Sec-WebSocket-Origin: %s", header_fields_["origin"].c_str()); |
| + boilerplate.push_back(buf); |
| + } |
| + } |
| + |
| + boilerplate.push_back(""); |
| + static const uint8_t kCRLF[] = "\r\n"; |
| + for (size_t i = 0; i < boilerplate.size(); ++i) { |
| + if (bufferevent_write(bev, boilerplate[i].c_str(), |
| + boilerplate[i].size()) || |
| + bufferevent_write(bev, kCRLF, RAWSIZEOF(kCRLF))) { |
| + return false; |
| + } |
| + } |
| + return !bufferevent_write(bev, &handshake_response_, |
| + sizeof(handshake_response_)); |
| +} |
| + |
| +Conn::Status Conn::ConsumeDestframe(struct evbuffer* evb) { |
| + uint8_t* buf = EVBUFFER_DATA(evb); |
| + size_t buf_size = EVBUFFER_LENGTH(evb); |
| + |
| + if (buf_size < 1) { |
| + return STATUS_INCOMPLETE; |
| + } |
| + if (buf[0] != 0) { |
| + return STATUS_ABORT; |
| + } |
| + if (buf_size < 1 + 1) { |
| + return STATUS_INCOMPLETE; |
| + } |
| + uint8_t* buf_end = buf + buf_size; |
| + uint8_t* term_pos = std::find(buf + 1, buf_end, 0xff); |
| + if (term_pos == buf_end) { |
| + if (buf_size >= Web2SocketServ::kReadBufferLimit) { |
| + // So big and still worth nothing. |
| + return STATUS_ABORT; |
| + } |
| + return STATUS_INCOMPLETE; |
| + } |
| + if (!FetchNamePort(buf + 1, term_pos, &destname_, &destport_)) { |
| + return STATUS_ABORT; |
| + } |
| + evbuffer_drain(evb, term_pos - buf + 1); |
| + return STATUS_OK; |
| +} |
| + |
| +Conn::Status Conn::ConsumeFrameHeader(struct evbuffer* evb) { |
| + uint8_t* buf = EVBUFFER_DATA(evb); |
| + size_t buf_size = EVBUFFER_LENGTH(evb); |
| + |
| + if (buf_size < 1) { |
| + return STATUS_INCOMPLETE; |
| + } |
| + if (buf[0] != 0) { |
| + return STATUS_ABORT; |
| + } |
| + evbuffer_drain(evb, 1); |
| + return STATUS_OK; |
| +} |
| + |
| +Conn::Status Conn::ProcessFrameData(struct evbuffer* evb) { |
| + uint8_t* buf = EVBUFFER_DATA(evb); |
| + size_t buf_size = EVBUFFER_LENGTH(evb); |
| + |
| + if (buf_size < 1) { |
|
tyoshino (SeeGerritForStatus)
2010/12/03 07:53:33
We can remove if-clause?
Denis Lagno
2010/12/03 16:28:50
It shouldn't happen in practice, but just a safety
|
| + return STATUS_INCOMPLETE; |
| + } |
| + uint8_t* buf_end = buf + buf_size; |
| + uint8_t* term_pos = std::find(buf, buf_end, 0xff); |
| + bool term_detected = (term_pos != buf_end); |
| + if (term_detected) { |
| + buf_size = term_pos - buf; |
| + } |
| + switch (phase_) { |
| + case PHASE_INSIDE_FRAME_BASE64: { |
| + if (term_detected && buf_size % 4) { |
| + // base64 is encoded in chunks of 4 bytes. |
| + return STATUS_ABORT; |
| + } |
| + if (buf_size < 4) { |
| + DCHECK(!term_detected); |
| + return STATUS_INCOMPLETE; |
| + } |
| + size_t bytes_to_process_atm = (buf_size / 4) * 4; |
| + std::string out_bytes; |
| + base::Base64Decode(std::string(buf, buf + bytes_to_process_atm), |
| + &out_bytes); |
| + evbuffer_drain(evb, bytes_to_process_atm); |
| + DCHECK(destchan_.bev_ != NULL); |
| + if (bufferevent_write(destchan_.bev_, |
| + out_bytes.c_str(), out_bytes.size())) { |
| + return STATUS_ABORT; |
| + } |
| + break; |
| + } |
| + case PHASE_INSIDE_FRAME_SKIP: { |
| + evbuffer_drain(evb, buf_size); |
| + break; |
| + } |
| + default: { |
| + return STATUS_ABORT; |
| + } |
| + } |
| + if (term_detected) { |
| + evbuffer_drain(evb, 1); |
| + return STATUS_OK; |
| + } |
| + return STATUS_INCOMPLETE; |
| +} |
| + |
| +bool Conn::TryConnectDest(const struct sockaddr* addr, |
| + socklen_t addrlen) { |
| + if (destchan_.sock_ >= 0 || |
| + destchan_.bev_ != NULL) { |
| + return false; |
| + } |
| + destchan_.sock_ = socket(addr->sa_family, SOCK_STREAM, 0); |
| + if (destchan_.sock_ < 0) { |
| + return false; |
| + } |
| + if (!Web2SocketServ::SetNonBlock(destchan_.sock_)) { |
| + return false; |
| + } |
| + if (connect(destchan_.sock_, addr, addrlen)) { |
| + if (errno != EINPROGRESS) { |
| + return false; |
| + } |
| + } |
| + destchan_.bev_ = bufferevent_new(destchan_.sock_, |
| + &OnDestchanRead, |
| + &OnDestchanWrite, |
| + &OnDestchanError, |
| + token_); |
| + if (destchan_.bev_ == NULL) { |
| + return false; |
| + } |
| + if (bufferevent_base_set(master_->evbase_, destchan_.bev_)) { |
| + return false; |
| + } |
| + bufferevent_setwatermark(destchan_.bev_, EV_READ, |
| + 0, Web2SocketServ::kReadBufferLimit); |
| + if (bufferevent_enable(destchan_.bev_, EV_READ | EV_WRITE)) { |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +void Conn::OnPrimchanRead(struct bufferevent* bev, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (bev == NULL || |
| + cs == NULL || |
| + bev != cs->primchan_.bev_) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) <= 0) { |
| + return; |
| + } |
| + cs->master_->MarkConnImportance(cs, true); |
| + for (;;) { |
| + switch (cs->phase_) { |
| + case PHASE_WAIT_HANDSHAKE: { |
| + switch (cs->ConsumeHeader(EVBUFFER_INPUT(bev))) { |
| + case STATUS_OK: { |
| + break; |
| + } |
| + case STATUS_INCOMPLETE: { |
| + return; |
| + } |
| + case STATUS_ABORT: |
| + default: { |
| + cs->master_->ZapConn(cs); |
| + return; |
| + } |
| + } |
| + // Header consumed OK. Do respond. |
| + if (!cs->EmitHandshake(bev)) { |
| + cs->master_->ZapConn(cs); |
| + return; |
| + } |
| + cs->phase_ = PHASE_WAIT_DESTFRAME; |
| + return; |
| + } |
| + case PHASE_WAIT_DESTFRAME: { |
| + switch (cs->ConsumeDestframe(EVBUFFER_INPUT(bev))) { |
| + case STATUS_OK: { |
| + { |
| + struct sockaddr_in sa; |
| + memset(&sa, 0, sizeof(sa)); |
| + sa.sin_port = htons(cs->destport_); |
| + if (inet_pton(sa.sin_family = AF_INET, |
| + cs->destname_.c_str(), |
| + &sa.sin_addr) == 1) { |
| + // valid IPv4 address supplied. |
| + if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) { |
| + cs->phase_ = PHASE_WAIT_DESTCONNECT; |
| + return; |
| + } |
| + } |
| + } |
| + { |
| + if (cs->destname_.size() >= 2 && |
| + cs->destname_[0] == '[' && |
| + cs->destname_[cs->destname_.size() - 1] == ']') { |
| + // Literal IPv6 address in brackets. |
| + cs->destname_ = |
| + cs->destname_.substr(1, cs->destname_.size() - 2); |
| + } |
| + struct sockaddr_in6 sa; |
| + memset(&sa, 0, sizeof(sa)); |
| + sa.sin6_port = htons(cs->destport_); |
| + if (inet_pton(sa.sin6_family = AF_INET6, |
| + cs->destname_.c_str(), |
| + &sa.sin6_addr) == 1) { |
| + // valid IPv6 address supplied. |
| + if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) { |
| + cs->phase_ = PHASE_WAIT_DESTCONNECT; |
| + return; |
| + } |
| + } |
| + } |
| + // Try to asynchronously perform DNS resolution. |
| + evdns_resolve_ipv4(cs->destname_.c_str(), 0, |
| + &OnDestResolutionIPv4, token); |
| + evdns_resolve_ipv6(cs->destname_.c_str(), 0, |
| + &OnDestResolutionIPv6, token); |
| + cs->phase_ = PHASE_WAIT_DESTCONNECT; |
| + return; |
| + } |
| + case STATUS_INCOMPLETE: { |
| + return; |
| + } |
| + case STATUS_ABORT: |
| + default: { |
| + cs->Shut(); |
| + return; |
| + } |
| + } |
| + } |
| + case PHASE_WAIT_DESTCONNECT: { |
| + if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) >= |
| + Web2SocketServ::kReadBufferLimit) { |
| + cs->Shut(); |
| + } |
| + return; |
| + } |
| + case PHASE_OUTSIDE_FRAME: { |
| + switch (cs->ConsumeFrameHeader(EVBUFFER_INPUT(bev))) { |
| + case STATUS_OK: { |
| + cs->phase_ = PHASE_INSIDE_FRAME_BASE64; |
| + // Process remaining data if any. |
| + break; |
| + } |
| + case STATUS_SKIP: { |
| + cs->phase_ = PHASE_INSIDE_FRAME_SKIP; |
| + // Process remaining data if any. |
| + break; |
| + } |
| + case STATUS_INCOMPLETE: { |
| + return; |
| + } |
| + case STATUS_ABORT: |
| + default: { |
| + cs->Shut(); |
| + return; |
| + } |
| + } |
| + break; |
| + } |
| + case PHASE_INSIDE_FRAME_BASE64: |
| + case PHASE_INSIDE_FRAME_SKIP: { |
| + switch (cs->ProcessFrameData(EVBUFFER_INPUT(bev))) { |
| + case STATUS_OK: { |
| + cs->phase_ = PHASE_OUTSIDE_FRAME; |
| + // Handle remaining data if any. |
| + break; |
| + } |
| + case STATUS_INCOMPLETE: { |
| + return; |
| + } |
| + case STATUS_ABORT: |
| + default: { |
| + cs->Shut(); |
| + return; |
| + } |
| + } |
| + break; |
| + } |
| + case PHASE_SHUT: { |
| + evbuffer_drain(EVBUFFER_INPUT(bev), |
| + EVBUFFER_LENGTH(EVBUFFER_INPUT(bev))); |
| + return; |
| + } |
| + case PHASE_DEFUNCT: |
| + default: { |
| + // Must not reach here. |
| + cs->master_->ZapConn(cs); |
| + return; |
| + } |
| + } |
| + } |
| +} |
| + |
| +void Conn::OnPrimchanWrite(struct bufferevent* bev, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (bev == NULL || |
| + cs == NULL || |
| + bev != cs->primchan_.bev_) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (cs->phase_ >= PHASE_SHUT) { |
| + cs->master_->ZapConn(cs); |
| + return; |
| + } |
| + if (cs->phase_ > PHASE_WAIT_DESTCONNECT) { |
| + OnDestchanRead(cs->destchan_.bev_, token); |
| + } |
| +} |
| + |
| +void Conn::OnPrimchanError(struct bufferevent* bev, |
| + short what, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (bev == NULL || |
| + cs == NULL || |
| + bev != cs->primchan_.bev_) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (cs->phase_ >= PHASE_SHUT) { |
| + cs->master_->ZapConn(cs); |
| + } else { |
| + cs->Shut(); |
| + } |
| +} |
| + |
| +void Conn::OnDestResolutionIPv4(int result, char type, |
| + int count, int ttl, |
| + void* addr_list, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (cs == NULL) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (cs->phase_ != PHASE_WAIT_DESTCONNECT) { |
| + return; |
| + } |
| + if (result == DNS_ERR_NONE && |
| + count >= 1 && |
| + addr_list != NULL && |
| + type == DNS_IPv4_A) { |
| + for (int i = 0; i < count; ++i) { |
| + struct sockaddr_in sa; |
| + memset(&sa, 0, sizeof(sa)); |
| + sa.sin_family = AF_INET; |
| + sa.sin_port = htons(cs->destport_); |
| + DCHECK(sizeof(sa.sin_addr) == sizeof(struct in_addr)); |
| + memcpy(&sa.sin_addr, |
| + static_cast<struct in_addr*>(addr_list) + i, |
| + sizeof(sa.sin_addr)); |
| + if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) { |
| + return; |
| + } |
| + } |
| + } |
| + cs->destresolution_ipv4_failure_ = true; |
| + if (cs->destresolution_ipv4_failure_ && cs->destresolution_ipv6_failure_) { |
| + cs->Shut(); |
| + } |
| +} |
| + |
| +void Conn::OnDestResolutionIPv6(int result, char type, |
| + int count, int ttl, |
| + void* addr_list, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (cs == NULL) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (cs->phase_ != PHASE_WAIT_DESTCONNECT) { |
| + return; |
| + } |
| + if (result == DNS_ERR_NONE && |
| + count >= 1 && |
| + addr_list != NULL && |
| + type == DNS_IPv6_AAAA) { |
| + for (int i = 0; i < count; ++i) { |
| + struct sockaddr_in6 sa; |
| + memset(&sa, 0, sizeof(sa)); |
| + sa.sin6_family = AF_INET6; |
| + sa.sin6_port = htons(cs->destport_); |
| + DCHECK(sizeof(sa.sin6_addr) == sizeof(struct in6_addr)); |
| + memcpy(&sa.sin6_addr, |
| + static_cast<struct in6_addr*>(addr_list) + i, |
| + sizeof(sa.sin6_addr)); |
| + if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) { |
| + return; |
| + } |
| + } |
| + } |
| + cs->destresolution_ipv6_failure_ = true; |
| + if (cs->destresolution_ipv4_failure_ && cs->destresolution_ipv6_failure_) { |
| + cs->Shut(); |
| + } |
| +} |
| + |
| +void Conn::OnDestConnectTimeout(int, short, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (cs == NULL) { |
| + // Sanity check failed. |
| + } |
| + if (cs->phase_ > PHASE_WAIT_DESTCONNECT) { |
| + return; |
| + } |
| + cs->Shut(); |
| +} |
| + |
| +void Conn::OnDestchanRead(struct bufferevent* bev, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (bev == NULL || |
| + cs == NULL || |
| + bev != cs->destchan_.bev_) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) <= 0) { |
| + return; |
| + } |
| + if (cs->primchan_.bev_ == NULL) { |
| + cs->master_->ZapConn(cs); |
| + return; |
| + } |
| + cs->master_->MarkConnImportance(cs, true); |
| + std::string out_bytes; |
| + base::Base64Encode( |
| + std::string( |
| + static_cast<const char*>(static_cast<void*>( |
| + EVBUFFER_DATA(EVBUFFER_INPUT(bev)))), |
| + EVBUFFER_LENGTH(EVBUFFER_INPUT(bev))), |
| + &out_bytes); |
| + evbuffer_drain(EVBUFFER_INPUT(bev), EVBUFFER_LENGTH(EVBUFFER_INPUT(bev))); |
| + static const uint8_t frame_header[] = { 0x00 }; |
| + static const uint8_t frame_terminator[] = { 0xff }; |
| + if (bufferevent_write(cs->primchan_.bev_, |
| + frame_header, sizeof(frame_header)) || |
| + bufferevent_write(cs->primchan_.bev_, |
| + out_bytes.c_str(), out_bytes.size()) || |
| + bufferevent_write(cs->primchan_.bev_, |
| + frame_terminator, sizeof(frame_terminator))) { |
| + cs->Shut(); |
| + } |
| +} |
| + |
| + |
| +void Conn::OnDestchanWrite(struct bufferevent* bev, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (bev == NULL || |
| + cs == NULL || |
| + bev != cs->destchan_.bev_) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (cs->phase_ == PHASE_WAIT_DESTCONNECT) { |
| + cs->phase_ = PHASE_OUTSIDE_FRAME; |
| + } |
| + OnPrimchanRead(cs->primchan_.bev_, token); |
| +} |
| + |
| +void Conn::OnDestchanError(struct bufferevent* bev, |
| + short what, Token token) { |
| + Conn* cs = Conn::Get(token); |
| + if (bev == NULL || |
| + cs == NULL || |
| + bev != cs->destchan_.bev_) { |
| + // Sanity check failed. |
| + return; |
| + } |
| + if (cs->phase_ >= PHASE_SHUT) { |
| + cs->master_->ZapConn(cs); |
| + } else { |
| + cs->Shut(); |
| + } |
| +} |
| + |
| +Conn::Token Conn::last_token_ = 0; |
| +Conn::TokenMap Conn::token_map_; |