Skip to content

Commit 61792e7

Browse files
authored
Merge feature/dynamic-firewalld-control to master (#6717)
`git show`: ``` commit 37a497e (HEAD -> private/bengangy/merge-firewalld, origin/private/bengangy/merge-firewalld) Merge: e2ba3d7 351f29c Author: Bengang Yuan <[email protected]> Date: Mon Oct 20 06:35:06 2025 +0000 Merge branch 'feature/dynamic-firewalld-control' into private/bengangy/merge-firewalld diff --cc ocaml/idl/datamodel_host.ml index be78d7f,1958e3813..29b5610 --- a/ocaml/idl/datamodel_host.ml +++ b/ocaml/idl/datamodel_host.ml @@@ -2696,7 -2657,7 +2704,8 @@@ let t ; set_ssh_enabled_timeout ; set_console_idle_timeout ; set_ssh_auto_mode + ; get_tracked_user_agents + ; update_firewalld_service_status ] ~contents: ([ diff --cc ocaml/xapi/message_forwarding.ml index 5741291,1082d0cd8..af2b1aa --- a/ocaml/xapi/message_forwarding.ml +++ b/ocaml/xapi/message_forwarding.ml @@@ -4117,12 -4115,9 +4117,16 @@@ functo let remote_fn = Client.Host.set_ssh_auto_mode ~self ~value in do_op_on ~local_fn ~__context ~host:self ~remote_fn + let get_tracked_user_agents ~__context ~self = + info "Host.get_tracked_user_agents: host = '%s'" + (host_uuid ~__context self) ; + let local_fn = Local.Host.get_tracked_user_agents ~self in + let remote_fn = Client.Host.get_tracked_user_agents ~self in + do_op_on ~local_fn ~__context ~host:self ~remote_fn ++ + let update_firewalld_service_status ~__context = + info "Host.update_firewalld_service_status" ; + Local.Host.update_firewalld_service_status ~__context end module Host_crashdump = struct diff --cc ocaml/xapi/xapi_globs.ml index 4244df7,f43ec6b8e..fcbc917 --- a/ocaml/xapi/xapi_globs.ml +++ b/ocaml/xapi/xapi_globs.ml @@@ -1339,10 -1330,14 +1343,18 @@@ let ssh_monitor_service = ref "xapi-ssh let ssh_auto_mode_default = ref true + type firewall_backend_type = Firewalld | Iptables + + (* Firewall backend to use. iptables in XS 8, firewalld in XS 9. *) + let firewall_backend = ref Iptables + + (* For firewalld, if dynamic control firewalld service. *) + let dynamic_control_firewalld_service = ref true + +let secure_boot_path = + ref + "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c" + (* Fingerprint of default patch key *) let citrix_patch_key = "NERDNTUzMDMwRUMwNDFFNDI4N0M4OEVCRUFEMzlGOTJEOEE5REUyNg==" diff --cc ocaml/xapi/xapi_host.ml index fab30bf,0358bcfed..90062c7 --- a/ocaml/xapi/xapi_host.ml +++ b/ocaml/xapi/xapi_host.ml @@@ -3340,6 -3356,62 +3363,66 @@@ let set_console_idle_timeout ~__contex Helpers.internal_error "Failed to set console timeout: %Ld: %s" value (Printexc.to_string e) +let get_tracked_user_agents ~__context ~self = + let _ : [`host] Ref.t = self in + Xapi_tracked_user_agents.get () ++ + let get_nbd_interfaces ~__context ~self = + let pifs = Db.Host.get_PIFs ~__context ~self in + let allowed_connected_networks = + (* We use Valid_ref_list to continue processing the list in case some + network refs are null or invalid *) + Valid_ref_list.filter_map + (fun pif -> + let network = Db.PIF.get_network ~__context ~self:pif in + let purpose = Db.Network.get_purpose ~__context ~self:network in + if List.mem `nbd purpose || List.mem `insecure_nbd purpose then + Some network + else + None + ) + pifs + in + let interfaces = + List.map + (fun network -> Db.Network.get_bridge ~__context ~self:network) + allowed_connected_networks + in + Xapi_stdext_std.Listext.List.setify interfaces + + let update_firewalld_service_status ~__context = + let open Firewall in + let enable_firewalld_service service = + try Firewalld.update_firewall_status service Enabled with _ -> () + in + match !Xapi_globs.firewall_backend with + | Firewalld -> + let self = Helpers.get_localhost ~__context in + let is_enabled = function + | Dlm -> + Xapi_clustering.Daemon.is_enabled () + | Http -> + not (Db.Host.get_https_only ~__context ~self) + | Nbd -> + get_nbd_interfaces ~__context ~self <> [] + | Ssh -> + Db.Host.get_ssh_enabled ~__context ~self + | Vxlan -> + List.exists + (fun tunnel -> + Db.PIF.get_currently_attached ~__context + ~self:(Db.Tunnel.get_access_PIF ~__context ~self:tunnel) + ) + (Db.Tunnel.get_all ~__context) + | Xenha -> + (* Only xha needs to enable firewalld service. Other HA cluster + stacks don't need. *) + bool_of_string (Localdb.get Constants.ha_armed) + && Localdb.get Constants.ha_cluster_stack + = !Xapi_globs.cluster_stack_default + in + List.iter + (fun s -> if is_enabled s then enable_firewalld_service s) + all_service_types + | Iptables -> + debug "No need to update firewalld service status when using iptables" diff --cc ocaml/xapi/xapi_host.mli index 4adb72d,b3aa2d467..316ee9f --- a/ocaml/xapi/xapi_host.mli +++ b/ocaml/xapi/xapi_host.mli @@@ -592,5 -590,15 +592,18 @@@ val schedule_disable_ssh_job val set_ssh_auto_mode : __context:Context.t -> self:API.ref_host -> value:bool -> unit +val get_tracked_user_agents : + __context:Context.t -> self:API.ref_host -> (string * string) list ++ + val get_nbd_interfaces : __context:Context.t -> self:API.ref_host -> string list + + val update_firewalld_service_status : __context:Context.t -> unit + (* Update the status of all the firewalld services to match the state of the + corresponding services. + This function is used in 2 scenarios: + 1. When xapi starts, to ensure that all the firewalld services are in the + correct state. + 2. When the firewalld restarts, all firewalld services are reset to the + default status. This function should be called to update these firewalld + services to the correct status. Xapi will expose an xe command line for + this scenario. *) ```
2 parents 915a4ca + 37a497e commit 61792e7

21 files changed

+502
-58
lines changed

ocaml/idl/datamodel.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10704,6 +10704,7 @@ let emergency_calls =
1070410704
; (Datamodel_host.t, Datamodel_host.emergency_disable_tls_verification)
1070510705
; (Datamodel_host.t, Datamodel_host.emergency_reenable_tls_verification)
1070610706
; (Datamodel_host.t, Datamodel_host.emergency_clear_mandatory_guidance)
10707+
; (Datamodel_host.t, Datamodel_host.update_firewalld_service_status)
1070710708
]
1070810709

1070910710
(** Whitelist of calls that will not get forwarded from the slave to master via the unix domain socket *)

ocaml/idl/datamodel_host.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2518,6 +2518,14 @@ let set_ssh_auto_mode =
25182518
]
25192519
~allowed_roles:_R_POOL_ADMIN ()
25202520

2521+
let update_firewalld_service_status =
2522+
call ~name:"update_firewalld_service_status" ~flags:[`Session] ~lifecycle:[]
2523+
~pool_internal:true ~hide_from_docs:true
2524+
~doc:
2525+
"Update firewalld services based on the corresponding xapi services \
2526+
status."
2527+
~allowed_roles:_R_POOL_OP ()
2528+
25212529
let latest_synced_updates_applied_state =
25222530
Enum
25232531
( "latest_synced_updates_applied_state"
@@ -2697,6 +2705,7 @@ let t =
26972705
; set_console_idle_timeout
26982706
; set_ssh_auto_mode
26992707
; get_tracked_user_agents
2708+
; update_firewalld_service_status
27002709
]
27012710
~contents:
27022711
([

ocaml/idl/datamodel_lifecycle.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ let prototyped_of_message = function
225225
Some "22.26.0"
226226
| "VTPM", "create" ->
227227
Some "22.26.0"
228+
| "host", "update_firewalld_service_status" ->
229+
Some "25.30.0-next"
228230
| "host", "set_ssh_auto_mode" ->
229231
Some "25.27.0"
230232
| "host", "set_console_idle_timeout" ->

ocaml/xapi-cli-server/cli_frontend.ml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2922,6 +2922,19 @@ let rec cmdtable_data : (string * cmd_spec) list =
29222922
; flags= [Neverforward]
29232923
}
29242924
)
2925+
; ( "host-update-firewalld-service-status"
2926+
, {
2927+
reqd= []
2928+
; optn= []
2929+
; help=
2930+
"Update firewalld services status based the corresponding xapi \
2931+
services status."
2932+
; implementation=
2933+
No_fd_local_session
2934+
Cli_operations.host_update_firewalld_service_status
2935+
; flags= [Neverforward]
2936+
}
2937+
)
29252938
; ( "diagnostic-compact"
29262939
, {
29272940
reqd= []

ocaml/xapi-cli-server/cli_operations.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5451,6 +5451,9 @@ let host_retrieve_wlb_evacuate_recommendations printer rpc session_id params =
54515451
let host_shutdown_agent _printer rpc session_id _params =
54525452
ignore (Client.Host.shutdown_agent ~rpc ~session_id)
54535453

5454+
let host_update_firewalld_service_status _printer rpc session_id _params =
5455+
ignore (Client.Host.update_firewalld_service_status ~rpc ~session_id)
5456+
54545457
let vdi_import fd _printer rpc session_id params =
54555458
let filename = List.assoc "filename" params in
54565459
let vdi =

ocaml/xapi/dbsync_slave.ml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,19 @@ let refresh_localhost_info ~__context info =
135135
) else
136136
Db.Host.remove_from_other_config ~__context ~self:host
137137
~key:Xapi_globs.host_no_local_storage ;
138-
let script_output =
139-
Helpers.call_script !Xapi_globs.firewall_port_config_script ["check"; "80"]
138+
let status =
139+
match Db.Host.get_https_only ~__context ~self:host with
140+
| true ->
141+
Firewall.Disabled
142+
| false ->
143+
Firewall.Enabled
140144
in
141-
try
142-
let network_state = Scanf.sscanf script_output "Port 80 open: %B" Fun.id in
143-
Db.Host.set_https_only ~__context ~self:host ~value:network_state
144-
with _ ->
145-
Helpers.internal_error
146-
"unexpected output from /etc/xapi.d/plugins/firewall-port: %s"
147-
script_output
145+
let module Fw =
146+
( val Firewall.firewall_provider !Xapi_globs.firewall_backend
147+
: Firewall.FIREWALL
148+
)
149+
in
150+
Fw.update_firewall_status Firewall.Http status
148151
(*************** update database tools ******************)
149152

150153
(** Record host memory properties in database *)

ocaml/xapi/dune

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@
262262
System_domains
263263
Xapi_psr
264264
Xapi_services
265-
Xapi_udhcpd))))
265+
Xapi_udhcpd)
266+
((pps ppx_deriving.enum)
267+
Firewall))))
266268

267269
(library
268270
(name xapi_internal_server_only)

ocaml/xapi/firewall.ml

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
(*
2+
* Copyright (c) Cloud Software Group, Inc.
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published
6+
* by the Free Software Foundation; version 2.1 only. with the special
7+
* exception on linking described in file LICENSE.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*)
14+
15+
module D = Debug.Make (struct let name = __MODULE__ end)
16+
17+
open D
18+
19+
type service_type = Dlm | Nbd | Ssh | Vxlan | Http | Xenha [@@deriving enum]
20+
21+
type status = Enabled | Disabled
22+
23+
type protocol = TCP | UDP
24+
25+
(* 1. For firewalld, xapi is always responsible for dynamically controlling
26+
firewalld services.
27+
2. For legacy iptables compatibility:
28+
- Certain iptables ports (such as 4789 for VXLAN) are managed dynamically.
29+
- Other ports (such as 22 for SSH) do not require dynamic control, as they
30+
are already enabled when the host boots up.
31+
- The `dynamic_control_iptables_port` parameter is used to decide if the
32+
ports should be controlled dynamically when iptables is selected.
33+
*)
34+
type service_info = {
35+
name: string
36+
; port: int
37+
; protocol: protocol
38+
; dynamic_control_iptables_port: bool
39+
}
40+
41+
let all_service_types =
42+
let length = max_service_type - min_service_type + 1 in
43+
List.init length (fun i ->
44+
service_type_of_enum (min_service_type + i) |> Option.get
45+
)
46+
47+
let status_to_string = function Enabled -> "enabled" | Disabled -> "disabled"
48+
49+
let protocol_to_string = function TCP -> "tcp" | UDP -> "udp"
50+
51+
let service_type_to_service_info = function
52+
| Dlm ->
53+
{
54+
name= "dlm"
55+
; port= !Xapi_globs.xapi_clusterd_port
56+
; protocol= TCP
57+
; dynamic_control_iptables_port= true
58+
}
59+
| Nbd ->
60+
{
61+
name= "nbd"
62+
; port= 10809
63+
; protocol= TCP
64+
; dynamic_control_iptables_port= true
65+
}
66+
| Ssh ->
67+
{
68+
name= "ssh"
69+
; port= 22
70+
; protocol= TCP
71+
; dynamic_control_iptables_port= false
72+
}
73+
| Vxlan ->
74+
{
75+
name= "vxlan"
76+
; port= 4789
77+
; protocol= UDP
78+
; dynamic_control_iptables_port= true
79+
}
80+
| Http ->
81+
{
82+
name= "xapi-insecure"
83+
; port= Constants.http_port
84+
; protocol= TCP
85+
; dynamic_control_iptables_port= true
86+
}
87+
| Xenha ->
88+
{
89+
name= "xenha"
90+
; port= Xapi_globs.xha_udp_port
91+
; protocol= UDP
92+
; dynamic_control_iptables_port= false
93+
}
94+
95+
module type FIREWALL = sig
96+
val update_firewall_status :
97+
?interfaces:string list -> service_type -> status -> unit
98+
end
99+
100+
module Firewalld : FIREWALL = struct
101+
let update_firewall_status ?(interfaces = []) service status =
102+
if !Xapi_globs.dynamic_control_firewalld_service then (
103+
let service_option =
104+
match status with
105+
| Enabled ->
106+
"--add-service"
107+
| Disabled ->
108+
"--remove-service"
109+
in
110+
let service_info = service_type_to_service_info service in
111+
( match interfaces with
112+
| _ :: _ when service = Nbd ->
113+
let interface_list = String.concat ", " interfaces in
114+
debug
115+
"%s: Enable NBD service as the following interfaces are used for \
116+
NBD: [%s]"
117+
__FUNCTION__ interface_list
118+
| _ ->
119+
()
120+
) ;
121+
try
122+
Helpers.call_script !Xapi_globs.firewall_cmd
123+
[service_option; service_info.name]
124+
|> ignore
125+
with e ->
126+
error
127+
"%s: Failed to update firewall service (%s) to (%s) with error: %s"
128+
__FUNCTION__ service_info.name (status_to_string status)
129+
(Printexc.to_string e) ;
130+
Helpers.internal_error "Failed to update firewall service (%s)"
131+
service_info.name
132+
)
133+
end
134+
135+
module Iptables : FIREWALL = struct
136+
let update_firewall_status ?(interfaces = []) service status =
137+
let service_info = service_type_to_service_info service in
138+
if service_info.dynamic_control_iptables_port then (
139+
try
140+
match service with
141+
| Dlm | Ssh | Vxlan | Http | Xenha ->
142+
let op =
143+
match status with Enabled -> "open" | Disabled -> "close"
144+
in
145+
Helpers.call_script
146+
!Xapi_globs.firewall_port_config_script
147+
[
148+
op
149+
; string_of_int service_info.port
150+
; protocol_to_string service_info.protocol
151+
]
152+
|> ignore
153+
| Nbd ->
154+
(* For legacy iptables, NBD port needs to be precisely controlled on
155+
each interface *)
156+
let args = "set" :: interfaces in
157+
Helpers.call_script !Xapi_globs.nbd_firewall_config_script args
158+
|> ignore
159+
with e ->
160+
error
161+
"%s: Failed to update firewall service (%s) to (%s) with error: %s"
162+
__FUNCTION__ service_info.name (status_to_string status)
163+
(Printexc.to_string e) ;
164+
Helpers.internal_error "Failed to update firewall service (%s)"
165+
service_info.name
166+
)
167+
end
168+
169+
let firewall_provider (backend : Xapi_globs.firewall_backend_type) :
170+
(module FIREWALL) =
171+
match backend with
172+
| Firewalld ->
173+
(module Firewalld)
174+
| Iptables ->
175+
(module Iptables)

ocaml/xapi/firewall.mli

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
(*
2+
* Copyright (c) Cloud Software Group, Inc.
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published
6+
* by the Free Software Foundation; version 2.1 only. with the special
7+
* exception on linking described in file LICENSE.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*)
14+
15+
type service_type = Dlm | Nbd | Ssh | Vxlan | Http | Xenha
16+
17+
type status = Enabled | Disabled
18+
19+
val all_service_types : service_type list
20+
21+
module type FIREWALL = sig
22+
val update_firewall_status :
23+
?interfaces:string list -> service_type -> status -> unit
24+
(** [update_firewall_status] updates the firewalld service status based on the
25+
status of the corresponding service.
26+
27+
[interfaces] is a list of bridge names of the Network objects whose
28+
purpose is `nbd` or `insecure_nbd`. [interfaces] is only used to controll
29+
the NBD iptables port dynamically, to specify which interfaces are
30+
permitted for NBD connections.
31+
*)
32+
end
33+
34+
module Firewalld : FIREWALL
35+
36+
module Iptables : FIREWALL
37+
38+
val firewall_provider : Xapi_globs.firewall_backend_type -> (module FIREWALL)

ocaml/xapi/message_forwarding.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4123,6 +4123,10 @@ functor
41234123
let local_fn = Local.Host.get_tracked_user_agents ~self in
41244124
let remote_fn = Client.Host.get_tracked_user_agents ~self in
41254125
do_op_on ~local_fn ~__context ~host:self ~remote_fn
4126+
4127+
let update_firewalld_service_status ~__context =
4128+
info "Host.update_firewalld_service_status" ;
4129+
Local.Host.update_firewalld_service_status ~__context
41264130
end
41274131

41284132
module Host_crashdump = struct

0 commit comments

Comments
 (0)