2017-03-02 09:52:55 -05:00
|
|
|
(* Copyright (C) 2015, Thomas Leonard <thomas.leonard@unikernel.com>
|
|
|
|
See the README file for details. *)
|
|
|
|
|
|
|
|
let src = Logs.Src.create "my-nat" ~doc:"NAT shim"
|
|
|
|
module Log = (val Logs.src_log src : Logs.LOG)
|
|
|
|
|
|
|
|
type action = [
|
2017-03-10 11:09:36 -05:00
|
|
|
| `NAT
|
|
|
|
| `Redirect of Mirage_nat.endpoint
|
2017-03-02 09:52:55 -05:00
|
|
|
]
|
|
|
|
|
2017-10-15 09:35:03 -04:00
|
|
|
module Nat = Mirage_nat_lru
|
2017-03-02 09:52:55 -05:00
|
|
|
|
2022-10-08 04:50:29 -04:00
|
|
|
module S =
|
|
|
|
Set.Make(struct type t = int let compare (a : int) (b : int) = compare a b end)
|
|
|
|
|
2017-03-10 11:09:36 -05:00
|
|
|
type t = {
|
|
|
|
table : Nat.t;
|
2022-10-08 04:50:29 -04:00
|
|
|
mutable udp_dns : S.t;
|
|
|
|
last_resort_port : int
|
2017-03-10 11:09:36 -05:00
|
|
|
}
|
2017-03-02 09:52:55 -05:00
|
|
|
|
2022-10-08 04:50:29 -04:00
|
|
|
let pick_port () =
|
|
|
|
1024 + Random.int (0xffff - 1024)
|
|
|
|
|
2020-01-11 09:36:02 -05:00
|
|
|
let create ~max_entries =
|
2017-03-15 04:56:24 -04:00
|
|
|
let tcp_size = 7 * max_entries / 8 in
|
|
|
|
let udp_size = max_entries - tcp_size in
|
2022-10-07 12:49:03 -04:00
|
|
|
let table = Nat.empty ~tcp_size ~udp_size ~icmp_size:100 in
|
2022-10-08 04:50:29 -04:00
|
|
|
let last_resort_port = pick_port () in
|
|
|
|
{ table ; udp_dns = S.empty ; last_resort_port }
|
2022-10-07 14:54:49 -04:00
|
|
|
|
|
|
|
let pick_free_port t proto =
|
2022-10-08 04:50:29 -04:00
|
|
|
let rec go retries =
|
|
|
|
if retries = 0 then
|
|
|
|
None
|
|
|
|
else
|
|
|
|
let p = 1024 + Random.int (0xffff - 1024) in
|
|
|
|
match proto with
|
|
|
|
| `Udp when S.mem p t.udp_dns || p = t.last_resort_port ->
|
|
|
|
go (retries - 1)
|
|
|
|
| _ -> Some p
|
2022-10-07 14:54:49 -04:00
|
|
|
in
|
2022-10-08 04:50:29 -04:00
|
|
|
go 10
|
2022-10-07 14:54:49 -04:00
|
|
|
|
|
|
|
let free_udp_port t ~src ~dst ~dst_port =
|
|
|
|
let rec go () =
|
2022-10-08 04:50:29 -04:00
|
|
|
let src_port =
|
|
|
|
Option.value ~default:t.last_resort_port (pick_free_port t `Udp)
|
|
|
|
in
|
2022-10-07 14:54:49 -04:00
|
|
|
if Nat.is_port_free t.table `Udp ~src ~dst ~src_port ~dst_port then begin
|
2022-10-08 04:50:29 -04:00
|
|
|
let remove =
|
|
|
|
if src_port <> t.last_resort_port then begin
|
|
|
|
t.udp_dns <- S.add src_port t.udp_dns;
|
|
|
|
(fun () -> t.udp_dns <- S.remove src_port t.udp_dns)
|
|
|
|
end else Fun.id
|
|
|
|
in
|
|
|
|
src_port, remove
|
2022-10-07 14:54:49 -04:00
|
|
|
end else
|
|
|
|
go ()
|
|
|
|
in
|
|
|
|
go ()
|
2017-03-10 11:09:36 -05:00
|
|
|
|
2022-10-08 04:50:29 -04:00
|
|
|
let dns_port t port = S.mem port t.udp_dns || port = t.last_resort_port
|
|
|
|
|
2017-03-10 11:09:36 -05:00
|
|
|
let translate t packet =
|
2022-10-07 12:49:03 -04:00
|
|
|
match Nat.translate t.table packet with
|
2017-03-10 11:09:36 -05:00
|
|
|
| Error (`Untranslated | `TTL_exceeded as e) ->
|
|
|
|
Log.debug (fun f -> f "Failed to NAT %a: %a"
|
|
|
|
Nat_packet.pp packet
|
|
|
|
Mirage_nat.pp_error e
|
|
|
|
);
|
|
|
|
None
|
2017-03-07 05:02:54 -05:00
|
|
|
| Ok packet -> Some packet
|
2017-03-02 09:52:55 -05:00
|
|
|
|
2022-10-07 14:54:49 -04:00
|
|
|
let remove_connections t ip =
|
|
|
|
ignore (Nat.remove_connections t.table ip)
|
2020-04-29 09:58:01 -04:00
|
|
|
|
2022-10-07 14:54:49 -04:00
|
|
|
let add_nat_rule_and_translate t ~xl_host action packet =
|
|
|
|
let proto = match packet with
|
|
|
|
| `IPv4 (_, `TCP _) -> `Tcp
|
|
|
|
| `IPv4 (_, `UDP _) -> `Udp
|
|
|
|
| `IPv4 (_, `ICMP _) -> `Icmp
|
2017-03-02 09:52:55 -05:00
|
|
|
in
|
2022-10-07 14:54:49 -04:00
|
|
|
match Nat.add t.table packet xl_host (fun () -> pick_free_port t proto) action with
|
|
|
|
| Error `Overlap -> Error "Too many retries"
|
|
|
|
| Error `Cannot_NAT -> Error "Cannot NAT this packet"
|
|
|
|
| Ok () ->
|
|
|
|
Log.debug (fun f -> f "Updated NAT table: %a" Nat.pp_summary t.table);
|
|
|
|
Option.to_result ~none:"No NAT entry, even after adding one!"
|
|
|
|
(translate t packet)
|