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