From 860e6dabb120289d21ec53da7170bb19c3aa3dae Mon Sep 17 00:00:00 2001 From: Riccardo Bicelli Date: Thu, 12 Dec 2019 16:51:13 +0100 Subject: [PATCH] First Release --- README.md | 49 +- README.md.backup | 47 + pfsense_zbx.php | 329 ++ template_pfsense_active.xml | 5907 +++++++++++++++++++++++++++++++++++ 4 files changed, 6330 insertions(+), 2 deletions(-) create mode 100644 README.md.backup create mode 100644 pfsense_zbx.php create mode 100644 template_pfsense_active.xml diff --git a/README.md b/README.md index 8aa5f8b..cd9e2f8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,47 @@ -# pfsense-zabbix-template -Zabbix Template for pfSense mostly based on pfsense php functions library +# pfSense Zabbix template + +This is a pfSense active template for zabbix, based on [Keenton Zabbix Template](https://github.com/keentonsas/zabbix-template-pfsense) for freeBSD part and a php script using pfSense functions library for monitoring specific data. + +Tested with pfSense 2.4 and Zabbix 4.0 + +## What it does + + - pfSense Version/Update Available + - Gateway Monitoring (Gateway Status/RTT with discovery) + - OpenVPN Server Monitoring (Server Status/Tunnel Status with discovery) + - CARP Monitoring (Global CARP State) + - Basic service monitoring (Service Status with discovery) + + +## Configuration + +First copy the file pfsense_zbx.php to your pfsense box (e.g. to /root/scripts). +Then install package "Zabbix Agent 4" on your pfSense Box + + +In Advanced Features-> User Parameters + +```bash +AllowRoot=1 +UserParameter=pfsense.states.max,grep "limit states" /tmp/rules.limits | cut -f4 -d ' ' +UserParameter=pfsense.states.current,grep "current entries" /tmp/pfctl_si_out | tr -s ' ' | cut -f4 -d ' ' +UserParameter=pfsense.mbuf.current,netstat -m | grep "mbuf clusters" | cut -f1 -d ' ' | cut -d '/' -f1 +UserParameter=pfsense.mbuf.cache,netstat -m | grep "mbuf clusters" | cut -f1 -d ' ' | cut -d '/' -f2 +UserParameter=pfsense.mbuf.max,netstat -m | grep "mbuf clusters" | cut -f1 -d ' ' | cut -d '/' -f4 +UserParameter=pfsense.discovery[*],/usr/local/bin/php /root/scripts/pfsense_zbx.php discovery $1 +UserParameter=pfsense.value[*],/usr/local/bin/php /root/scripts/pfsense_zbx.php $1 $2 $3 +``` + +__Please note that **AllowRoot=1** option is required in order to execute correctly OpenVPN checks and others._ + +Then import xml template in Zabbix and add your pfSense hosts. + +If you are running a redundant CARP setup you can adjust the macro {#EXPECTED_CARP_STATUS} to a value representing what is CARP expected status on monitored box. + +Possible values are: + + - 0: Disabled + - 1: Master + - 2: Backup + +This is useful when monitoring services which could stay stopped on CARP Backup Member. \ No newline at end of file diff --git a/README.md.backup b/README.md.backup new file mode 100644 index 0000000..218e1c0 --- /dev/null +++ b/README.md.backup @@ -0,0 +1,47 @@ +# pfSense Zabbix template + +This is a pfSense active template for zabbix, based on [Keenton Zabbix Template](https://github.com/keentonsas/zabbix-template-pfsense) for freeBSD part and a php script using pfSense functions library for monitoring specific data. + +Tested with pfSense 2.4 and Zabbix 4.0 + +## What it does + + - pfSense Version/Update Available + - Gateway Monitoring (Gateway Status/RTT with discovery) + - OpenVPN Server Monitoring (Server Status/Tunnel Status with discovery) + - CARP Monitoring (Global CARP State) + - Basic service monitoring (Service Status with discovery) + + +## Configuration + +First copy the file pfsense_zbx.php to your pfsense box (e.g. to /root/scripts). +Then install package "Zabbix Agent 4" on your pfSense Box + + +In Advanced Features-> User Parameters + +```bash +AllowRoot=1 +UserParameter=pfsense.states.max,grep "limit states" /tmp/rules.limits | cut -f4 -d ' ' +UserParameter=pfsense.states.current,grep "current entries" /tmp/pfctl_si_out | tr -s ' ' | cut -f4 -d ' ' +UserParameter=pfsense.mbuf.current,netstat -m | grep "mbuf clusters" | cut -f1 -d ' ' | cut -d '/' -f1 +UserParameter=pfsense.mbuf.cache,netstat -m | grep "mbuf clusters" | cut -f1 -d ' ' | cut -d '/' -f2 +UserParameter=pfsense.mbuf.max,netstat -m | grep "mbuf clusters" | cut -f1 -d ' ' | cut -d '/' -f4 +UserParameter=pfsense.discovery[*],/usr/local/bin/php /root/scripts/pfsense_zbx.php discovery $1 +UserParameter=pfsense.value[*],/usr/local/bin/php /root/scripts/pfsense_zbx.php $1 $2 $3 +``` + +__Please note that **AllowRoot=1** option is required in order to execute correctly OpenVPN checks and others._ + +Then import xml template in Zabbix and add your pfSense hosts. + +If you are running a redundant CARP setup you can adjust the macro {#EXPECTED_CARP_STATUS} to a value representing what is CARP expected status on monitored box. + +Possible values are: + + - 0: Disabled + - 1: Master + - 2: Backup + +This is useful when monitoring services which could stay stopped on CARP Backup Member. \ No newline at end of file diff --git a/pfsense_zbx.php b/pfsense_zbx.php new file mode 100644 index 0000000..357a0ff --- /dev/null +++ b/pfsense_zbx.php @@ -0,0 +1,329 @@ + +This program is licensed under Apache 2.0 License +*/ + +require_once('globals.inc'); +require_once('functions.inc'); +require_once('config.inc'); +require_once('util.inc'); + +require_once('interfaces.inc'); + +//For OpenVPN Discovery +require_once('openvpn.inc'); + +//For Service Discovery +require_once("service-utils.inc"); + +//For System +require_once('pkg-utils.inc'); + + +function pfz_get_if_name($hwif){ + + $ifdescrs = get_configured_interface_with_descr(true); + foreach ($ifdescrs as $ifdescr => $ifname){ + $ifinfo = get_interface_info($ifdescr); + if($ifinfo["hwif"]==$hwif){ + echo $ifname; + return true; + } + } + +} + + +//OpenVPN Server Discovery +function pfz_openvpn_get_all_servers(){ + $servers = openvpn_get_active_servers(); + $sk_servers = openvpn_get_active_servers("p2p"); + $servers = array_merge($servers,$sk_servers); + return ($servers); +} + + +function pfz_openvpn_serverdiscovery() { + $servers = pfz_openvpn_get_all_servers(); + + $json_string = '{"data":['; + + foreach ($servers as $server){ + $name = trim(preg_replace('/\w{3}(\d)?\:\d{4,5}/i', '', $server['name'])); + $json_string .= '{"{#SERVER}":"' . $server['vpnid'] . '"'; + $json_string .= ',"{#NAME}":"' . $name . '"'; + $json_string .= '},'; + } + + $json_string = rtrim($json_string,","); + $json_string .= "]}"; + + echo $json_string; +} + + +function pfz_openvpn_servervalue($server_id,$valuekey){ + $servers = pfz_openvpn_get_all_servers(); + foreach($servers as $server) { + if($server['vpnid']==$server_id) + $value = $server[$valuekey]; + } + if ($value=="") $value="none"; + echo $value; +} + + +//OpenVPN Client Discovery +function pfz_openvpn_clientdiscovery() { + $clients = openvpn_get_active_clients(); + + $json_string = '{"data":['; + + foreach ($clients as $client){ + $name = trim(preg_replace('/\w{3}(\d)?\:\d{4,5}/i', '', $client['name'])); + $json_string .= '{"{#CLIENT}":"' . $client['vpnid'] . '"'; + $json_string .= ',"{#NAME}":"' . $name . '"'; + $json_string .= '},'; + } + + $json_string = rtrim($json_string,","); + $json_string .= "]}"; + + echo $json_string; +} + + +function pfz_openvpn_clientvalue($client_id, $valuekey){ + $clients = openvpn_get_active_clients(); + foreach($clients as $client) { + if($client['vpnid']==$client_id) + $value = $client[$valuekey]; + } + if ($value=="") $value="none"; + echo $value; +} + + +//Services Discovery +function pfz_services_discovery(){ + $services = get_services(); + + $json_string = '{"data":['; + + foreach ($services as $service){ + if (!empty($service['name'])) { + + $status = get_service_status($service); + if ($status="") $status = 0; + + if (empty($service['id'])) $id=""; + else $id = "." . $service["id"]; + + $json_string .= '{"{#SERVICE}":"' . $service['name'] . $id . '"'; + $json_string .= ',"{#DESCRIPTION}":"' . $service['description'] . '"'; + $json_string .= '},'; + } + } + $json_string = rtrim($json_string,","); + $json_string .= "]}"; + + echo $json_string; + +} + + +function pfz_service_value($name,$value){ + $services = get_services(); + + //List of service which are stopped on CARP Slave. + //For now this is the best way i found for filtering out the triggers + //Waiting for a way in Zabbix to use Global Regexp in triggers with items discovery + $stopped_on_carp_slave = array("haproxy","openvpn."); + + foreach ($services as $service){ + if (empty($service['id'])) { + $namecfr=$service["name"]; + $carpcfr=$service["name"]; + } else { + $namecfr = $service['name'] . "." . $service["id"]; + $carpcfr = $service['name'] . "."; + } + + if ($namecfr == $name){ + switch ($value) { + + case "status": + $status = get_service_status($service); + if ($status=="") $status = 0; + echo $status; + break; + + case "name": + echo $namecfr; + break; + + case "enabled": + if (is_service_enabled($service['name'])) + echo 1; + else + echo 0; + break; + + case "run_on_carp_slave": + if (in_array($carpcfr,$stopped_on_carp_slave)) + echo 0; + else + echo 1; + default: + echo $service[$value]; + break; + } + } + } +} + + +//Gateway Discovery +function pfz_gw_rawstatus() { + //Return a Raw Gateway Status, useful for action Scripts (e.g. Update Cloudflare DNS config) + $gws = return_gateways_status(true); + $gw_string=""; + foreach ($gws as $gw){ + $gw_string .= ($gw['name'] . '.' . $gw['status'] .","); + } + echo rtrim($gw_string,","); +} + + +function pfz_gw_discovery() { + $gws = return_gateways_status(true); + + $json_string = '{"data":['; + foreach ($gws as $gw){ + $json_string .= '{"{#GATEWAY}":"' . $gw['name'] . '"'; + $json_string .= '},'; + } + $json_string = rtrim($json_string,","); + $json_string .= "]}"; + + echo $json_string; +} + + +function pfz_gw_value($gw, $valuekey) { + $gws = return_gateways_status(true); + if(array_key_exists($gw,$gws)) + echo $gws[$gw][$valuekey]; +} + + +function pfz_carp_status(){ + //Detect CARP Status + global $config; + $status_return = 0; + $status = get_carp_status(); + $carp_detected_problems = get_single_sysctl("net.inet.carp.demotion"); + + if ($status != 0) { //CARP is enabled + + if ($carp_detected_problems != 0) { + echo 4; //There's some Major Problems with CARP + return true; + } + + $status_changed = false; + $prev_status = ""; + foreach ($config['virtualip']['vip'] as $carp) { + if ($carp['mode'] != "carp") { + continue; + } + $if_status = get_carp_interface_status("_vip{$carp['uniqid']}"); + + if ( ($prev_status != $if_status) && (empty($if_status)==false) ) { //Some glitches with GUI + if ($prev_status!="") $status_changed = true; + $prev_status = $if_status; + } + } + if ($status_changed) { + //CARP Status is inconsistent across interfaces + echo 3; + } else { + if ($prev_status=="MASTER") + echo 1; + else + echo 2; + } + } else { + //CARP is Disabled + echo 0; + } +} + + +//System Information +function pfz_get_system_value($section){ + switch ($section){ + case "version": + echo( get_system_pkg_version()['version']); + break; + case "installed_version": + echo( get_system_pkg_version()['installed_version']); + break; + + } +} + +//Argument parsers +function pfz_discovery($section){ + switch (strtolower($section)){ + case "gw": + pfz_gw_discovery(); + break; + case "openvpn_server": + pfz_openvpn_serverdiscovery(); + break; + case "openvpn_client": + pfz_openvpn_clientdiscovery(); + break; + case "services": + pfz_services_discovery(); + break; + } +} + + +switch (strtolower($argv[1])){ + case "discovery": + pfz_discovery($argv[2]); + break; + case "gw_value": + pfz_gw_value($argv[2],$argv[3]); + break; + case "gw_status": + pfz_gw_rawstatus(); + break; + case "openvpn_servervalue": + pfz_openvpn_servervalue($argv[2],$argv[3]); + break; + case "openvpn_clientvalue": + pfz_openvpn_clientvalue($argv[2],$argv[3]); + break; + case "service_value": + pfz_service_value($argv[2],$argv[3]); + break; + case "carp_status": + pfz_carp_status(); + break; + case "if_name": + pfz_get_if_name($argv[2]); + break; + case "system": + pfz_get_system_value($argv[2]); + break; + default: + pfz_test(); +} diff --git a/template_pfsense_active.xml b/template_pfsense_active.xml new file mode 100644 index 0000000..c74055a --- /dev/null +++ b/template_pfsense_active.xml @@ -0,0 +1,5907 @@ + + + 4.0 + 2019-12-12T15:46:50Z + + + Templates + + + + + + + + {Template pfSense Active:vfs.file.cksum[/etc/passwd].diff(0)}>0 + 0 + + /etc/passwd has been changed on {HOST.NAME} + 0 + + + 0 + 2 + + 0 + 0 + + + + + {Template pfSense Active:pfsense.value[carp_status].last()}>2 + 0 + + CARP Problems on {HOST.NAME} + 0 + + + 0 + 4 + CARP Problems + 0 + 0 + + + + + {Template pfSense Active:pfsense.expected_carp_status.last()}<>0 and {Template pfSense Active:pfsense.value[carp_status].last()}<>{$EXPECTED_CARP_STATUS} + 0 + + CARP Status not Expected on {HOST.NAME} + 0 + + + 0 + 4 + pfSense CARP is not in the state Expected. This means that a failover could be in process. + 0 + 0 + + + + + {Template pfSense Active:kernel.maxfiles.last(0)}<1024 + 0 + + Configured max number of opened files is too low on {HOST.NAME} + 0 + + + 0 + 1 + + 0 + 0 + + + + + {Template pfSense Active:kernel.maxproc.last(0)}<256 + 0 + + Configured max number of processes is too low on {HOST.NAME} + 0 + + + 0 + 1 + + 0 + 0 + + + + + {Template pfSense Active:system.uname.diff(0)}>0 + 0 + + Host information was changed on {HOST.NAME} + 0 + + + 0 + 1 + + 0 + 0 + + + + + {Template pfSense Active:system.hostname.diff(0)}>0 + 0 + + Hostname was changed on {HOST.NAME} + 0 + + + 0 + 1 + + 0 + 0 + + + + + {Template pfSense Active:vm.memory.size[available].last(0)}<20M + 0 + + Lack of available memory on server {HOST.NAME} + 0 + + + 0 + 3 + + 0 + 0 + + + + + {Template pfSense Active:system.swap.size[,pfree].last(0)}<50 + 0 + + Lack of free swap space on {HOST.NAME} + 0 + + + 0 + 2 + It probably means that the systems requires more physical memory. + 0 + 0 + + + + + {Template pfSense Active:pfsense.mbuf.ptotal.last()}>80 + 0 + + MBUF used at 80% + 0 + + + 0 + 2 + + 0 + 0 + + + + + {Template pfSense Active:pfsense.mbuf.ptotal.last()}>90 + 0 + + MBUF used at 90% + 0 + + + 0 + 4 + + 0 + 0 + + + + + {Template pfSense Active:pfsense.value[system,version].last()}<>{Template pfSense Active:pfsense.value[system,installed_version].last()} + 0 + + New Version Available on {HOST.NAME} + 0 + + + 0 + 1 + Noify of new version of pfsense available + 0 + 0 + + + + + {Template pfSense Active:pfsense.value[gw_status].diff()}>0 + 1 + {Template pfSense Active:pfsense.value[gw_status].diff()}=0 + pfSense Gateway Status Changed on {HOST.NAME} + 0 + + + 0 + 3 + Gateway Status Change, for use with an acion Script (e.g. update DNS record) + 0 + 1 + + + + + {Template pfSense Active:system.cpu.load[percpu,avg1].avg(5m)}>5 + 0 + + Processor load is too high on {HOST.NAME} + 0 + + + 0 + 2 + + 0 + 0 + + + + + {Template pfSense Active:pfsense.states.pused.last()}>80 + 0 + + State Table used at 80% + 0 + + + 0 + 2 + + 0 + 0 + + + + + {Template pfSense Active:pfsense.states.pused.last()}>90 + 0 + + State Table used at 90% + 0 + + + 0 + 4 + + 0 + 0 + + + + + {Template pfSense Active:proc.num[].avg(5m)}>300 + 0 + + Too many processes on {HOST.NAME} + 0 + + + 0 + 2 + + 0 + 0 + + + + + {Template pfSense Active:proc.num[,,run].avg(5m)}>30 + 0 + + Too many processes running on {HOST.NAME} + 0 + + + 0 + 2 + + 0 + 0 + + + + + {Template pfSense Active:system.uptime.change(0)}<0 + 0 + + {HOST.NAME} has just been restarted + 0 + + + 0 + 1 + + 0 + 0 + + + + + + + Active Connections + 900 + 200 + 0.0000 + 100.0000 + 1 + 0 + 0 + 1 + 0 + 0.0000 + 0.0000 + 1 + 2 + 0 + + Template pfSense Active + pfsense.states.max + + + + 0 + 5 + FF2C27 + 0 + 2 + 0 + + Template pfSense Active + pfsense.states.current + + + + + + Active Connections (pie) + 600 + 340 + 0.0000 + 0.0000 + 0 + 0 + 2 + 1 + 0 + 0.0000 + 0.0000 + 0 + 0 + 0 + 0 + + + 0 + 0 + 5B5B5B + 0 + 2 + 2 + + Template pfSense Active + pfsense.states.max + + + + 1 + 5 + FF2C27 + 0 + 2 + 0 + + Template pfSense Active + pfsense.states.current + + + + + + CPU jumps + 900 + 200 + 0.0000 + 100.0000 + 1 + 1 + 0 + 1 + 0 + 0.0000 + 0.0000 + 0 + 0 + 0 + 0 + + + 0 + 5 + 009900 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.switches + + + + 1 + 5 + 000099 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.intr + + + + + + CPU load + 900 + 200 + 0.0000 + 100.0000 + 1 + 1 + 1 + 1 + 0 + 0.0000 + 0.0000 + 1 + 0 + 0 + 0 + + + 0 + 0 + FFA619 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.load[percpu,avg1] + + + + 1 + 0 + E86E30 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.load[percpu,avg5] + + + + 2 + 0 + FF2F26 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.load[percpu,avg15] + + + + + + CPU utilization (Line) + 900 + 200 + 0.0000 + 100.0000 + 1 + 0 + 0 + 1 + 0 + 0.0000 + 0.0000 + 1 + 1 + 0 + 0 + + + 0 + 5 + FFE819 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.util[,interrupt] + + + + 1 + 5 + E85D17 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.util[,nice] + + + + 2 + 5 + DF26FF + 0 + 2 + 0 + + Template pfSense Active + system.cpu.util[,system] + + + + 3 + 5 + 1775E8 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.util[,user] + + + + 4 + 0 + 03D933 + 0 + 2 + 0 + + Template pfSense Active + system.cpu.util[,idle] + + + + + + Memory Available details (pie) + 600 + 340 + 0.0000 + 0.0000 + 0 + 0 + 2 + 1 + 0 + 0.0000 + 0.0000 + 0 + 0 + 0 + 0 + + + 0 + 0 + 003300 + 0 + 2 + 2 + + Template pfSense Active + vm.memory.size[available] + + + + 1 + 0 + 005500 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[free] + + + + 2 + 0 + 007700 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[cached] + + + + 3 + 0 + 009900 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[inactive] + + + + + + Memory usage + 900 + 200 + 0.0000 + 100.0000 + 1 + 0 + 1 + 1 + 0 + 0.0000 + 0.0000 + 1 + 2 + 0 + + Template pfSense Active + vm.memory.size[total] + + + + 0 + 0 + 00EE00 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[wired] + + + + 1 + 0 + 00CC00 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[active] + + + + 2 + 0 + 007700 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[inactive] + + + + 3 + 0 + 005500 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[cached] + + + + 4 + 0 + 003300 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[free] + + + + + + Memory Usage simple (pie) + 600 + 340 + 0.0000 + 0.0000 + 0 + 0 + 2 + 1 + 0 + 0.0000 + 0.0000 + 0 + 0 + 0 + 0 + + + 0 + 0 + 003300 + 0 + 2 + 0 + + Template pfSense Active + vm.memory.size[available] + + + + 1 + 0 + 00DD00 + 0 + 2 + 0 + + Template pfSense Active + kt.mem.used + + + + + + Network Memory Buffer + 900 + 200 + 0.0000 + 100.0000 + 1 + 0 + 1 + 1 + 0 + 0.0000 + 0.0000 + 1 + 2 + 0 + + Template pfSense Active + pfsense.mbuf.max + + + + 0 + 0 + B26E16 + 0 + 2 + 0 + + Template pfSense Active + pfsense.mbuf.current + + + + 1 + 0 + FFCE8E + 0 + 2 + 0 + + Template pfSense Active + pfsense.mbuf.cache + + + + + + Network Memory Buffer (pie) + 600 + 340 + 0.0000 + 0.0000 + 0 + 0 + 2 + 1 + 0 + 0.0000 + 0.0000 + 0 + 0 + 0 + 0 + + + 0 + 0 + 5B5B5B + 0 + 2 + 2 + + Template pfSense Active + pfsense.mbuf.max + + + + 1 + 0 + FFCE8E + 0 + 2 + 0 + + Template pfSense Active + pfsense.mbuf.cache + + + + 2 + 0 + B26E16 + 0 + 2 + 0 + + Template pfSense Active + pfsense.mbuf.current + + + + + + Swap usage + 600 + 340 + 0.0000 + 0.0000 + 0 + 0 + 2 + 1 + 1 + 0.0000 + 0.0000 + 0 + 0 + 0 + 0 + + + 0 + 0 + 5B5B5B + 0 + 2 + 2 + + Template pfSense Active + system.swap.size[,total] + + + + 1 + 0 + FFFF33 + 0 + 2 + 0 + + Template pfSense Active + system.swap.size[,used] + + + + + + + + Generic YesNo + + + 0 + No + + + 1 + Yes + + + + + pfSense CARP Status + + + 0 + Disabled + + + 1 + Master + + + 2 + Backup + + + 3 + Inconsistent + + + 4 + Problem + + + + + pfSense Gateway Status + + + 0 + Up + + + 1 + Packet Loss + + + 2 + High Delay + + + 3 + High Packet Loss + + + 4 + Forced Down + + + 5 + Down + + + + + pfSense OpenVPN Interface Status + + + 0 + Down + + + 1 + Up + + + + + pfSense OpenVPN Mode + + + 1 + Peer to Peer (SSL/TLS) + + + 2 + P2P Shared Key + + + 3 + Remote Access (SSL/TLS) + + + 4 + Remote Access (User Auth) + + + 5 + Remote Access 8SSL/TLS + User Auth) + + + + + Service state + + + 0 + Down + + + 1 + Up + + + + +