1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9 #include <quic/api/IoBufQuicBatch.h>
10
11 #include <quic/common/SocketUtil.h>
12 #include <quic/happyeyeballs/QuicHappyEyeballsFunctions.h>
13
14 namespace quic {
IOBufQuicBatch(BatchWriterPtr && batchWriter,bool threadLocal,folly::AsyncUDPSocket & sock,const folly::SocketAddress & peerAddress,QuicTransportStatsCallback * statsCallback,QuicClientConnectionState::HappyEyeballsState * happyEyeballsState)15 IOBufQuicBatch::IOBufQuicBatch(
16 BatchWriterPtr&& batchWriter,
17 bool threadLocal,
18 folly::AsyncUDPSocket& sock,
19 const folly::SocketAddress& peerAddress,
20 QuicTransportStatsCallback* statsCallback,
21 QuicClientConnectionState::HappyEyeballsState* happyEyeballsState)
22 : batchWriter_(std::move(batchWriter)),
23 threadLocal_(threadLocal),
24 sock_(sock),
25 peerAddress_(peerAddress),
26 statsCallback_(statsCallback),
27 happyEyeballsState_(happyEyeballsState) {}
28
write(std::unique_ptr<folly::IOBuf> && buf,size_t encodedSize)29 bool IOBufQuicBatch::write(
30 std::unique_ptr<folly::IOBuf>&& buf,
31 size_t encodedSize) {
32 pktSent_++;
33
34 // see if we need to flush the prev buffer(s)
35 if (batchWriter_->needsFlush(encodedSize)) {
36 // continue even if we get an error here
37 flush(FlushType::FLUSH_TYPE_ALWAYS);
38 }
39
40 // try to append the new buffers
41 if (batchWriter_->append(
42 std::move(buf),
43 encodedSize,
44 peerAddress_,
45 threadLocal_ ? &sock_ : nullptr)) {
46 // return if we get an error here
47 return flush(FlushType::FLUSH_TYPE_ALWAYS);
48 }
49
50 return true;
51 }
52
flush(FlushType flushType)53 bool IOBufQuicBatch::flush(FlushType flushType) {
54 if (threadLocal_ &&
55 (flushType == FlushType::FLUSH_TYPE_ALLOW_THREAD_LOCAL_DELAY)) {
56 return true;
57 }
58 bool ret = flushInternal();
59 reset();
60
61 return ret;
62 }
63
reset()64 void IOBufQuicBatch::reset() {
65 batchWriter_->reset();
66 }
67
isRetriableError(int err)68 bool IOBufQuicBatch::isRetriableError(int err) {
69 return err == EAGAIN || err == EWOULDBLOCK || err == ENOBUFS;
70 }
71
flushInternal()72 bool IOBufQuicBatch::flushInternal() {
73 if (batchWriter_->empty()) {
74 return true;
75 }
76
77 bool written = false;
78 folly::Optional<int> firstSocketErrno;
79 if (!happyEyeballsState_ || happyEyeballsState_->shouldWriteToFirstSocket) {
80 auto consumed = batchWriter_->write(sock_, peerAddress_);
81 if (consumed < 0) {
82 firstSocketErrno = errno;
83 }
84 written = (consumed >= 0);
85 if (happyEyeballsState_) {
86 happyEyeballsState_->shouldWriteToFirstSocket =
87 (consumed >= 0 || isRetriableError(errno));
88
89 if (!happyEyeballsState_->shouldWriteToFirstSocket) {
90 sock_.pauseRead();
91 }
92 }
93 }
94
95 // If error occured on first socket, kick off second socket immediately
96 if (!written && happyEyeballsState_ &&
97 happyEyeballsState_->connAttemptDelayTimeout &&
98 happyEyeballsState_->connAttemptDelayTimeout->isScheduled()) {
99 happyEyeballsState_->connAttemptDelayTimeout->timeoutExpired();
100 happyEyeballsState_->connAttemptDelayTimeout->cancelTimeout();
101 }
102
103 folly::Optional<int> secondSocketErrno;
104 if (happyEyeballsState_ && happyEyeballsState_->shouldWriteToSecondSocket) {
105 auto consumed = batchWriter_->write(
106 *happyEyeballsState_->secondSocket,
107 happyEyeballsState_->secondPeerAddress);
108 if (consumed < 0) {
109 secondSocketErrno = errno;
110 }
111
112 // written is marked true if either socket write succeeds
113 written |= (consumed >= 0);
114 happyEyeballsState_->shouldWriteToSecondSocket =
115 (consumed >= 0 || isRetriableError(errno));
116 if (!happyEyeballsState_->shouldWriteToSecondSocket) {
117 happyEyeballsState_->secondSocket->pauseRead();
118 }
119 }
120
121 if (!written && statsCallback_) {
122 if (firstSocketErrno.has_value()) {
123 QUIC_STATS(
124 statsCallback_,
125 onUDPSocketWriteError,
126 QuicTransportStatsCallback::errnoToSocketErrorType(
127 firstSocketErrno.value()));
128 }
129 if (secondSocketErrno.has_value()) {
130 QUIC_STATS(
131 statsCallback_,
132 onUDPSocketWriteError,
133 QuicTransportStatsCallback::errnoToSocketErrorType(
134 secondSocketErrno.value()));
135 }
136 }
137
138 // If we have no happy eyeballs state, we only care if the first socket had
139 // an error. Otherwise we check both.
140 if ((!happyEyeballsState_ && firstSocketErrno.has_value() &&
141 !isRetriableError(firstSocketErrno.value())) ||
142 (happyEyeballsState_ && !happyEyeballsState_->shouldWriteToFirstSocket &&
143 !happyEyeballsState_->shouldWriteToSecondSocket)) {
144 auto firstSocketErrorMsg = firstSocketErrno.has_value()
145 ? folly::to<std::string>(
146 folly::errnoStr(firstSocketErrno.value()), ", ")
147 : "";
148 auto secondSocketErrorMsg = secondSocketErrno.has_value()
149 ? folly::errnoStr(secondSocketErrno.value())
150 : "";
151 auto errorMsg =
152 folly::to<std::string>(firstSocketErrorMsg, secondSocketErrorMsg);
153 // Both sockets becomes fatal, close connection
154 VLOG(4) << "Error writing to the socket " << errorMsg << " "
155 << peerAddress_;
156
157 // We can get write error for any reason, close the conn only if network
158 // is unreachable, for all others, we throw a transport exception
159 if (isNetworkUnreachable(errno)) {
160 throw QuicInternalException(
161 folly::to<std::string>("Error on socket write ", errorMsg),
162 LocalErrorCode::CONNECTION_ABANDONED);
163 } else {
164 throw QuicTransportException(
165 folly::to<std::string>("Error on socket write ", errorMsg),
166 TransportErrorCode::INTERNAL_ERROR);
167 }
168 }
169
170 if (!written) {
171 // This can happen normally, so ignore. Now we treat most errors same
172 // as a loss to avoid looping.
173 return false; // done
174 }
175
176 return true; // success, not done yet
177 }
178 } // namespace quic
179