Skip to content

Commit a6aa363

Browse files
authored
Merge Feature/limit vnc console sessions to master (xapi-project#6820)
Merge feature VNC console session limitation This feature adds two new pool-level controls for VNC console connections. 1. Connection Limiting New field: pool.limit_console_sessions (boolean, default: false) When enabled, only one user can connect to each VM/host console at a time. Additional connection attempts are rejected with an error message showing who is currently connected. How it works: - Tracks active connections per VM/host using unique connection IDs - Rejects new connections when limit is enabled and one exists - Automatically cleans up on disconnect - WebSocket connections are blocked when limiting is enabled 2. Idle Timeout New field: pool.vm_console_idle_timeout (seconds, default: 0 = no timeout) Automatically disconnects idle console sessions after the specified time. Separate host.console_idle_timeout field exists for control domain consoles. How it works: - Monitors keyboard and mouse activity via RFB protocol parsing - Resets timer on user input (KeyEvent, PointerEvent) - Disconnects when idle timeout exceeded - Default value of 0 preserves existing behavior (no timeout)
2 parents 788b8f3 + c5009f1 commit a6aa363

18 files changed

Lines changed: 1322 additions & 16 deletions

ocaml/idl/datamodel_common.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open Datamodel_roles
1010
to leave a gap for potential hotfixes needing to increment the schema version.*)
1111
let schema_major_vsn = 5
1212

13-
let schema_minor_vsn = 792
13+
let schema_minor_vsn = 793
1414

1515
(* Historical schema versions just in case this is useful later *)
1616
let rio_schema_major_vsn = 5

ocaml/idl/datamodel_lifecycle.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ let prototyped_of_field = function
147147
Some "23.18.0"
148148
| "VM", "actions__after_softreboot" ->
149149
Some "23.1.0"
150+
| "pool", "vm_console_idle_timeout" ->
151+
Some "26.0.0-next"
152+
| "pool", "limit_console_sessions" ->
153+
Some "26.0.0-next"
150154
| "pool", "ha_reboot_vm_on_internal_shutdown" ->
151155
Some "25.16.0"
152156
| "pool", "license_server" ->

ocaml/idl/datamodel_pool.ml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,6 +2249,19 @@ let t =
22492249
"Indicates whether an HA-protected VM that is shut down from \
22502250
inside (not through the API) should be automatically rebooted \
22512251
when HA is enabled"
2252+
; field ~writer_roles:_R_POOL_OP ~qualifier:RW ~lifecycle:[] ~ty:Bool
2253+
~default_value:(Some (VBool false)) "limit_console_sessions"
2254+
"When true, only one console connection per VM/host in the pool is \
2255+
accepted. Otherwise every connection for a VM/host's console is \
2256+
accepted. Note: when true, connection attempts via websocket will \
2257+
be rejected."
2258+
; field ~writer_roles:_R_POOL_OP ~qualifier:RW ~lifecycle:[] ~ty:Int
2259+
~default_value:(Some (VInt 0L)) "vm_console_idle_timeout"
2260+
"The maximum time (in seconds) that a VM's console can be idle \
2261+
before it is automatically disconnected. The default value 0 \
2262+
means never timeout. This setting applies only to VM consoles; \
2263+
for host consoles, use the separate parameter \
2264+
'host.console_idle_timeout'."
22522265
]
22532266
)
22542267
()

ocaml/idl/schematest.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex
33
(* BEWARE: if this changes, check that schema has been bumped accordingly in
44
ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *)
55

6-
let last_known_schema_hash = "d8cb04ccddfd91ca3f0f9074dcf7c219"
6+
let last_known_schema_hash = "a01358e3ff5f42d5aee162e995d2ec05"
77

88
let current_schema_hash : string =
99
let open Datamodel_types in

ocaml/libs/http-lib/http_svr.ml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ let response_error_html ?(version = "1.1") s code message hdrs body =
149149
D.debug "Response %s" (Http.Response.to_string res) ;
150150
Unixext.really_write_string s (Http.Response.to_wire_string res)
151151

152+
let response_custom_error ?req s error_code reason body =
153+
let version = Option.map get_return_version req in
154+
response_error_html ?version s error_code reason [] body
155+
152156
let response_unauthorised ?req label s =
153157
let version = Option.map get_return_version req in
154158
let body =
@@ -331,7 +335,7 @@ module Server = struct
331335
x.handlers []
332336
end
333337

334-
let escape uri =
338+
let escape str =
335339
(* from xapi-stdext-std xstringext *)
336340
let escaped ~rules string =
337341
let aux h t =
@@ -353,7 +357,7 @@ let escape uri =
353357
; ('"', """)
354358
; ('&', "&")
355359
]
356-
uri
360+
str
357361

358362
exception Generic_error of string
359363

ocaml/libs/http-lib/http_svr.mli

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ end
5050

5151
exception Generic_error of string
5252

53+
val escape : string -> string
54+
(** [escape str] escapes HTML/XML special characters in [str] for safe inclusion in HTML/XML content. *)
55+
5356
type socket
5457

5558
val bind : ?listen_backlog:int -> Unix.sockaddr -> string -> socket
@@ -97,6 +100,9 @@ val response_unauthorised :
97100

98101
val response_forbidden : ?req:Http.Request.t -> Unix.file_descr -> unit
99102

103+
val response_custom_error :
104+
?req:Http.Request.t -> Unix.file_descr -> string -> string -> string -> unit
105+
100106
val response_badrequest : ?req:Http.Request.t -> Unix.file_descr -> unit
101107

102108
val response_internal_error :

ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@ module CBuf = struct
338338
in
339339
let read = Unix.read fd x.buffer next len in
340340
if read = 0 then x.r_closed <- true ;
341-
x.len <- x.len + read
341+
x.len <- x.len + read ;
342+
(x.buffer, read, next)
342343
end
343344

344345
exception Process_still_alive
@@ -381,11 +382,21 @@ let with_polly f =
381382
let finally () = Polly.close polly in
382383
Xapi_stdext_pervasives.Pervasiveext.finally (fun () -> f polly) finally
383384

384-
let proxy (a : Unix.file_descr) (b : Unix.file_descr) =
385+
exception Close_proxy
386+
387+
let proxy ?should_close ?(poll_timeout = -1) (a : Unix.file_descr)
388+
(b : Unix.file_descr) =
385389
let size = 64 * 1024 in
386390
(* [a'] is read from [a] and will be written to [b] *)
387391
(* [b'] is read from [b] and will be written to [a] *)
388392
let a' = CBuf.empty size and b' = CBuf.empty size in
393+
394+
let close_proxy () =
395+
Unix.shutdown a Unix.SHUTDOWN_ALL ;
396+
Unix.shutdown b Unix.SHUTDOWN_ALL ;
397+
raise Close_proxy
398+
in
399+
389400
Unix.set_nonblock a ;
390401
Unix.set_nonblock b ;
391402
with_polly @@ fun polly ->
@@ -413,13 +424,22 @@ let proxy (a : Unix.file_descr) (b : Unix.file_descr) =
413424
Polly.upd polly a a_events ;
414425
if Polly.Events.(b_events <> empty) then
415426
Polly.upd polly b b_events ;
416-
Polly.wait_fold polly 4 (-1) () (fun _polly fd events () ->
427+
Polly.wait_fold polly 4 poll_timeout (Bytes.empty, 0, 0)
428+
(fun _polly fd events acc ->
417429
(* Do the writing before the reading *)
418430
if Polly.Events.(test out events) then
419431
if a = fd then CBuf.write b' a else CBuf.write a' b ;
420432
if Polly.Events.(test inp events) then
421-
if a = fd then CBuf.read a' a else CBuf.read b' b
422-
) ;
433+
if a = fd then (
434+
ignore (CBuf.read a' a) ;
435+
acc
436+
) else
437+
CBuf.read b' b
438+
else
439+
acc
440+
)
441+
|> fun data ->
442+
Option.iter (fun cb -> if cb data then close_proxy ()) should_close ;
423443
(* If there's nothing else to read or write then signal the other end *)
424444
List.iter
425445
(fun (buf, fd) ->

ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,12 @@ exception Process_still_alive
126126

127127
val kill_and_wait : ?signal:int -> ?timeout:float -> int -> unit
128128

129-
val proxy : Unix.file_descr -> Unix.file_descr -> unit
129+
val proxy :
130+
?should_close:(bytes * int * int -> bool)
131+
-> ?poll_timeout:int
132+
-> Unix.file_descr
133+
-> Unix.file_descr
134+
-> unit
130135

131136
val really_read : Unix.file_descr -> bytes -> int -> int -> unit
132137

ocaml/tests/common/test_common.ml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,8 @@ let make_pool ~__context ~master ?(name_label = "") ?(name_description = "")
314314
?(last_update_sync = API.Date.epoch) ?(update_sync_frequency = `daily)
315315
?(update_sync_day = 0L) ?(update_sync_enabled = false)
316316
?(recommendations = []) ?(license_server = [])
317-
?(ha_reboot_vm_on_internal_shutdown = true) () =
317+
?(ha_reboot_vm_on_internal_shutdown = true)
318+
?(limit_console_sessions = false) ?(vm_console_idle_timeout = 0L) () =
318319
let pool_ref = Ref.make () in
319320
Db.Pool.create ~__context ~ref:pool_ref ~uuid:(make_uuid ()) ~name_label
320321
~name_description ~master ~default_SR ~suspend_image_SR ~crash_dump_SR
@@ -335,7 +336,8 @@ let make_pool ~__context ~master ?(name_label = "") ?(name_description = "")
335336
~ext_auth_cache_enabled:false ~ext_auth_cache_size:50L
336337
~ext_auth_cache_expiry:300L ~update_sync_frequency ~update_sync_day
337338
~update_sync_enabled ~recommendations ~license_server
338-
~ha_reboot_vm_on_internal_shutdown ;
339+
~ha_reboot_vm_on_internal_shutdown ~limit_console_sessions
340+
~vm_console_idle_timeout ;
339341
pool_ref
340342

341343
let default_sm_features =

ocaml/tests/suite_alcotest.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,6 @@ let () =
7070
@ Test_session.tests
7171
@ Test_xapi_cmd_result.tests
7272
@ Test_extauth_plugin_ADwinbind.tests
73+
@ Test_rfb_client_msgtype_parser.tests
7374
@ Test_tracked_user_agents.tests
7475
)

0 commit comments

Comments
 (0)