include_guard "port_forwarding" template port_forwarding { alias("_arg0") forwardings_file; alias("_arg1") template_forward; # Map which holds the set of current port forwardings. # Enties are: {protocol, port_start, port_end, dest_addr}:"" value([]) map; # Blocker which is initially down and is toggled down-up # whenever the forwarding change. blocker() update_blocker; # Process manager, each forwarding has a port_forwarding__instance process. # The process identifiers are the same as the keys in the map. process_manager() mgr; # Spawn a process for dealing with storage of port forwardings on disk. spawn("port_forwarding__stored", {}); } template port_forwarding__instance { alias("_caller") pf; alias("_arg0") protocol; alias("_arg1") port_start; alias("_arg2") port_end; alias("_arg3") dest_addr; log("notice", "adding port forwarding ", protocol, ":", port_start, ":", port_end, " to ", dest_addr); log_r("notice", "removed port forwarding ", protocol, ":", port_start, ":", port_end, " to ", dest_addr); # Do the forwarding. call_with_caller_target(pf.template_forward, {protocol, port_start, port_end, dest_addr}, "pf._caller"); } template port_forwarding_add { alias(_arg0) pf; alias("_arg1") protocol; alias("_arg2") port_start; alias("_arg3") port_end; alias("_arg4") dest_addr; var("false") succeeded; var("") error_text; var("true") not_finished; backtrack_point() finished_point; If (not_finished) { # Check for conflicts with existing forwardings. Foreach (pf.map.keys As entry) { value(entry) entry; entry->get("0") e_protocol; entry->get("1") e_port_start; entry->get("2") e_port_end; val_different(protocol, e_protocol) different_protocol; num_lesser(port_end, e_port_start) before; num_greater(port_start, e_port_end) after; or(different_protocol, before, after) no_conflict; not(no_conflict) conflict; If (conflict) { error_text->set("Port forwarding conflicts with an existing forwarding."); not_finished->set("false"); finished_point->go(); }; }; # Build entry key. var({protocol, port_start, port_end, dest_addr}) key; # Insert to map and toggle blocker. pf.map->insert(key, ""); pf.update_blocker->downup(); # Start process. pf.mgr->start(key, "port_forwarding__instance", {protocol, port_start, port_end, dest_addr}); succeeded->set("true"); not_finished->set("false"); finished_point->go(); }; } template port_forwarding_remove { alias(_arg0) pf; alias("_arg1") protocol; alias("_arg2") port_start; alias("_arg3") port_end; alias("_arg4") dest_addr; var("false") succeeded; var("") error_text; var("true") not_finished; backtrack_point() finished_point; If (not_finished) { # Build entry key. var({protocol, port_start, port_end, dest_addr}) key; # Check if the forwarding exists. pf.map->try_get(key) entry; not(entry.exists) does_not_exist; If (does_not_exist) { error_text->set("Port forwarding does not exist."); not_finished->set("false"); finished_point->go(); }; # Stop process. pf.mgr->stop(key); # Remove from map and toggle blocker. pf.map->remove(key); pf.update_blocker->downup(); succeeded->set("true"); not_finished->set("false"); finished_point->go(); }; } template port_forwarding__stored { alias("_caller") pf; # Create file if it doesn't exist. file_stat(pf.forwardings_file) stat; If (stat.succeeded) { print(); } Else { file_write(pf.forwardings_file, "{}\n"); }; # Read port forwardings from file. file_read(pf.forwardings_file) data; from_string(data) forwardings; # Add them. Foreach (forwardings As fwd) { value(fwd) fwd; fwd->get("0") protocol; fwd->get("1") port_start; fwd->get("2") port_end; fwd->get("3") dest_addr; call("port_forwarding_add", {"_caller.pf", protocol, port_start, port_end, dest_addr}); }; # Write forwardings to file on exit. imperative("", {}, "port_forwarding__write", {}, "6000"); # Also write forwardings whenever they are changed. pf.update_blocker->use(); call("port_forwarding__write", {}); } template port_forwarding__write { alias("_caller.pf") pf; # Convert forwardings to string. to_string(pf.map.keys) data; concat(data, "\n") data; # Build name of temporary file. concat(pf.forwardings_file, ".new") temp_file; # Write temporary file. file_write(temp_file, data); # Move to live file. runonce({"/bin/mv", temp_file, pf.forwardings_file}); }