5 changed files with 338 additions and 29 deletions
@ -0,0 +1,203 @@ |
|||
;; NetworkRemoteCommands.cake: Use Network.cake, Protocol.cake, and RemoteCommands.cake together |
|||
;; to make a distributed command system. |
|||
;; TODO: Add heartbeats and acks to make more reliable. |
|||
(set-cakelisp-option cakelisp-src-dir "Dependencies/cakelisp/src") |
|||
(add-cakelisp-search-directory "Dependencies/cakelisp/runtime") |
|||
(import |
|||
;; Cakelisp |
|||
"CHelpers.cake" |
|||
;; GameLib |
|||
"Cryptography.cake" "Network.cake" "RemoteCommands.cake" "Protocol.cake" "DynamicArray.cake" |
|||
"KeyManagement.cake") |
|||
|
|||
(c-import |
|||
"<errno.h>" "<sys/select.h>" |
|||
&with-decls "<stdbool.h>" "<sodium.h>") |
|||
|
|||
(def-type-alias-global client-permissions (unsigned int)) |
|||
(var-global client-permission-receive-commands client-permissions (bit-shift-<< 1 1)) |
|||
(var-global client-permission-send-commands-to-server client-permissions (bit-shift-<< 1 2)) |
|||
|
|||
(defstruct network-machine-data |
|||
name (addr (const char)) |
|||
;; public-key-hash (unsigned int) |
|||
permissions client-permissions |
|||
public-key (array crypto_box_PUBLICKEYBYTES (unsigned char))) |
|||
|
|||
(forward-declare (struct remote-command-queue) |
|||
(struct network-server-state)) |
|||
|
|||
(defstruct network-client-connection |
|||
id (unsigned int) |
|||
permissions client-permissions |
|||
name (addr (const char)) |
|||
public-key (addr (unsigned char)) |
|||
connection socket-type |
|||
send-to-socket bool |
|||
commands-to-send (addr remote-command-queue)) |
|||
|
|||
(defun-local accept-new-connection |
|||
(state (addr network-server-state) |
|||
trusted-machines (addr network-machine-data) |
|||
num-trusted-machines int |
|||
;; dynarray |
|||
active-connections (addr (addr network-client-connection)) |
|||
&return bool) |
|||
(netlog "Accepting new connection\n") |
|||
|
|||
;; Receive new connection |
|||
(var client-address (struct sockaddr_in) (array 0)) |
|||
(var client-address-length int (sizeof client-address)) |
|||
(var client-socket socket-type |
|||
(comptime-cond |
|||
('Windows ;; Grr... |
|||
(accept (path state > server-socket) |
|||
(type-cast (addr client-address) (addr (struct sockaddr))) |
|||
(addr client-address-length))) |
|||
(true |
|||
(accept (path state > server-socket) |
|||
(type-cast (addr client-address) (addr (struct sockaddr))) |
|||
(type-cast (addr client-address-length) (addr (unsigned int))))))) |
|||
(when (< client-socket 0) |
|||
;; Server socket is non-blocking: we'll connect next time |
|||
(when (or (= errno EAGAIN) |
|||
(= errno EINPROGRESS)) |
|||
(return 0)) |
|||
|
|||
(perror "Error accepting connection") |
|||
(return 1)) |
|||
|
|||
(var this-machine-id (unsigned int) (receive-cake-protocol-introduction client-socket)) |
|||
(unless this-machine-id |
|||
(netlog "Error making initial handshake.\n") |
|||
(close client-socket) |
|||
(return 1)) |
|||
(var client-machine (addr network-machine-data) null) |
|||
(each-in-range num-trusted-machines i |
|||
(var machine (addr network-machine-data) (addr (at i trusted-machines))) |
|||
(var machine-id (unsigned int) |
|||
(machine-id-from-public-key (path machine > public-key))) |
|||
(when (= this-machine-id machine-id) |
|||
(set client-machine machine) |
|||
(break))) |
|||
(unless client-machine |
|||
(netlog "A machine with ID %d attempted to connect, but it was not in the trusted machines list. |
|||
The connection will not proceed.\n" this-machine-id) |
|||
(close client-socket) |
|||
(return 1)) |
|||
|
|||
(var new-client network-client-connection (array 0)) |
|||
(set-fields new-client |
|||
id this-machine-id |
|||
permissions (path client-machine > permissions) |
|||
name (path client-machine > name) |
|||
public-key (path client-machine > public-key) |
|||
connection client-socket) |
|||
(dynarray-push (deref active-connections) new-client) |
|||
|
|||
(netlog "Successfully accepted connection with %s\n" |
|||
(field new-client name)) |
|||
(return 0)) |
|||
|
|||
(defun network-server-wait-or-receive-remote-commands |
|||
(state (addr network-server-state) |
|||
trusted-machines (addr network-machine-data) |
|||
num-trusted-machines int |
|||
;; dynarray |
|||
active-connections (addr (addr network-client-connection)) |
|||
&return int) |
|||
(var client-keys-data encryption-keys (array 0)) |
|||
(memcpy (field client-keys-data my-secret-key) g-my-secret-key |
|||
(sizeof (field client-keys-data my-secret-key))) |
|||
;; Don't copy the client key yet because we don't know it! |
|||
(var client-keys (addr encryption-keys) (addr client-keys-data)) |
|||
|
|||
;; (netlog "Waiting for connection...\n") |
|||
(var read-connections fd_set) |
|||
(var write-connections fd_set) |
|||
(var exception-connections fd_set) |
|||
(scope |
|||
(var highest-file-descriptor int 0) |
|||
(FD_ZERO (addr read-connections)) |
|||
(FD_ZERO (addr write-connections)) |
|||
(FD_ZERO (addr exception-connections)) |
|||
(FD_SET (path state > server-socket) (addr read-connections)) |
|||
(when (> (path state > server-socket) highest-file-descriptor) |
|||
(set highest-file-descriptor (path state > server-socket))) |
|||
(each-item-addr-in-dynarray (deref active-connections) i connection (addr network-client-connection) |
|||
(when (bit-and (path connection > permissions) client-permission-send-commands-to-server) |
|||
(FD_SET (path connection > connection) (addr read-connections))) |
|||
(when (path connection > send-to-socket) |
|||
(FD_SET (path connection > connection) (addr write-connections))) |
|||
(FD_SET (path connection > connection) (addr exception-connections)) |
|||
(when (> (path connection > connection) highest-file-descriptor) |
|||
(set highest-file-descriptor (path connection > connection)))) |
|||
;; Select with no timeout, because we have nothing else to do |
|||
(var select-result int |
|||
(select (+ 1 highest-file-descriptor) |
|||
(addr read-connections) (addr write-connections) (addr exception-connections) |
|||
null)) |
|||
(cond |
|||
((= 0 select-result) |
|||
(return 0)) |
|||
((= -1 select-result) |
|||
(perror "In select, waiting for connections") |
|||
(return 1)))) |
|||
|
|||
(when (FD_ISSET (path state > server-socket) (addr read-connections)) |
|||
(accept-new-connection state trusted-machines num-trusted-machines active-connections)) |
|||
|
|||
(var incoming-commands (addr remote-command-queue) (create-remote-command-queue)) |
|||
(defer (free-remote-command-queue incoming-commands)) |
|||
|
|||
;; Iterate in reverse for safe erasing |
|||
(each-in-dynarray-reverse (deref active-connections) i |
|||
(var connection (addr network-client-connection) (addr (at i (deref active-connections)))) |
|||
(var this-machine-permissions client-permissions (path connection > permissions)) |
|||
(var client-socket socket-type (path connection > connection)) |
|||
|
|||
(when (FD_ISSET client-socket (addr exception-connections)) |
|||
(netlog "Exception encountered; lost connection to %s\n" (path connection > name)) |
|||
(dynarray-delete (deref active-connections) i) |
|||
(continue)) |
|||
|
|||
;; Set their public key |
|||
;; We re-use client-keys to not have to copy the master secret key all the time |
|||
(memcpy (path client-keys > their-public-key) |
|||
(path connection > public-key) |
|||
(sizeof (path client-keys > their-public-key))) |
|||
|
|||
(clear-remote-command-queue incoming-commands) |
|||
|
|||
;; Receive |
|||
(when (FD_ISSET client-socket (addr read-connections)) |
|||
(unless (bit-and this-machine-permissions client-permission-send-commands-to-server) |
|||
(netlog "Machine %s tried to send commands to the server, but it lacks permission to do so. |
|||
It will be disconnected.\n" |
|||
(path connection > name)) |
|||
(close client-socket) |
|||
(dynarray-delete (deref active-connections) i) |
|||
(continue)) |
|||
(unless (receive-command-queue incoming-commands client-socket client-keys) |
|||
(netlog "Lost connection to %s\n" (path connection > name)) |
|||
(dynarray-delete (deref active-connections) i) |
|||
(continue)) |
|||
(execute-requested-remote-commands incoming-commands null)) |
|||
|
|||
;; Send |
|||
(when (and (path connection > send-to-socket) |
|||
(FD_ISSET client-socket (addr write-connections))) |
|||
(unless (bit-and this-machine-permissions client-permission-receive-commands) |
|||
(netlog "Server is trying to send commands to machine %s, but the machine lacks permission |
|||
to receive commands. It will be disconnected.\n" |
|||
(path connection > name)) |
|||
(close client-socket) |
|||
(dynarray-delete (deref active-connections) i) |
|||
(continue)) |
|||
(netlog "Server is sending commands to %s\n" (path connection > name)) |
|||
(set (path connection > send-to-socket) false) |
|||
(unless (send-command-queue (path connection > commands-to-send) client-socket client-keys) |
|||
(netlog "Lost connection to %s\n" (path connection > name)) |
|||
(dynarray-delete (deref active-connections) i) |
|||
(continue)))) |
|||
(return 0)) |
Loading…
Reference in new issue