|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace RESTAPI\Models; |
| 4 | + |
| 5 | +require_once 'RESTAPI/autoloader.inc'; |
| 6 | + |
| 7 | +use RESTAPI\Core\Model; |
| 8 | +use RESTAPI\Fields\IntegerField; |
| 9 | +use RESTAPI\Fields\NestedModelField; |
| 10 | +use RESTAPI\Fields\StringField; |
| 11 | + |
| 12 | +/** |
| 13 | + * Defines a Model that represents the live runtime status of a WireGuard tunnel. Data is sourced from wg_get_status() |
| 14 | + * and is read-only. Peer status information is embedded via a nested WireGuardTunnelStatusPeer model. |
| 15 | + */ |
| 16 | +class WireGuardTunnelStatus extends Model { |
| 17 | + public StringField $name; |
| 18 | + public StringField $status; |
| 19 | + public StringField $public_key; |
| 20 | + public StringField $listen_port; |
| 21 | + public IntegerField $mtu; |
| 22 | + public IntegerField $transfer_rx; |
| 23 | + public IntegerField $transfer_tx; |
| 24 | + public IntegerField $inpkts; |
| 25 | + public IntegerField $outpkts; |
| 26 | + public StringField $descr; |
| 27 | + public NestedModelField $peers; |
| 28 | + |
| 29 | + public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], mixed ...$options) { |
| 30 | + # Model attributes |
| 31 | + $this->internal_callable = 'get_wireguard_status'; |
| 32 | + $this->many = true; |
| 33 | + $this->verbose_name = 'WireGuard Tunnel Status'; |
| 34 | + $this->packages = ['pfSense-pkg-WireGuard']; |
| 35 | + $this->package_includes = [ |
| 36 | + 'wireguard/includes/wg_service.inc', |
| 37 | + 'wireguard/includes/wg.inc', |
| 38 | + 'wireguard/includes/wg_globals.inc', |
| 39 | + 'wireguard/includes/wg_api.inc', |
| 40 | + ]; |
| 41 | + |
| 42 | + # Fields |
| 43 | + $this->name = new StringField( |
| 44 | + default: null, |
| 45 | + allow_null: true, |
| 46 | + read_only: true, |
| 47 | + verbose_name: 'Name', |
| 48 | + help_text: 'The WireGuard tunnel interface name (e.g. wg0).', |
| 49 | + ); |
| 50 | + $this->status = new StringField( |
| 51 | + default: null, |
| 52 | + choices: ['up', 'down'], |
| 53 | + allow_null: true, |
| 54 | + read_only: true, |
| 55 | + verbose_name: 'Status', |
| 56 | + help_text: 'The current operational status of the tunnel interface.', |
| 57 | + ); |
| 58 | + $this->public_key = new StringField( |
| 59 | + default: null, |
| 60 | + allow_null: true, |
| 61 | + read_only: true, |
| 62 | + verbose_name: 'Public Key', |
| 63 | + help_text: 'The public key of this tunnel.', |
| 64 | + ); |
| 65 | + $this->listen_port = new StringField( |
| 66 | + default: null, |
| 67 | + allow_null: true, |
| 68 | + read_only: true, |
| 69 | + verbose_name: 'Listen Port', |
| 70 | + help_text: 'The UDP port this tunnel is listening on.', |
| 71 | + ); |
| 72 | + $this->mtu = new IntegerField( |
| 73 | + default: null, |
| 74 | + allow_null: true, |
| 75 | + read_only: true, |
| 76 | + verbose_name: 'MTU', |
| 77 | + help_text: 'The MTU of the tunnel interface.', |
| 78 | + ); |
| 79 | + $this->transfer_rx = new IntegerField( |
| 80 | + default: null, |
| 81 | + allow_null: true, |
| 82 | + read_only: true, |
| 83 | + verbose_name: 'Transfer RX', |
| 84 | + help_text: 'The total number of bytes received on the tunnel interface.', |
| 85 | + ); |
| 86 | + $this->transfer_tx = new IntegerField( |
| 87 | + default: null, |
| 88 | + allow_null: true, |
| 89 | + read_only: true, |
| 90 | + verbose_name: 'Transfer TX', |
| 91 | + help_text: 'The total number of bytes transmitted on the tunnel interface.', |
| 92 | + ); |
| 93 | + $this->inpkts = new IntegerField( |
| 94 | + default: null, |
| 95 | + allow_null: true, |
| 96 | + read_only: true, |
| 97 | + verbose_name: 'Inbound Packets', |
| 98 | + help_text: 'The total number of inbound packets observed on the tunnel interface.', |
| 99 | + ); |
| 100 | + $this->outpkts = new IntegerField( |
| 101 | + default: null, |
| 102 | + allow_null: true, |
| 103 | + read_only: true, |
| 104 | + verbose_name: 'Outbound Packets', |
| 105 | + help_text: 'The total number of outbound packets observed on the tunnel interface.', |
| 106 | + ); |
| 107 | + $this->descr = new StringField( |
| 108 | + default: '', |
| 109 | + allow_empty: true, |
| 110 | + read_only: true, |
| 111 | + verbose_name: 'Description', |
| 112 | + help_text: 'The description of this tunnel as stored in the pfSense configuration.', |
| 113 | + ); |
| 114 | + $this->peers = new NestedModelField( |
| 115 | + model_class: 'WireGuardPeerStatus', |
| 116 | + default: [], |
| 117 | + allow_empty: true, |
| 118 | + read_only: true, |
| 119 | + verbose_name: 'Peers', |
| 120 | + help_text: 'The live status of each peer associated with this WireGuard tunnel.', |
| 121 | + ); |
| 122 | + |
| 123 | + parent::__construct($id, $parent_id, $data, ...$options); |
| 124 | + } |
| 125 | + |
| 126 | + /** |
| 127 | + * Retrieves the live WireGuard status from the `wg` CLI tool and maps it into the flat array format |
| 128 | + * expected by the Model framework's internal_callable mechanism. |
| 129 | + * @return array An indexed array of tunnel status records, each containing peer status as a sub-array. |
| 130 | + */ |
| 131 | + protected function get_wireguard_status(): array { |
| 132 | + $tunnels = []; |
| 133 | + |
| 134 | + $this->check_packages(); |
| 135 | + |
| 136 | + foreach (wg_get_status() as $device_name => $device) { |
| 137 | + # Build the indexed peer list from the public-key-keyed peers array |
| 138 | + $peers = []; |
| 139 | + |
| 140 | + foreach ($device['peers'] as $peer) { |
| 141 | + $peers[] = [ |
| 142 | + 'tunnel_device' => $device_name, |
| 143 | + 'public_key' => $peer['public_key'] ?? null, |
| 144 | + 'preshared_key' => $peer['preshared_key'] ?? null, |
| 145 | + 'endpoint' => $peer['endpoint'] ?? null, |
| 146 | + 'allowed_ips' => $peer['allowed_ips'] ?? null, |
| 147 | + 'latest_handshake' => isset($peer['latest_handshake']) ? (int) $peer['latest_handshake'] : null, |
| 148 | + 'transfer_rx' => isset($peer['transfer_rx']) ? (int) $peer['transfer_rx'] : null, |
| 149 | + 'transfer_tx' => isset($peer['transfer_tx']) ? (int) $peer['transfer_tx'] : null, |
| 150 | + 'persistent_keepalive' => $peer['persistent_keepalive'] ?? null, |
| 151 | + 'descr' => $peer['config']['descr'] ?? '', |
| 152 | + ]; |
| 153 | + } |
| 154 | + |
| 155 | + $tunnels[] = [ |
| 156 | + 'name' => $device_name, |
| 157 | + 'status' => $device['status'] ?? null, |
| 158 | + 'public_key' => $device['public_key'] ?? null, |
| 159 | + 'listen_port' => $device['listen_port'] ?? null, |
| 160 | + 'mtu' => isset($device['mtu']) ? (int) $device['mtu'] : null, |
| 161 | + 'transfer_rx' => isset($device['transfer_rx']) ? (int) $device['transfer_rx'] : null, |
| 162 | + 'transfer_tx' => isset($device['transfer_tx']) ? (int) $device['transfer_tx'] : null, |
| 163 | + 'inpkts' => isset($device['inpkts']) ? (int) $device['inpkts'] : null, |
| 164 | + 'outpkts' => isset($device['outpkts']) ? (int) $device['outpkts'] : null, |
| 165 | + 'descr' => $device['config']['descr'] ?? '', |
| 166 | + 'peers' => $peers, |
| 167 | + ]; |
| 168 | + } |
| 169 | + |
| 170 | + return $tunnels; |
| 171 | + } |
| 172 | +} |
0 commit comments