2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2017-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/ext/vsdebug/socket_transport.h"
18 #include "hphp/runtime/ext/vsdebug/debugger.h"
23 SocketTransport::SocketTransport(Debugger
* debugger
, int listenPort
) :
24 DebugTransport(debugger
),
26 m_clientConnected(false),
27 m_listenPort(listenPort
),
28 m_connectThread(this, &SocketTransport::listenForClientConnection
) {
32 assertx(m_abortPipeFd
[0] == -1 && m_abortPipeFd
[1] == -1);
34 // Create a set of pipe file descriptors to use to inform the thread
35 // polling for socket connections that it's time to exit.
37 m_connectThread
.start();
40 SocketTransport::~SocketTransport() {
46 stopConnectionThread();
49 void SocketTransport::stopConnectionThread() {
51 write(m_abortPipeFd
[1], &value
, 1);
52 close(m_abortPipeFd
[1]);
53 m_abortPipeFd
[1] = -1;
55 m_connectThread
.waitForEnd();
58 void SocketTransport::createAbortPipe() {
60 if (m_abortPipeFd
[0] != -1) {
61 close(m_abortPipeFd
[0]);
64 if (m_abortPipeFd
[1] != -1) {
65 close(m_abortPipeFd
[1]);
68 if (pipe(m_abortPipeFd
) < 0) {
70 VSDebugLogger::LogLevelError
,
71 "Failed to open pipe for transport termination event."
74 // This is unexpected and treated as fatal because we won't be able
75 // to stop the polling thread in an orderly fashion at this point.
80 void SocketTransport::onClientDisconnected() {
82 VSDebugLogger::LogLevelInfo
,
83 "SocketTransport client disconnected."
86 DebugTransport::onClientDisconnected();
88 // If we're not shutting down, start listening for new connections.
92 if (!m_terminating
&& m_clientConnected
) {
93 stopConnectionThread();
94 m_clientConnected
= false;
96 m_connectThread
.start();
101 bool SocketTransport::clientConnected() const {
103 return m_clientConnected
;
106 void SocketTransport::listenForClientConnection() {
107 // If there was a previous connection in the base class, shut it down.
108 // This will close the old transport and wait for both the send and receive
109 // worker threads to terminate before proceeding.
110 if (getTransportFd() >= 0) {
115 VSDebugLogger::LogLevelInfo
,
116 "SocketTransport worker thread listening for connections..."
122 abortFd
= m_abortPipeFd
[0];
123 assertx(abortFd
>= 0);
126 struct addrinfo hint
;
127 struct addrinfo
* ai
= nullptr;
128 std::vector
<int> socketFds
;
130 memset(&hint
, 0, sizeof(hint
));
131 hint
.ai_family
= AF_UNSPEC
;
132 hint
.ai_socktype
= SOCK_STREAM
;
133 hint
.ai_flags
= AI_PASSIVE
;
140 for (auto it
= socketFds
.begin(); it
!= socketFds
.end(); it
++) {
145 m_abortPipeFd
[0] = -1;
148 VSDebugLogger::LogLevelInfo
,
149 "SocketTransport connection polling thread exiting."
153 // Share existing DebuggerDisableIPv6 configuration with hphpd.
154 if (RuntimeOption::DebuggerDisableIPv6
) {
155 hint
.ai_family
= AF_INET
;
158 const auto name
= RuntimeOption::DebuggerServerIP
.empty()
160 : RuntimeOption::DebuggerServerIP
.c_str();
161 if (getaddrinfo(name
, std::to_string(m_listenPort
).c_str(), &hint
, &ai
)) {
163 VSDebugLogger::LogLevelError
,
164 "Failed to call getaddrinfo: %d.",
171 // Attempt to listen on the specified port on each of this host's available
173 struct addrinfo
* address
;
174 bool anyInterfaceBound
= false;
175 for (address
= ai
; address
!= nullptr; address
= address
->ai_next
) {
176 if (bindAndListen(address
, socketFds
)) {
177 anyInterfaceBound
= true;
181 if (!anyInterfaceBound
) {
183 VSDebugLogger::LogLevelWarning
,
184 "Debugger failed to bind to any interface!"
189 VSDebugLogger::LogLevelInfo
,
190 "Debugger bound to at least one interface."
194 waitForConnection(socketFds
, abortFd
);
197 bool SocketTransport::bindAndListen(
198 struct addrinfo
* address
,
199 std::vector
<int>& socketFds
201 int fd
= socket(address
->ai_family
,
202 address
->ai_socktype
,
203 address
->ai_protocol
);
205 if (fd
< 0 && errno
== EAFNOSUPPORT
) {
207 VSDebugLogger::LogLevelWarning
,
208 "SocketTransport: socket() call failed: %d",
212 // Don't bind to this address, but still try to process other
213 // addresses if there are any.
217 constexpr int yes
= 1;
218 setsockopt(fd
, SOL_SOCKET
, SO_REUSEADDR
, &yes
, sizeof(yes
));
219 if (address
->ai_family
== AF_INET6
) {
220 setsockopt(fd
, IPPROTO_IPV6
, IPV6_V6ONLY
, &yes
, sizeof(yes
));
223 socketFds
.push_back(fd
);
225 if (bind(fd
, address
->ai_addr
, address
->ai_addrlen
) < 0) {
227 VSDebugLogger::LogLevelError
,
228 "Failed to call bind: %d. (%s)",
236 if (listen(fd
, 1) < 0) {
238 VSDebugLogger::LogLevelError
,
239 "Failed to call listen on port %u: %d. (%s)",
251 void SocketTransport::rejectClientWithMsg(int newFd
, int abortFd
) {
253 VSDebugLogger::LogLevelInfo
,
254 "SocketTransport: new client connection rejected because another "
255 "client is already connected."
258 folly::dynamic rejectMsg
= folly::dynamic::object
;
259 rejectMsg
["category"] = OutputLevelError
;
260 rejectMsg
["output"] = "Could not attach to HHVM: another debugger "
261 "client is already attached!";
263 folly::dynamic response
= folly::dynamic::object
;
264 response
["event"] = EventTypeOutput
;
265 response
["body"] = rejectMsg
;
266 response
["type"] = MessageTypeEvent
;
268 // Send the user an error message.
269 std::string serialized
= folly::toJson(response
);
270 const char* output
= serialized
.c_str();
271 write(newFd
, output
, strlen(output
) + 1);
273 // Perform an orderly shutdown of the socket so that the message is actually
274 // sent and received. This requires us to shutdown the write end of the socket
275 // and then drain the receive buffer before closing the socket. If abortFd
276 // is signalled before this is complete, we just close the socket and bail.
277 ::shutdown(newFd
, SHUT_WR
);
280 size_t size
= sizeof(struct pollfd
) * 2;
281 struct pollfd
* fds
= (struct pollfd
*)malloc(size
);
282 char* buffer
= (char*)malloc(1024);
284 if (fds
!= nullptr) {
290 if (buffer
!= nullptr) {
295 if (buffer
== nullptr || fds
== nullptr) {
299 int eventMask
= POLLIN
| POLLERR
| POLLHUP
;
300 constexpr int abortIdx
= 0;
301 constexpr int readIdx
= 1;
303 memset(fds
, 0, size
);
304 fds
[abortIdx
].fd
= abortFd
;
305 fds
[abortIdx
].events
= eventMask
;
306 fds
[readIdx
].fd
= newFd
;
307 fds
[readIdx
].events
= eventMask
;
310 result
= poll(fds
, 2, -1);
311 if (result
== -EINTR
) {
313 } else if (result
< 0 ||
314 fds
[abortIdx
].revents
!= 0 ||
315 fds
[readIdx
].revents
& POLLERR
||
316 fds
[readIdx
].revents
& POLLHUP
320 } else if (fds
[readIdx
].revents
& POLLIN
) {
321 result
= read(newFd
, buffer
, 1024);
329 void SocketTransport::waitForConnection(
330 std::vector
<int>& socketFds
,
333 // Wait for a connection on any of the fds we are listening to. We allow only
334 // one debugger client to connect a a time, so once any bound fd accepts a
335 // connection, we stop listening on all the others.
336 int count
= socketFds
.size() + 1;
337 size_t size
= sizeof(struct pollfd
) * count
;
338 struct pollfd
* fds
= (struct pollfd
*)malloc(size
);
339 if (fds
== nullptr) {
341 VSDebugLogger::LogLevelError
,
342 "SocketTransport out of memory while trying to create pollfd."
347 memset(fds
, 0, size
);
350 if (fds
!= nullptr) {
355 // fds[0] will contain the read end of our "abort" pipe. Another thread will
356 // write data to tihs pipe to signal it's time for this worker to stop
357 // blocking in poll() and terminate.
358 int eventMask
= POLLIN
| POLLERR
| POLLHUP
;
360 fds
[0].events
= eventMask
;
362 for (unsigned int i
= 1; i
< count
; i
++) {
363 fds
[i
].fd
= socketFds
[i
- 1];
364 fds
[i
].events
= eventMask
;
367 // Poll socket fds until a connection is established or we're terminated.
370 VSDebugLogger::LogLevelInfo
,
371 "SocketTransport polling for connections..."
374 int ret
= poll(fds
, count
, -1);
381 VSDebugLogger::LogLevelError
,
382 "Polling inputs failed: %d. (%s)",
389 if (fds
[0].revents
!= 0) {
391 VSDebugLogger::LogLevelInfo
,
392 "Socket polling thread terminating due to shutdown request."
398 socklen_t len
= sizeof(sa
);
399 for (unsigned int i
= 1; i
< count
; i
++) {
400 if (fds
[i
].revents
& POLLIN
) {
401 int newFd
= ::accept(fds
[i
].fd
, &sa
, &len
);
404 VSDebugLogger::LogLevelWarning
,
405 "Accept returned an error: %d. (%s)",
412 if (m_clientConnected
) {
413 // A client is already connected!
415 rejectClientWithMsg(newFd
, abortFd
);
419 VSDebugLogger::LogLevelInfo
,
420 "SocketTransport: new client connection accepted."
423 // We have established a connection with a client.
424 m_clientConnected
= true;
425 setTransportFd(newFd
);
426 m_debugger
->setClientConnected(true);
431 // Reset the event flags.