Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 5.6k
Add Reverse Diagnostics Server#33307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
josalem
merged 52 commits into
dotnet:master
from
josalem:dev/josalem/diag-server-handshakeApr 20, 2020
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
bc21866
Add Advertise IPC Command
0255b37
Add untested select connect and nonblocking accept
7f8bb61
Add select API on windows
4b97ca8
Implement select for unix
0cb1e88
Update diagnostics server to use both modes
dbc9624
Add DiagnosticsIpcFactory abstraction
52cd9b0
Fix precondition
3918060
fix x86 build
33da551
Fixed errors
f7a28c4
fix pipe connection on window
22e4476
fix gcc build
8204e70
fix gcc build
a74f8a3
Change environment variable name
6b1fe2a
Remove unnecessary protocol code
c3d1bfd
Add retry semantics to reversed pipe
0045b1c
Use random 16 bit number for cookie
2c3baa4
Adding const-ness to Poll APIs
398d0d8
reset timeout on connection failure/change
17eb025
Clean up caching logic and add logging
dcd0329
Code cleaning
ebf9fb7
DiagnosticsIpcFactory -> IpcStreamFactory
5eae64c
Change ownership of connections
834d71d
Modify abstraction
0a193b0
Little bit of code cleaning
ddc2880
Change Advertise Cookie to 128 bit GUID
74244be
Add comment about connect
911ca0a
Simplify advertise meta-protocol
8c0630a
Clean up resources in ~IpcStream::DiagnosticsIpc
ead4041
Fix Windows implementation
e905958
fix const-ness on unix
89ce842
Add some asserts and comments
8adb3f9
Fix AV in shutdown path
23f1d00
Add initial test harness and sample tests
76b8c04
fix test issue on mac
d651b7d
Fix bad access in unix diag ipc
5a3fe23
More tests
f44fd01
Fix race in Windows Poll code
3a4e77e
Test updates
469d425
fix nits from review
d93d846
Add ConnectionState abstraction
0fce7bc
Add test case
6e2f642
Fix windows build
18c9959
Fix test
91d6d0e
Introduce a timeout to read/write
d4bf4c1
Implement timeout read/write for unix
9311238
adjust poll timeout to be more aggressive
b0e1218
Fix nits from review
1cd8f29
simplify branching in GetIpcPollHandle
3530a25
Add 2 byte field to advertise
67acd21
Fix cast for advertize
8c83f22
additional logging in tests
1cbb720
Loops unix send/recv to handle partial io
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
277 changes: 234 additions & 43 deletions
277 src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -12,13 +12,20 @@ | ||
| #include "diagnosticsipc.h" | ||
| #include "processdescriptor.h" | ||
| IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress) : | ||
| #if __GNUC__ | ||
| #include <poll.h> | ||
| #else | ||
| #include <sys/poll.h> | ||
| #endif // __GNUC__ | ||
| IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress, ConnectionMode mode) : | ||
| mode(mode), | ||
| _serverSocket(serverSocket), | ||
| _pServerAddress(new sockaddr_un), | ||
| _isClosed(false) | ||
| _isClosed(false), | ||
| _isListening(false) | ||
| { | ||
| _ASSERTE(_pServerAddress != nullptr); | ||
| _ASSERTE(_serverSocket != -1); | ||
| _ASSERTE(pServerAddress != nullptr); | ||
| if (_pServerAddress == nullptr || pServerAddress == nullptr) | ||
| @@ -32,24 +39,8 @@ IpcStream::DiagnosticsIpc::~DiagnosticsIpc() | ||
| delete _pServerAddress; | ||
| } | ||
| IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const pIpcName, ErrorCallback callback) | ||
| IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const pIpcName, ConnectionMode mode, ErrorCallback callback) | ||
| { | ||
| #ifdef __APPLE__ | ||
| mode_t prev_mask = umask(~(S_IRUSR | S_IWUSR)); // This will set the default permission bit to 600 | ||
| #endif // __APPLE__ | ||
| const int serverSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); | ||
| if (serverSocket == -1) | ||
| { | ||
| if (callback != nullptr) | ||
| callback(strerror(errno), errno); | ||
| #ifdef __APPLE__ | ||
| umask(prev_mask); | ||
| #endif // __APPLE__ | ||
| _ASSERTE(!"Failed to create diagnostics IPC socket."); | ||
| return nullptr; | ||
| } | ||
| sockaddr_un serverAddress{}; | ||
| serverAddress.sun_family = AF_UNIX; | ||
| @@ -71,6 +62,24 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p | ||
| "socket"); | ||
| } | ||
| if (mode == ConnectionMode::CLIENT) | ||
| return new IpcStream::DiagnosticsIpc(-1, &serverAddress, ConnectionMode::CLIENT); | ||
| #ifdef __APPLE__ | ||
| mode_t prev_mask = umask(~(S_IRUSR | S_IWUSR)); // This will set the default permission bit to 600 | ||
| #endif // __APPLE__ | ||
| const int serverSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); | ||
| if (serverSocket == -1) | ||
| { | ||
| if (callback != nullptr) | ||
| callback(strerror(errno), errno); | ||
| #ifdef __APPLE__ | ||
| umask(prev_mask); | ||
| #endif // __APPLE__ | ||
| _ASSERTE(!"Failed to create diagnostics IPC socket."); | ||
| return nullptr; | ||
| } | ||
| #ifndef __APPLE__ | ||
| if (fchmod(serverSocket, S_IRUSR | S_IWUSR) == -1) | ||
| @@ -99,33 +108,52 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p | ||
| return nullptr; | ||
| } | ||
| const int fSuccessfulListen = ::listen(serverSocket, /* backlog */ 255); | ||
| #ifdef __APPLE__ | ||
| umask(prev_mask); | ||
| #endif // __APPLE__ | ||
| return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress, mode); | ||
| } | ||
| bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) | ||
| { | ||
| _ASSERTE(mode == ConnectionMode::SERVER); | ||
| if (mode != ConnectionMode::SERVER) | ||
| { | ||
| if (callback != nullptr) | ||
| callback("Cannot call Listen on a client connection", -1); | ||
| return false; | ||
| } | ||
| if (_isListening) | ||
| return true; | ||
| const int fSuccessfulListen = ::listen(_serverSocket, /* backlog */ 255); | ||
| if (fSuccessfulListen == -1) | ||
| { | ||
| if (callback != nullptr) | ||
| callback(strerror(errno), errno); | ||
| _ASSERTE(fSuccessfulListen != -1); | ||
| const int fSuccessUnlink = ::unlink(serverAddress.sun_path); | ||
| const int fSuccessUnlink = ::unlink(_pServerAddress->sun_path); | ||
| _ASSERTE(fSuccessUnlink != -1); | ||
| const int fSuccessClose = ::close(serverSocket); | ||
| const int fSuccessClose = ::close(_serverSocket); | ||
| _ASSERTE(fSuccessClose != -1); | ||
| #ifdef __APPLE__ | ||
| umask(prev_mask); | ||
| #endif // __APPLE__ | ||
| return nullptr; | ||
| return false; | ||
| } | ||
| else | ||
| { | ||
| _isListening = true; | ||
| return true; | ||
| } | ||
| #ifdef __APPLE__ | ||
| umask(prev_mask); | ||
| #endif // __APPLE__ | ||
| return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress); | ||
| } | ||
| IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const | ||
| IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) | ||
| { | ||
| _ASSERTE(mode == ConnectionMode::SERVER); | ||
| _ASSERTE(_isListening); | ||
| sockaddr_un from; | ||
| socklen_t fromlen = sizeof(from); | ||
| const int clientSocket = ::accept(_serverSocket, (sockaddr *)&from, &fromlen); | ||
| @@ -136,7 +164,114 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const | ||
| return nullptr; | ||
| } | ||
| return new IpcStream(clientSocket); | ||
| return new IpcStream(clientSocket, mode); | ||
| } | ||
| IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) | ||
| { | ||
| _ASSERTE(mode == ConnectionMode::CLIENT); | ||
| sockaddr_un clientAddress{}; | ||
| clientAddress.sun_family = AF_UNIX; | ||
| const int clientSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); | ||
| if (clientSocket == -1) | ||
| { | ||
| if (callback != nullptr) | ||
| callback(strerror(errno), errno); | ||
| return nullptr; | ||
| } | ||
| // We don't expect this to block since this is a Unix Domain Socket. `connect` may block until the | ||
| // TCP handshake is complete for TCP/IP sockets, but UDS don't use TCP. `connect` will return even if | ||
| // the server hasn't called `accept`. | ||
| if (::connect(clientSocket, (struct sockaddr *)_pServerAddress, sizeof(*_pServerAddress)) < 0) | ||
| { | ||
| if (callback != nullptr) | ||
| callback(strerror(errno), errno); | ||
| return nullptr; | ||
| } | ||
| return new IpcStream(clientSocket, ConnectionMode::CLIENT); | ||
| } | ||
| int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *rgIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) | ||
| { | ||
| // prepare the pollfd structs | ||
| pollfd *pollfds = new pollfd[nHandles]; | ||
| for (uint32_t i = 0; i < nHandles; i++) | ||
| { | ||
| rgIpcPollHandles[i].revents = 0; // ignore any values in revents | ||
| int fd = -1; | ||
| if (rgIpcPollHandles[i].pIpc != nullptr) | ||
| { | ||
| // SERVER | ||
| _ASSERTE(rgIpcPollHandles[i].pIpc->mode == ConnectionMode::SERVER); | ||
| fd = rgIpcPollHandles[i].pIpc->_serverSocket; | ||
| } | ||
| else | ||
| { | ||
| // CLIENT | ||
| _ASSERTE(rgIpcPollHandles[i].pStream != nullptr); | ||
| fd = rgIpcPollHandles[i].pStream->_clientSocket; | ||
| } | ||
| pollfds[i].fd = fd; | ||
| pollfds[i].events = POLLIN; | ||
| } | ||
| int retval = poll(pollfds, nHandles, timeoutMs); | ||
| // Check results | ||
| if (retval < 0) | ||
| { | ||
| for (uint32_t i = 0; i < nHandles; i++) | ||
| { | ||
| if ((pollfds[i].revents & POLLERR) && callback != nullptr) | ||
| callback(strerror(errno), errno); | ||
| rgIpcPollHandles[i].revents = (uint8_t)PollEvents::ERR; | ||
| } | ||
| delete[] pollfds; | ||
| return -1; | ||
| } | ||
| else if (retval == 0) | ||
| { | ||
| // we timed out | ||
| delete[] pollfds; | ||
| return 0; | ||
| } | ||
| for (uint32_t i = 0; i < nHandles; i++) | ||
| { | ||
| if (pollfds[i].revents != 0) | ||
| { | ||
| // error check FIRST | ||
| if (pollfds[i].revents & POLLHUP) | ||
| { | ||
| // check for hangup first because a closed socket | ||
| // will technically meet the requirements for POLLIN | ||
| // i.e., a call to recv/read won't block | ||
| rgIpcPollHandles[i].revents = (uint8_t)PollEvents::HANGUP; | ||
| delete[] pollfds; | ||
| return -1; | ||
josalem marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| else if ((pollfds[i].revents & (POLLERR|POLLNVAL))) | ||
| { | ||
| if (callback != nullptr) | ||
| callback("Poll error", (uint32_t)pollfds[i].revents); | ||
| rgIpcPollHandles[i].revents = (uint8_t)PollEvents::ERR; | ||
| delete[] pollfds; | ||
| return -1; | ||
josalem marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| else if (pollfds[i].revents & POLLIN) | ||
| { | ||
| rgIpcPollHandles[i].revents = (uint8_t)PollEvents::SIGNALED; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| delete[] pollfds; | ||
| return 1; | ||
| } | ||
| void IpcStream::DiagnosticsIpc::Close(ErrorCallback callback) | ||
| @@ -172,45 +307,101 @@ void IpcStream::DiagnosticsIpc::Unlink(ErrorCallback callback) | ||
| } | ||
| IpcStream::~IpcStream() | ||
| { | ||
| Close(); | ||
| } | ||
| void IpcStream::Close(ErrorCallback) | ||
| { | ||
| if (_clientSocket != -1) | ||
| { | ||
| Flush(); | ||
| const int fSuccessClose = ::close(_clientSocket); | ||
| _ASSERTE(fSuccessClose != -1); | ||
| _clientSocket = -1; | ||
| } | ||
| } | ||
| bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const | ||
| bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead, const int32_t timeoutMs) | ||
| { | ||
| _ASSERTE(lpBuffer != nullptr); | ||
| const ssize_t ssize = ::recv(_clientSocket, lpBuffer, nBytesToRead, 0); | ||
| const bool fSuccess = ssize != -1; | ||
| if (timeoutMs != InfiniteTimeout) | ||
| { | ||
| pollfd pfd; | ||
| pfd.fd = _clientSocket; | ||
| pfd.events = POLLIN; | ||
| int retval = poll(&pfd, 1, timeoutMs); | ||
| if (retval <= 0 || pfd.revents != POLLIN) | ||
| { | ||
| // timeout or error | ||
| return false; | ||
| } | ||
| // else fallthrough | ||
| } | ||
| uint8_t *lpBufferCursor = (uint8_t*)lpBuffer; | ||
| ssize_t currentBytesRead = 0; | ||
| ssize_t totalBytesRead = 0; | ||
| bool fSuccess = true; | ||
| while (fSuccess && nBytesToRead - totalBytesRead > 0) | ||
| { | ||
| currentBytesRead = ::recv(_clientSocket, lpBufferCursor, nBytesToRead - totalBytesRead, 0); | ||
| fSuccess = currentBytesRead != 0; | ||
| if (!fSuccess) | ||
| break; | ||
| totalBytesRead += currentBytesRead; | ||
| lpBufferCursor += currentBytesRead; | ||
| } | ||
| if (!fSuccess) | ||
| { | ||
| // TODO: Add error handling. | ||
| } | ||
| nBytesRead = static_cast<uint32_t>(ssize); | ||
| nBytesRead = static_cast<uint32_t>(totalBytesRead); | ||
| return fSuccess; | ||
| } | ||
| bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const | ||
| bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten, const int32_t timeoutMs) | ||
| { | ||
| _ASSERTE(lpBuffer != nullptr); | ||
| const ssize_t ssize = ::send(_clientSocket, lpBuffer, nBytesToWrite, 0); | ||
| const bool fSuccess = ssize != -1; | ||
| if (timeoutMs != InfiniteTimeout) | ||
| { | ||
| pollfd pfd; | ||
| pfd.fd = _clientSocket; | ||
| pfd.events = POLLOUT; | ||
| int retval = poll(&pfd, 1, timeoutMs); | ||
| if (retval <= 0 || pfd.revents != POLLOUT) | ||
| { | ||
| // timeout or error | ||
| return false; | ||
| } | ||
| // else fallthrough | ||
| } | ||
| uint8_t *lpBufferCursor = (uint8_t*)lpBuffer; | ||
| ssize_t currentBytesWritten = 0; | ||
| ssize_t totalBytesWritten = 0; | ||
| bool fSuccess = true; | ||
| while (fSuccess && nBytesToWrite - totalBytesWritten > 0) | ||
| { | ||
| currentBytesWritten = ::send(_clientSocket, lpBufferCursor, nBytesToWrite - totalBytesWritten, 0); | ||
| fSuccess = currentBytesWritten != -1; | ||
| if (!fSuccess) | ||
| break; | ||
| lpBufferCursor += currentBytesWritten; | ||
| totalBytesWritten += currentBytesWritten; | ||
| } | ||
| if (!fSuccess) | ||
| { | ||
| // TODO: Add error handling. | ||
| } | ||
| nBytesWritten = static_cast<uint32_t>(ssize); | ||
| nBytesWritten = static_cast<uint32_t>(totalBytesWritten); | ||
| return fSuccess; | ||
| } | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.