mirror of
https://github.com/mirage/qubes-mirage-firewall.git
synced 2025-04-26 01:49:07 -04:00
104 lines
4.5 KiB
OCaml
104 lines
4.5 KiB
OCaml
(* Copyright (C) 2015, Thomas Leonard <thomas.leonard@unikernel.com>
|
|
See the README file for details. *)
|
|
|
|
(** Enforce firewall rules from QubesDB. *)
|
|
|
|
open Packet
|
|
module Q = Pf_qubes.Parse_qubes
|
|
|
|
let src = Logs.Src.create "rules" ~doc:"Firewall rules"
|
|
module Log = (val Logs.src_log src : Logs.LOG)
|
|
|
|
let dns_port = 53
|
|
|
|
(* OCaml normally warns if you don't match all fields, but that's OK here. *)
|
|
[@@@ocaml.warning "-9"]
|
|
|
|
(* we want to replace this list with a structure including rules from QubesDB.
|
|
we need:
|
|
1) code for reading the rules (we have some for noticing new clients: dao.ml)
|
|
2) code for parsing the rules (use ocaml-pf, reduced to the Qubes ruleset)
|
|
3) code for putting the rules in a structure readable here (???)
|
|
- also the rules are per-client, so the current structure doesn't really accommodate them
|
|
- there is a structure tracking each client in Client_eth, which is using a map from IP addresses to
|
|
Fw_utils.client_link. let's try putting the rules in this client_link structure?
|
|
- initially we can set them up with a list, and then look for faster/better/clearer structures later
|
|
4) code for applying the rules to incoming traffic (below, already in this file)
|
|
*)
|
|
|
|
(* Does the packet match our rules? *)
|
|
let classify_client_packet (packet : ([`Client of Fw_utils.client_link], _) Packet.t) : Packet.action =
|
|
let matches_port dstports (port : int) = match dstports with
|
|
| None -> true
|
|
| Some (Q.Range_inclusive (min, max)) -> min <= port && port <= max
|
|
in
|
|
let matches_proto rule packet = match rule.Pf_qubes.Parse_qubes.proto with
|
|
| None -> true
|
|
| Some rule_proto -> match rule_proto, packet.transport_header with
|
|
| `tcp, `TCP header -> matches_port rule.Q.dstports header.dst_port
|
|
| `udp, `UDP header -> matches_port rule.Q.dstports header.dst_port
|
|
| `icmp, `ICMP header ->
|
|
begin
|
|
match rule.icmp_type with
|
|
| None -> true
|
|
| Some rule_icmp_type ->
|
|
Icmpv4_wire.ty_to_int header.ty == rule_icmp_type
|
|
end
|
|
| _, _ -> false
|
|
in
|
|
let matches_dest rule packet = match rule.Pf_qubes.Parse_qubes.dst with
|
|
| `any -> true
|
|
| `hosts subnet ->
|
|
Ipaddr.Prefix.mem (V4 packet.ipv4_header.Ipv4_packet.dst) subnet
|
|
in
|
|
let (`Client client_link) = packet.src in
|
|
let rules = snd client_link#get_rules in
|
|
Log.debug (fun f -> f "checking %d rules for a match" (List.length rules));
|
|
List.find_opt (fun rule ->
|
|
if not (matches_proto rule packet) then begin
|
|
Log.debug (fun f -> f "rule %d is not a match - proto" rule.Q.number);
|
|
false
|
|
end else if not (matches_dest rule packet) then begin
|
|
Log.debug (fun f -> f "rule %d is not a match - dest" rule.Q.number);
|
|
false
|
|
end else begin
|
|
Log.debug (fun f -> f "rule %d is a match" rule.Q.number);
|
|
true
|
|
end) rules |> function
|
|
| None -> `Drop "No matching rule; assuming default drop"
|
|
| Some {Q.action = Accept; number; _} ->
|
|
Log.debug (fun f -> f "allowing packet matching rule %d" number);
|
|
`Accept
|
|
| Some {Q.action = Drop; number; _} ->
|
|
`Drop (Printf.sprintf "rule %d explicitly drops this packet" number)
|
|
|
|
(** This function decides what to do with a packet from a client VM.
|
|
|
|
It takes as input an argument [info] (of type [Packet.info]) describing the
|
|
packet, and returns an action (of type [Packet.action]) to perform.
|
|
|
|
See packet.ml for the definitions of [info] and [action].
|
|
|
|
Note: If the packet matched an existing NAT rule then this isn't called. *)
|
|
let from_client (packet : ([`Client of Fw_utils.client_link], _) Packet.t) : Packet.action =
|
|
match packet with
|
|
| { dst = (`External _ | `NetVM) } -> begin
|
|
(* see whether this traffic is allowed *)
|
|
match classify_client_packet packet with
|
|
| `Accept -> `NAT
|
|
| `Drop s -> `Drop s
|
|
end
|
|
| { dst = `Client_gateway; transport_header = `UDP header; _ } ->
|
|
(* TODO: this is where we should implement specialtarget dns rules? *)
|
|
if header.dst_port = dns_port
|
|
then `NAT_to (`NetVM, dns_port)
|
|
else `Drop "packet addressed to client gateway"
|
|
| { dst = (`Client_gateway | `Firewall_uplink) } -> `Drop "packet addressed to firewall itself"
|
|
| { dst = `Client _ } -> classify_client_packet packet
|
|
|
|
(** Decide what to do with a packet received from the outside world.
|
|
Note: If the packet matched an existing NAT rule then this isn't called. *)
|
|
let from_netvm (packet : ([`NetVM | `External of _], _) Packet.t) : Packet.action =
|
|
match packet with
|
|
| _ -> `Drop "drop by default"
|