feat(macos): validate dev + apple-dev presets on macOS, fix 3 cross-platform bugs
macOS port groundwork — core, server, tools, and tests now build and run on macOS 26.5 / Apple Silicon. ctest --preset dev green 21/21 (2 consecutive runs). apple-dev produces valid arm64 libvoicecat.a + XCFramework for the Swift Package. Three real cross-platform bugs found and fixed (all latent on Windows/Linux): 1. test_m2_voice.cpp POSIX branch missing <netdb.h> — Linux glibc transitively includes it, macOS doesn't. Would fail on any strict POSIX system. 2. SIGPIPE killing processes on macOS — writing to a closed TCP socket raises SIGPIPE by default (doesn't exist on Windows, benign on Linux). Fixed by ignoring SIGPIPE in both core client init and server startup (POSIX-only, #ifndef _WIN32). Production fix, not just tests. 3. Use-after-free of Asio's kqueue reactor on server shutdown — the deterministic test_tofu_flow segfault. TcpServerConn's tls_read_loop runs on a blocking-I/O thread; when Server::run() returned, io_context was destroyed while those threads were still running. On macOS kqueue the reactor pointer is null'd immediately -> segfault in socket.close(). Latent on Windows IOCP and Linux epoll. Fix: TcpAcceptor now tracks connections; new shutdown() closes all + joins threads before io is destroyed; Server::stop() now closes acceptor + media_relay too (was just io.stop()). Verified: dev + apple-dev presets build green, 21/21 tests pass, server starts + two vccli text chat over TLS (M1 on Mac), vccli --voice starts MIC stream via CoreAudio (M2 protocol-level), vccli --list-devices enumerates CoreAudio devices, xcodebuild -create-xcframework produces valid VoiceCatCore.xcframework. No ABI or proto changes. Docs updated: building.md, clients/apple/README.md, PROGRESS.md, CLAUDE.md status line.
This commit is contained in:
@@ -6,8 +6,9 @@ and what's next* read [`PROGRESS.md`](PROGRESS.md); for *design* read [`docs/`](
|
||||
|
||||
> **One-line status:** M5 (moderation & admin UI) is complete — permissions, kick/ban/move,
|
||||
> server-mute, channel CRUD, in-app account management, disconnect/keepalive/reaper. Windows
|
||||
> WinForms C# client is shipped (M4). `ctest --preset dev` green — 21/21 tests. macOS/iOS
|
||||
> Swift client is next. See [`PROGRESS.md`](PROGRESS.md).
|
||||
> WinForms C# client is shipped (M4). **macOS port validated** — `dev` + `apple-dev` presets
|
||||
> build green, 21/21 tests pass on macOS. `ctest --preset dev` green — 21/21 tests.
|
||||
> macOS/iOS Swift client is next. See [`PROGRESS.md`](PROGRESS.md).
|
||||
|
||||
VoiceCat = self-hosted native voice & text chat (TeamSpeak/Mumble-style). Plain TCP (control)
|
||||
+ UDP (media), no WebRTC, encrypted by default. A shared C++ core (`libvoicecat`) drives
|
||||
|
||||
67
PROGRESS.md
67
PROGRESS.md
@@ -10,6 +10,73 @@ up instantly. Newest status at the top.
|
||||
|
||||
## ▶ Where we left off / next action
|
||||
|
||||
- **Done:** **macOS port — `dev` + `apple-dev` presets validated** (2026-06-18). The core,
|
||||
server, tools, and tests now build and run on macOS (Apple Silicon, macOS 26.5, Apple clang
|
||||
21). This lays the groundwork for the macOS/iOS Swift client. Three real bugs found and
|
||||
fixed (all were cross-platform issues that manifested on macOS but were latent on
|
||||
Windows/Linux):
|
||||
1. **Missing `<netdb.h>` in `test_m2_voice.cpp` POSIX branch** — the raw-socket test's
|
||||
`#else` branch included `<arpa/inet.h>`/`<netinet/in.h>`/`<sys/socket.h>`/`<unistd.h>`
|
||||
but not `<netdb.h>` (needed for `addrinfo`/`getaddrinfo`/`freeaddrinfo`). On Linux glibc
|
||||
these headers transitively include `<netdb.h>`; on macOS they don't. Fixed by adding
|
||||
`# include <netdb.h>` to the POSIX branch (mirrors `core/src/core/client.cpp:16` which
|
||||
already had it). Real latent bug — would fail on any strict POSIX system.
|
||||
2. **SIGPIPE killing processes on macOS** — on macOS, writing to a closed TCP socket
|
||||
raises `SIGPIPE` by default (unlike Windows where it doesn't exist, or Linux where it's
|
||||
often benign). This killed `test_tofu_flow` (intermittent SIGPIPE/SEGFAULT) and would
|
||||
also kill `voicecat-server` and `vccli` in production when a peer dropped mid-write.
|
||||
Fixed by ignoring SIGPIPE (`std::signal(SIGPIPE, SIG_IGN)`) in both the core client
|
||||
init (`core/src/core/client.cpp` POSIX branch of the `#ifdef _WIN32` WSAStartup block)
|
||||
and the server startup (`server/src/server.cpp` before the `asio::signal_set`). Both are
|
||||
POSIX-only (`#ifndef _WIN32`), process-global, and idempotent. The server's
|
||||
`asio::signal_set(SIGINT, SIGTERM)` is unaffected (independent signals).
|
||||
3. **Use-after-free of Asio's kqueue reactor on server shutdown** — the root cause of the
|
||||
deterministic `test_tofu_flow` segfault (EXC_BAD_ACCESS in
|
||||
`kqueue_reactor::deregister_descriptor(this=0x0000000000000000)`). `TcpServerConn`'s
|
||||
`tls_read_loop` runs on a dedicated blocking-I/O thread (not async on `io_context`).
|
||||
When `Server::stop()` → `io.stop()` → `Server::run()` returned, the local
|
||||
`asio::io_context` was destroyed while `tls_read_loop` threads were still running. When
|
||||
a thread detected the disconnect and called `TcpServerConn::close()` → `socket.close()`
|
||||
→ Asio tried to deregister from the kqueue reactor — but the reactor (owned by
|
||||
`io_context`) was already destroyed, and on macOS kqueue the reactor pointer is null'd
|
||||
immediately. Latent on Windows (IOCP) and Linux (epoll) where the timing is more
|
||||
forgiving. **Fix:** `TcpAcceptor` now tracks its connections (new `conns_` vector +
|
||||
`conns_mu_`); `TcpAcceptor::stop()` closes all tracked connections while `io_context` is
|
||||
still alive; new `TcpAcceptor::shutdown()` method calls `stop()` then
|
||||
`wait_closed()` on each connection (new `TcpServerConn::wait_closed()` joins
|
||||
`tls_thread_`); `Server::run()` calls `acceptor.shutdown()` after `io.run()` returns and
|
||||
before `io` is destroyed; `Server::stop()`'s `stop_fn_` now calls `acceptor.stop()` +
|
||||
`media_relay->stop()` + `io.stop()` (was just `io.stop()`). After `acceptor.shutdown()`,
|
||||
when `tls_read_loop` threads exit and call `on_disconnected` → `ConnSession::close()` →
|
||||
`TcpServerConn::close()`, the `close()` is a no-op (`closing_.exchange(true)` returns
|
||||
true) — no reactor access occurs after `io` is destroyed.
|
||||
- **Environment setup:** vcpkg cloned to `~/code/vcpkg` + bootstrapped. `VCPKG_ROOT` must
|
||||
be set. Homebrew `autoconf-archive` is required (vcpkg's libsodium port needs it for
|
||||
autoreconf — `brew install autoconf-archive`). `autoconf`/`automake`/`libtool` were
|
||||
already installed; `glibtoolize` (Homebrew's macOS name for `libtoolize`) is handled by
|
||||
vcpkg automatically.
|
||||
- **Verified:** `cmake --preset dev` + `cmake --build --preset dev` green (21 binaries).
|
||||
`ctest --preset dev --parallel 1` — **21/21 green** (2 consecutive runs, 64s each).
|
||||
`cmake --preset apple-dev` + `cmake --build --preset apple-dev` green → valid 1.9 MB
|
||||
arm64 `libvoicecat.a` (167 exported C ABI symbols, correct visibility).
|
||||
`xcodebuild -create-xcframework` → valid `VoiceCatCore.xcframework` (macOS-arm64 slice
|
||||
with `voicecat.h` headers). Server runtime: `voicecat-server` starts, generates identity,
|
||||
SQLite, Lobby, binds TCP+UDP, clean SIGINT shutdown. Two `vccli` text chat over TLS
|
||||
(M1 exit criterion on Mac). `vccli --voice --input-mode vad` starts a MIC stream via
|
||||
CoreAudio (M2 protocol-level on Mac — ear test pending). `vccli --list-devices`
|
||||
enumerates 4 CoreAudio input + 3 output devices with correct defaults.
|
||||
- **Apple framework linking:** NOT needed — modern macOS ld (Xcode 26.5) auto-discovers
|
||||
CoreAudio/CoreFoundation frameworks in `/System/Library/Frameworks` without explicit
|
||||
`-framework` flags. miniaudio's `MINIAUDIO_IMPLEMENTATION` compiles the CoreAudio calls
|
||||
inline, and the linker resolves them automatically. No `if(APPLE)` CMake block was added.
|
||||
- **Docs updated:** `docs/building.md` (`apple-dev` row + §6 updated from "scaffolding" to
|
||||
"validated"), `clients/apple/README.md` (macOS slice build confirmed, iOS slices still
|
||||
scaffolding), `PROGRESS.md` (this entry).
|
||||
- **Still deferred (per scope):** macOS `SCREEN_AUDIO` loopback via ScreenCaptureKit (stub
|
||||
returns `false` — per `docs/voice.md §9`); iOS cross-compile presets (`apple-ios`/
|
||||
`apple-ios-sim` — scaffolding); `vc_audio_suspend`/`vc_audio_resume` ABI hooks (defer to
|
||||
iOS client milestone, keep ABI stable).
|
||||
|
||||
- **Done:** **CMake preset cleanup + cross-platform build config** (2026-06-18). The preset
|
||||
set was a mess — `dev` (never used), `m1-dev` (the one everyone used), `m2-dev`
|
||||
(cache-identical to `m1-dev`, never used), no optimized+tests preset, no stripping.
|
||||
|
||||
@@ -18,11 +18,22 @@ Planned shape (see [`docs/architecture.md`](../../docs/architecture.md) §4 and
|
||||
|
||||
Nothing here yet — the core must reach M2 (working voice) before the GUI is worth building.
|
||||
|
||||
## Building the core for Apple platforms (scaffolding)
|
||||
## Building the core for Apple platforms
|
||||
|
||||
The CMake presets `apple-dev`, `apple-ios`, and `apple-ios-sim` produce static `libvoicecat.a`
|
||||
slices for the Swift Package / XCFramework. They are **scaffolding** — not yet CI-validated.
|
||||
Build on macOS (they won't work on Windows/Linux):
|
||||
slices for the Swift Package / XCFramework.
|
||||
|
||||
**`apple-dev` (macOS slice) is validated** — builds green on macOS 26.5 / Apple Silicon
|
||||
(Apple clang 21, vcpkg `arm64-osx` triplet) and produces a valid 1.9 MB arm64 static library
|
||||
with 167 exported C ABI symbols (correct visibility). The XCFramework creation path is also
|
||||
verified — `xcodebuild -create-xcframework` produces a valid `VoiceCatCore.xcframework`
|
||||
with the `.a` + `voicecat.h` headers, ready for a Swift Package binary target.
|
||||
|
||||
**`apple-ios` and `apple-ios-sim` (iOS slices) are scaffolding** — not yet CI-validated.
|
||||
The vcpkg `arm64-ios` / `arm64-ios-sim` triplets for all 8 deps need verification.
|
||||
|
||||
Prerequisites: `VCPKG_ROOT` set, Xcode + iOS SDK installed, and Homebrew `autoconf-archive`
|
||||
(needed by vcpkg's libsodium port — `brew install autoconf-archive`).
|
||||
|
||||
```bash
|
||||
# Prerequisites: VCPKG_ROOT set, Xcode + iOS SDK installed
|
||||
@@ -51,6 +62,8 @@ xcodebuild -create-xcframework \
|
||||
-output build/VoiceCatCore.xcframework
|
||||
```
|
||||
|
||||
The XCFramework is then consumed by the Swift Package as a binary target. Actual
|
||||
The XCFramework is then consumed by the Swift Package as a binary target. The macOS-only
|
||||
XCFramework (single `apple-dev` slice) is verified to build now; the full 3-slice XCFramework
|
||||
(macOS + iOS device + iOS simulator) waits for the iOS presets to be validated. Actual
|
||||
`AVAudioSession` integration, `Info.plist` mic permission, ReplayKit extension, and SwiftUI
|
||||
UI work are tracked as follow-up tasks — the presets exist so the build entry point is ready.
|
||||
UI work are tracked as follow-up tasks.
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -172,6 +173,12 @@ void vc_client::run_io(std::string host, uint16_t port) {
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa{};
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
#else
|
||||
// POSIX/macOS: ignore SIGPIPE — a write to a closed socket returns EPIPE instead of
|
||||
// terminating the process. On macOS SIGPIPE is delivered by default (unlike Windows
|
||||
// where it doesn't exist); without this, a peer dropping mid-TLS-write kills us.
|
||||
// Process-global and idempotent (safe to call per run_io).
|
||||
std::signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
struct addrinfo hints{};
|
||||
|
||||
@@ -322,6 +322,17 @@ void TcpServerConn::close() {
|
||||
// In non-TLS mode, the Asio async chain will naturally stop when the socket closes.
|
||||
}
|
||||
|
||||
void TcpServerConn::wait_closed() {
|
||||
if (tls_thread_.joinable()) {
|
||||
if (std::this_thread::get_id() == tls_thread_.get_id()) {
|
||||
// Being called from our own TLS thread — detach to avoid self-join deadlock.
|
||||
tls_thread_.detach();
|
||||
} else {
|
||||
tls_thread_.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── TcpAcceptor ─────────────────────────────────────────────────────────────
|
||||
|
||||
namespace {
|
||||
@@ -359,6 +370,33 @@ void TcpAcceptor::stop() {
|
||||
stopped_ = true;
|
||||
std::error_code ignored;
|
||||
acceptor_.close(ignored);
|
||||
// Close all tracked connections so their TLS read threads exit. The socket close
|
||||
// happens while the io_context (and its reactor) is still alive, preventing the
|
||||
// null-reactor use-after-free that manifests on macOS kqueue.
|
||||
std::vector<std::shared_ptr<TcpServerConn>> to_close;
|
||||
{
|
||||
std::lock_guard lk(conns_mu_);
|
||||
to_close = conns_;
|
||||
}
|
||||
for (auto& conn : to_close) conn->close();
|
||||
}
|
||||
|
||||
void TcpAcceptor::shutdown() {
|
||||
stop();
|
||||
// Wait for every connection's TLS I/O thread to finish. close() (called by stop())
|
||||
// set closing_=true and closed the socket, so tls_read_loop is already exiting or has
|
||||
// exited; the join is brief. This must complete BEFORE the io_context is destroyed.
|
||||
std::vector<std::shared_ptr<TcpServerConn>> to_join;
|
||||
{
|
||||
std::lock_guard lk(conns_mu_);
|
||||
to_join = std::move(conns_);
|
||||
}
|
||||
for (auto& conn : to_join) {
|
||||
conn->wait_closed();
|
||||
}
|
||||
// to_join drops here — if a thread captured shared_from_this, the TcpServerConn stays
|
||||
// alive until that thread releases it; the destructor's close() is a no-op (already
|
||||
// closed) and tls_thread_ is already joined, so no reactor access occurs.
|
||||
}
|
||||
|
||||
void TcpAcceptor::do_accept() {
|
||||
@@ -371,7 +409,14 @@ void TcpAcceptor::do_accept() {
|
||||
}
|
||||
socket.set_option(asio::ip::tcp::no_delay(true));
|
||||
auto conn = factory_(std::move(socket));
|
||||
if (conn) conn->start();
|
||||
if (conn) {
|
||||
conn->start();
|
||||
// Track so shutdown() can close + join before the io_context is destroyed.
|
||||
{
|
||||
std::lock_guard lk(conns_mu_);
|
||||
conns_.push_back(conn);
|
||||
}
|
||||
}
|
||||
do_accept();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,6 +112,10 @@ class TcpServerConn : public std::enable_shared_from_this<TcpServerConn> {
|
||||
// Close the connection (safe from any thread).
|
||||
void close();
|
||||
|
||||
// Block until the TLS I/O thread (if any) has finished. Must be called after close().
|
||||
// Safe to call from any thread except the TLS I/O thread itself.
|
||||
void wait_closed();
|
||||
|
||||
bool connected() const { return connected_.load(std::memory_order_acquire); }
|
||||
|
||||
private:
|
||||
@@ -155,18 +159,28 @@ class TcpAcceptor {
|
||||
// Start accepting. Call once; re-arms itself automatically.
|
||||
void start();
|
||||
|
||||
// Stop accepting (does not close existing connections).
|
||||
// Stop accepting and close all tracked connections (safe while io_context is alive).
|
||||
void stop();
|
||||
|
||||
// Stop accepting, close all connections, and block until every connection's
|
||||
// I/O thread has finished. Call BEFORE the io_context is destroyed — the TLS read
|
||||
// threads do blocking I/O (not async on the io_context) and will touch the reactor
|
||||
// on socket close if the io_context is already gone (manifests as a null-kqueue-reactor
|
||||
// segfault on macOS; latent on Windows IOCP / Linux epoll where timing is more forgiving).
|
||||
void shutdown();
|
||||
|
||||
// Actual bound port (useful when bind_port=0 lets the OS pick).
|
||||
uint16_t local_port() const { return static_cast<uint16_t>(acceptor_.local_endpoint().port()); }
|
||||
|
||||
private:
|
||||
void do_accept();
|
||||
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
ConnFactory factory_;
|
||||
bool stopped_{false};
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
ConnFactory factory_;
|
||||
bool stopped_{false};
|
||||
|
||||
std::mutex conns_mu_;
|
||||
std::vector<std::shared_ptr<TcpServerConn>> conns_;
|
||||
};
|
||||
|
||||
// ── UDP media channel (M2) ───────────────────────────────────────────────────
|
||||
|
||||
@@ -17,7 +17,7 @@ and *how to drive the binaries by hand*.
|
||||
| `release` | `build/release` | vcpkg | Release | ON | ON | ON | no | all | Optimized build with the full test suite. Use to run tests against optimized code, profile, or catch optimizer-sensitive bugs. Symbols kept (not stripped) so stack traces and profiling remain useful. |
|
||||
| `server-release` | `build/server-release` | vcpkg | Release | ON | ON | OFF | **yes** | all | Production-shaped build for deployment. Optimized + stripped binaries (`-s`), no tests. This is what you'd ship/run — see [docs/deployment.md](deployment.md). |
|
||||
| `windows-client` | `build/windows-client` | vcpkg | Release | OFF | OFF | OFF | no | Windows | Produces a redistributable `voicecat.dll` for the C# WinForms client (M4). Static MinGW runtime — no `libgcc_s_seh-1.dll` etc. See [clients/windows/README.md](../clients/windows/README.md). |
|
||||
| `apple-dev` | `build/apple-dev` | vcpkg | Release | OFF | OFF | OFF | no | macOS | **Scaffolding** — static `libvoicecat.a` for the Swift Package / XCFramework (macOS slice). Not yet CI-validated; build on macOS to verify. See [clients/apple/README.md](../clients/apple/README.md). |
|
||||
| `apple-dev` | `build/apple-dev` | vcpkg | Release | OFF | OFF | OFF | no | macOS | Static `libvoicecat.a` for the Swift Package / XCFramework (macOS slice). Validated on macOS 26.5 / Apple Silicon — builds green, produces valid arm64 `.a` + XCFramework. See [clients/apple/README.md](../clients/apple/README.md). |
|
||||
| `apple-ios` | `build/apple-ios` | vcpkg | Release | OFF | OFF | OFF | no | macOS→iOS | **Scaffolding** — cross-compiled static `libvoicecat.a` for iOS device (`arm64-ios`). One XCFramework slice. Not yet CI-validated. |
|
||||
| `apple-ios-sim` | `build/apple-ios-sim` | vcpkg | Release | OFF | OFF | OFF | no | macOS→iOS sim | **Scaffolding** — cross-compiled static `libvoicecat.a` for iOS simulator (`arm64-ios-sim`). One XCFramework slice. Not yet CI-validated. |
|
||||
|
||||
@@ -207,8 +207,10 @@ symbol tables from the binaries, producing smaller executables suitable for dist
|
||||
## 6. Apple platform builds (scaffolding)
|
||||
|
||||
The `apple-dev`, `apple-ios`, and `apple-ios-sim` presets produce static `libvoicecat.a`
|
||||
slices for the Swift Package / XCFramework. They are **scaffolding** — not yet CI-validated
|
||||
and won't build on Windows. Build on macOS:
|
||||
slices for the Swift Package / XCFramework. The `apple-dev` preset (macOS slice) is
|
||||
**validated** — it builds green on macOS 26.5 / Apple Silicon and produces a valid arm64
|
||||
`.a` + XCFramework. The iOS cross-compile presets (`apple-ios`, `apple-ios-sim`) are still
|
||||
**scaffolding** — not yet CI-validated. Build on macOS:
|
||||
|
||||
```bash
|
||||
# macOS slice (arm64-osx on Apple Silicon, x64-osx on Intel)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "server.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <csignal>
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
@@ -135,13 +136,26 @@ int Server::run() {
|
||||
// Arm programmatic stop (for tests and embedders).
|
||||
{
|
||||
std::lock_guard lk(stop_mutex_);
|
||||
stop_fn_ = [&io] { io.stop(); };
|
||||
stop_fn_ = [&] {
|
||||
acceptor.stop();
|
||||
media_relay->stop();
|
||||
io.stop();
|
||||
};
|
||||
}
|
||||
|
||||
// Notify caller of the actual bound port (matters when bind_port==0).
|
||||
uint16_t bound = acceptor.local_port();
|
||||
if (cfg_.on_ready) cfg_.on_ready(bound);
|
||||
|
||||
#ifndef _WIN32
|
||||
// POSIX/macOS: ignore SIGPIPE — writing to a disconnected client's TCP socket returns
|
||||
// EPIPE instead of killing the server process. On macOS SIGPIPE is delivered by
|
||||
// default (unlike Windows where the signal doesn't exist). Must be set before any
|
||||
// async writes are posted. Process-global; safe alongside the asio::signal_set below
|
||||
// (which only catches SIGINT/SIGTERM).
|
||||
std::signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
// Graceful shutdown on SIGINT/SIGTERM
|
||||
asio::signal_set signals(io, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](std::error_code, int sig) {
|
||||
@@ -184,6 +198,13 @@ int Server::run() {
|
||||
|
||||
io.run();
|
||||
|
||||
// Close all connections and block until their TLS I/O threads have finished.
|
||||
// MUST happen before io (and its reactor) is destroyed — the TLS read threads do
|
||||
// blocking I/O (not async on io_context) and touch the reactor on socket close.
|
||||
// On macOS kqueue the reactor pointer is null'd immediately on io_context destruction,
|
||||
// causing a segfault; latent on Windows IOCP / Linux epoll where timing is more forgiving.
|
||||
acceptor.shutdown();
|
||||
|
||||
{
|
||||
std::lock_guard lk(stop_mutex_);
|
||||
stop_fn_ = nullptr;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
static int sock_error() { return WSAGetLastError(); }
|
||||
#else
|
||||
# include <arpa/inet.h>
|
||||
# include <netdb.h>
|
||||
# include <netinet/in.h>
|
||||
# include <sys/socket.h>
|
||||
# include <unistd.h>
|
||||
|
||||
Reference in New Issue
Block a user