diff --git a/cg-net.el b/cg-net.el index fd7861d..4fcf79d 100644 --- a/cg-net.el +++ b/cg-net.el @@ -60,6 +60,23 @@ "Default TCP port used to host or join a game." :type 'integer :group 'cg-net) +(defcustom cg-net-host-address "127.0.0.1" + "Address the host's listening socket binds when hosting a game. +The default, \"127.0.0.1\", accepts connections only from this +machine; remote players reach it through a tunnel they were +deliberately given (for example ssh port forwarding), which also +encrypts the traffic in transit. + +Setting this to \"0.0.0.0\" listens on every network interface, which +means anyone able to reach this machine's port can take a seat: there +is no password and no encryption on the wire. That can be a +reasonable choice on a trusted LAN, but it is a choice -- make it +deliberately." + :type '(choice (const :tag "This machine only (recommended)" "127.0.0.1") + (const :tag "Every interface (anyone who can reach you)" "0.0.0.0") + (string :tag "A specific interface address")) + :group 'cg-net) + (defvar cg-net-state-functions nil "Abnormal hook run on a client after the game state is updated. Each function is called with the client's game object.") @@ -157,11 +174,12 @@ properties stripped (`cg-net--scrub') before HANDLER sees them." (and cg-net--host (process-live-p (cg-net-host-server cg-net--host)))) (defun cg-net-host-start (game &optional port) - "Begin hosting GAME on PORT (default `cg-net-port'). Return the server process." + "Begin hosting GAME on PORT (default `cg-net-port'). Return the server process. +The socket binds `cg-net-host-address' -- by default, this machine only." (let* ((port (or port cg-net-port)) (server (make-network-process :name "cg-host" :server t :service port - :host "0.0.0.0" :family 'ipv4 :coding 'utf-8 + :host cg-net-host-address :family 'ipv4 :coding 'utf-8 :log #'cg-net--host-accept))) (setq cg-net--host (cg-net--host-make :server server :game game)) server)) diff --git a/test/card-games-tests.el b/test/card-games-tests.el index 0579310..f77f312 100644 --- a/test/card-games-tests.el +++ b/test/card-games-tests.el @@ -104,6 +104,25 @@ host must reach the client bare." (cg-net-disconnect) (cg-net-host-stop))))) +(ert-deftest cgt-net-host-loopback-default () + "Hosting binds to this machine only unless deliberately widened. +Finding 1 of the 2026-07-29 review: the default of +`cg-net-host-address' is loopback, and the listening socket really +binds it -- wider exposure is a setting the user turns on, not a +silent default." + (should (equal "127.0.0.1" (default-value 'cg-net-host-address))) + (condition-case _ + (delete-process + (make-network-process :name "cgt-probe4" :server t :service 0 + :host "127.0.0.1" :family 'ipv4)) + (error (ert-skip "TCP not available"))) + (let* ((hgame (make-instance 'cgt-net-game :env (list :counter 0))) + (srv (cg-net-host-start hgame 0))) + (unwind-protect + (should (equal [127 0 0 1] + (substring (process-contact srv :local) 0 4))) + (cg-net-host-stop)))) + ;;;; Gaps (ert-deftest cgt-gaps-deal ()