mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feat] (MG_Remote, P11 B1): the fd:<control>,<aux> client endpoint - adopt two connected unix sockets a broker opened for this process, refuse by name anything that is not a distinct connected AF_UNIX stream pair, and present the client's own PairBind pair before the Hello, over shared segments
This commit is contained in:
@@ -67,6 +67,21 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return static_cast<std::uint32_t>(::getpid());
|
||||
#endif
|
||||
}
|
||||
|
||||
// P11 B1: `fd:<control>,<aux>` - two decimal descriptor numbers, one comma, nothing else.
|
||||
// Strict on purpose: a stray space or sign is an operator's typo, and a typo that parsed
|
||||
// would name some other descriptor of this process.
|
||||
bool ParseFdEndpoint(const std::string& spec, int* outControl, int* outAux) {
|
||||
const auto comma = spec.find(',');
|
||||
if (comma == std::string::npos || spec.find(',', comma + 1) != std::string::npos) return false;
|
||||
const auto number = [](const std::string& text, int* out) {
|
||||
if (text.empty() || text.size() > 9 || text.find_first_not_of("0123456789") != std::string::npos)
|
||||
return false;
|
||||
*out = std::atoi(text.c_str());
|
||||
return true;
|
||||
};
|
||||
return number(spec.substr(0, comma), outControl) && number(spec.substr(comma + 1), outAux);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#define MGP5_C0_STUB(what) \
|
||||
@@ -843,6 +858,41 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
const std::string configured(MG_Config::Ipc.Control.c_str());
|
||||
// P11 B1 (CONTRACT-P11 B1): a PRE-CONNECTED PAIR, fd:<control>,<aux>. A client in another
|
||||
// app (or the shell) may not connect() to the server app's endpoint - SELinux `connectto`
|
||||
// (B0-CROSS-APP.md) - so the server app's broker connected both sockets and an app_process
|
||||
// helper passed the client ends in, inherited across its execve. This process only ADOPTS
|
||||
// them: it presents its own PairBind pair (the broker wrote nothing) and says Hello. Connect
|
||||
// dial, and no data endpoint - the aux socket carries the Welcome's descriptors, so the plane
|
||||
// is shared segments exactly as for `unix:`.
|
||||
if (configured.compare(0, 3, "fd:") == 0) {
|
||||
const std::string data(MG_Config::Ipc.Data.c_str());
|
||||
int control = -1;
|
||||
int aux = -1;
|
||||
if (!ParseFdEndpoint(configured.substr(3), &control, &aux)) {
|
||||
MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} MOBILEGL_IPC_CONTROL='%s' is not fd:<control>,<aux> "
|
||||
"(two descriptor numbers and one comma)",
|
||||
configured.c_str());
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
if (data != "auto" && data != "shm") {
|
||||
MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} unsupported control/data pair %s/%s - an fd: pair is "
|
||||
"a unix connection and carries shared segments only",
|
||||
configured.c_str(), data.c_str());
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
m_connectDial = true;
|
||||
m_dataEndpoint.clear();
|
||||
std::unique_ptr<Transport::SocketTransport> socket;
|
||||
const auto adopted = Transport::SocketTransport::AdoptConnectedPair(control, aux, socket);
|
||||
if (adopted != MOBILEGL_OK) return adopted;
|
||||
const auto started = StartOverSocket(std::move(socket));
|
||||
if (started == MOBILEGL_OK) {
|
||||
MGLOG_I("MG_Remote client: control=fd data=shm server=fd:%d,%d pid=%u dial=connect", control, aux,
|
||||
m_peerServerPid);
|
||||
}
|
||||
return started;
|
||||
}
|
||||
if (configured != "fork") {
|
||||
const bool tcp = configured.compare(0, 6, "tcp://") == 0;
|
||||
const bool unixSocket = configured.compare(0, 5, "unix:") == 0;
|
||||
|
||||
@@ -317,17 +317,13 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
}
|
||||
|
||||
// One more connection to `path` whose first bytes are `payload`, framed: a DataBind
|
||||
// (ConnectDataConnection) or a PairBind (ConnectTo). `what` names the frame in the log.
|
||||
MobileGLResult ConnectPresenting(const std::string& path, std::uint32_t timeoutMs, const void* payload,
|
||||
std::uint64_t size, const char* what, int* outFd) {
|
||||
*outFd = -1;
|
||||
// `payload`, framed, as the first bytes on the connected socket `fd`: a DataBind or a
|
||||
// PairBind. `what` names the frame in the log. The descriptor is left open either way; the
|
||||
// caller owns it.
|
||||
MobileGLResult SendFirstFrame(int fd, const void* payload, std::uint64_t size, const char* what) {
|
||||
std::vector<std::uint8_t> framed;
|
||||
const MobileGLResult appended = AppendFrame(framed, payload, size);
|
||||
if (appended != MOBILEGL_OK) return appended;
|
||||
int fd = -1;
|
||||
const MobileGLResult connected = ConnectOne(path, timeoutMs, &fd);
|
||||
if (connected != MOBILEGL_OK) return connected;
|
||||
std::size_t written = 0;
|
||||
while (written < framed.size()) {
|
||||
const ssize_t n = ::send(fd, framed.data() + written, framed.size() - written, MSG_NOSIGNAL);
|
||||
@@ -335,12 +331,47 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
WireLogError("MG_Remote SocketTransport: the %s could not be sent (%zu of %zu bytes): %s", what,
|
||||
written, framed.size(), std::strerror(errno));
|
||||
::close(fd);
|
||||
return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
// One more connection to `path` whose first bytes are `payload`, framed: a DataBind
|
||||
// (ConnectDataConnection) or a PairBind (ConnectTo). `what` names the frame in the log.
|
||||
MobileGLResult ConnectPresenting(const std::string& path, std::uint32_t timeoutMs, const void* payload,
|
||||
std::uint64_t size, const char* what, int* outFd) {
|
||||
*outFd = -1;
|
||||
int fd = -1;
|
||||
const MobileGLResult connected = ConnectOne(path, timeoutMs, &fd);
|
||||
if (connected != MOBILEGL_OK) return connected;
|
||||
const MobileGLResult sent = SendFirstFrame(fd, payload, size, what);
|
||||
if (sent != MOBILEGL_OK) {
|
||||
::close(fd);
|
||||
return sent;
|
||||
}
|
||||
*outFd = fd;
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
// P11 B1: what an adopted descriptor must be for the `fd:` endpoint - open, a connected
|
||||
// AF_UNIX stream socket. Returns null when it is, otherwise the reason, for the log line.
|
||||
const char* AdoptableUnixStreamProblem(int fd) {
|
||||
if (fd < 0) return "is negative";
|
||||
if (::fcntl(fd, F_GETFD) < 0) return "is not an open descriptor in this process";
|
||||
int type = 0;
|
||||
socklen_t typeSize = sizeof(type);
|
||||
if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &typeSize) != 0) return "is not a socket";
|
||||
if (type != SOCK_STREAM) return "is not a stream socket";
|
||||
sockaddr_storage local{};
|
||||
socklen_t localSize = sizeof(local);
|
||||
if (::getsockname(fd, reinterpret_cast<sockaddr*>(&local), &localSize) != 0 ||
|
||||
local.ss_family != AF_UNIX)
|
||||
return "is not an AF_UNIX socket (a TCP pair has no descriptor channel; use tcp:// instead)";
|
||||
sockaddr_storage peer{};
|
||||
socklen_t peerSize = sizeof(peer);
|
||||
if (::getpeername(fd, reinterpret_cast<sockaddr*>(&peer), &peerSize) != 0) return "is not connected";
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MobileGLResult SocketTransport::Listen(const std::string& path, int* outListenFd) {
|
||||
@@ -428,6 +459,52 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
MobileGLResult SocketTransport::AdoptConnectedPair(int controlFd, int auxFd,
|
||||
std::unique_ptr<SocketTransport>& outClient) {
|
||||
outClient.reset();
|
||||
// Checked before a byte is written, so a stale or mistyped `fd:` names what it points at
|
||||
// instead of writing a PairBind into something that is not the server.
|
||||
if (controlFd == auxFd) {
|
||||
WireLogError("MG_Remote: Refuse{ProtocolMismatch} fd: endpoint names descriptor %d twice; the control "
|
||||
"and aux connections are two sockets", controlFd);
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
const int fds[2] = {controlFd, auxFd};
|
||||
const char* names[2] = {"control", "aux"};
|
||||
for (int index = 0; index < 2; ++index) {
|
||||
if (const char* problem = AdoptableUnixStreamProblem(fds[index])) {
|
||||
WireLogError("MG_Remote: Refuse{ProtocolMismatch} fd: endpoint's %s descriptor %d %s", names[index],
|
||||
fds[index], problem);
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
// They arrived inheritable (the helper cleared FD_CLOEXEC so they would survive its execve);
|
||||
// from here they are this session's, and a program's own children must not inherit them.
|
||||
::fcntl(controlFd, F_SETFD, FD_CLOEXEC);
|
||||
::fcntl(auxFd, F_SETFD, FD_CLOEXEC);
|
||||
// P11 PAIR: whoever opened the two connections, the CLIENT presents the pair's identity -
|
||||
// one fresh nonce, control then aux - exactly as ConnectTo does, before the Hello. The
|
||||
// broker that connected them wrote nothing on them.
|
||||
std::uint8_t nonce[kPairNonceBytes];
|
||||
MobileGLResult result = MintNonce(nonce, sizeof(nonce));
|
||||
if (result == MOBILEGL_OK) {
|
||||
const auto controlBind = EncodePairBind(nonce, false);
|
||||
result = SendFirstFrame(controlFd, controlBind.data(), controlBind.size(),
|
||||
"fd: control connection's PairBind");
|
||||
}
|
||||
if (result == MOBILEGL_OK) {
|
||||
const auto auxBind = EncodePairBind(nonce, true);
|
||||
result = SendFirstFrame(auxFd, auxBind.data(), auxBind.size(), "fd: aux connection's PairBind");
|
||||
}
|
||||
if (result != MOBILEGL_OK) {
|
||||
::close(controlFd);
|
||||
::close(auxFd);
|
||||
return result;
|
||||
}
|
||||
outClient = std::make_unique<SocketTransport>(controlFd, auxFd, TransportRole::Client);
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
MobileGLResult SocketTransport::ConnectControl(const std::string& path, std::uint32_t timeoutMs,
|
||||
std::unique_ptr<SocketTransport>& outClient) {
|
||||
if (!TcpName(path)) return ConnectTo(path, timeoutMs, outClient);
|
||||
@@ -816,6 +893,12 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
MobileGLResult SocketTransport::AdoptConnectedPair(int, int, std::unique_ptr<SocketTransport>& outClient) {
|
||||
outClient.reset();
|
||||
WireLogError("MG_Remote SocketTransport: the fd: endpoint is POSIX only (CONTRACT-P11 B1)");
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
SocketTransport::SocketTransport(int, int, TransportRole role) : m_role(role) {}
|
||||
SocketTransport::~SocketTransport() = default;
|
||||
int SocketTransport::TakeDataFd() { return -1; }
|
||||
|
||||
@@ -104,6 +104,16 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
static MobileGLResult ConnectTo(const std::string& path, std::uint32_t timeoutMs,
|
||||
std::unique_ptr<SocketTransport>& outClient);
|
||||
|
||||
// P11 B1: the client's half when SOMEBODY ELSE connected - the `fd:<control>,<aux>`
|
||||
// endpoint. A broker in the server's own app (the only context SELinux lets connect to its
|
||||
// endpoint) opened both connections and handed the client ends over; this adopts them. Both
|
||||
// must be open, connected AF_UNIX stream sockets and distinct (refused by name otherwise,
|
||||
// nothing written). They become CLOEXEC, and the client then presents one PairBind pair on
|
||||
// them - a fresh nonce, control then aux - exactly as ConnectTo does: the broker writes
|
||||
// nothing, so the server's PairAcceptor pairs them by the client's own nonce.
|
||||
static MobileGLResult AdoptConnectedPair(int controlFd, int auxFd,
|
||||
std::unique_ptr<SocketTransport>& outClient);
|
||||
|
||||
// ---- PH-7 (4), ID-P7-3: a TCP data connection is BOUND, not paired --------
|
||||
//
|
||||
// The server used to pair a session's two connections by ARRIVAL ORDER inside a window
|
||||
|
||||
Reference in New Issue
Block a user