summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/openvpn/client.conf.tmpl2
-rw-r--r--data/templates/salt-minion/minion.tmpl4
-rw-r--r--data/templates/snmp/etc.snmpd.conf.tmpl4
-rw-r--r--interface-definitions/interfaces-tunnel.xml.in35
-rw-r--r--op-mode-definitions/dhcp.xml.in4
-rw-r--r--op-mode-definitions/dns-forwarding.xml.in2
-rw-r--r--op-mode-definitions/flow-accounting-op.xml.in2
-rw-r--r--op-mode-definitions/ipoe-server.xml.in2
-rw-r--r--op-mode-definitions/ipv6-route.xml.in38
-rw-r--r--op-mode-definitions/pppoe-server.xml.in2
-rw-r--r--op-mode-definitions/restart-snmp.xml.in13
-rw-r--r--op-mode-definitions/restart-ssh.xml.in13
-rw-r--r--op-mode-definitions/show-ipv6-route.xml.in30
-rw-r--r--op-mode-definitions/vrrp.xml.in2
-rw-r--r--python/vyos/ifconfig/tunnel.py145
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py19
-rw-r--r--vyos-configtest0
17 files changed, 211 insertions, 106 deletions
diff --git a/data/templates/openvpn/client.conf.tmpl b/data/templates/openvpn/client.conf.tmpl
index 62387ef7c..e6e15b6ad 100644
--- a/data/templates/openvpn/client.conf.tmpl
+++ b/data/templates/openvpn/client.conf.tmpl
@@ -23,7 +23,7 @@ ifconfig-ipv6-push {{ ipv6_ip[0] }} {{ ipv6_remote }}
push "route-ipv6 {{ route6 }}"
{% endfor %}
{% for net6 in ipv6_subnet %}
-iroute {{ net6 }}
+iroute-ipv6 {{ net6 }}
{% endfor %}
{% endif %}
{% if disable is defined %}
diff --git a/data/templates/salt-minion/minion.tmpl b/data/templates/salt-minion/minion.tmpl
index 405fb9131..99749b57a 100644
--- a/data/templates/salt-minion/minion.tmpl
+++ b/data/templates/salt-minion/minion.tmpl
@@ -21,7 +21,9 @@ hash_type: {{ hash }}
# location. Remote logging works best when configured to use rsyslogd(8) (e.g.:
# ``file:///dev/log``), with rsyslogd(8) configured for network logging. The URI
# format is: <file|udp|tcp>://<host|socketpath>:<port-if-required>/<log-facility>
-log_file: file:///dev/log
+# log_file: file:///dev/log
+#
+log_file: /var/log/salt/minion
# The level of messages to send to the console.
# One of 'garbage', 'trace', 'debug', info', 'warning', 'error', 'critical'.
diff --git a/data/templates/snmp/etc.snmpd.conf.tmpl b/data/templates/snmp/etc.snmpd.conf.tmpl
index 278506350..db2114fa1 100644
--- a/data/templates/snmp/etc.snmpd.conf.tmpl
+++ b/data/templates/snmp/etc.snmpd.conf.tmpl
@@ -22,6 +22,10 @@ notificationEvent linkDownTrap linkDown ifIndex ifDescr ifType ifAdminStatus i
monitor -r 10 -e linkUpTrap "Generate linkUp" ifOperStatus != 2
monitor -r 10 -e linkDownTrap "Generate linkDown" ifOperStatus == 2
+# Remove all old ifTable entries with the same ifName as newly appeared
+# interface (with different ifIndex) - this is the case on e.g. ppp interfaces
+interface_replace_old yes
+
########################
# configurable section #
########################
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index 3a4db6f09..39e274840 100644
--- a/interface-definitions/interfaces-tunnel.xml.in
+++ b/interface-definitions/interfaces-tunnel.xml.in
@@ -177,25 +177,42 @@
<help>IPv4 specific tunnel parameters</help>
</properties>
<children>
+ <leafNode name="no-pmtu-discovery">
+ <properties>
+ <help>Disable path MTU discovery</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="ttl">
<properties>
- <help>Time to live field</help>
+ <help>Time to live (default: 0)</help>
<valueHelp>
- <format>0-255</format>
- <description>Time to live (default 255)</description>
+ <format>0</format>
+ <description>Copy value from original IP header</description>
+ </valueHelp>
+ <valueHelp>
+ <format>1-255</format>
+ <description>Time to Live</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-255"/>
</constraint>
<constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage>
</properties>
- <defaultValue>255</defaultValue>
+ <defaultValue>0</defaultValue>
</leafNode>
<leafNode name="tos">
<properties>
- <help>Type of Service (TOS)</help>
+ <help>Type of Service (default: 0)</help>
+ <completionHelp>
+ <list>inherit</list>
+ </completionHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Copy value from original IP header</description>
+ </valueHelp>
<valueHelp>
- <format>0-99</format>
+ <format>1-99</format>
<description>Type of Service (TOS)</description>
</valueHelp>
<constraint>
@@ -203,7 +220,7 @@
</constraint>
<constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage>
</properties>
- <defaultValue>inherit</defaultValue>
+ <defaultValue>0</defaultValue>
</leafNode>
<leafNode name="key">
<properties>
@@ -215,7 +232,7 @@
<constraint>
<validator name="numeric" argument="--range 0-4294967295"/>
</constraint>
- <constraintErrorMessage>key must be between 0-4294967295</constraintErrorMessage>
+ <constraintErrorMessage>Key must be in range 0-4294967295</constraintErrorMessage>
</properties>
</leafNode>
</children>
@@ -230,7 +247,7 @@
<help>Encaplimit field</help>
<valueHelp>
<format>0-255</format>
- <description>Encaplimit (default 4)</description>
+ <description>Encaplimit (default: 4)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-255"/>
diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in
index 48752cfd5..1dacbd5ba 100644
--- a/op-mode-definitions/dhcp.xml.in
+++ b/op-mode-definitions/dhcp.xml.in
@@ -123,7 +123,7 @@
<children>
<node name="dhcp">
<properties>
- <help>Restart DHCP processes</help>
+ <help>Restart DHCP server processes</help>
</properties>
<children>
<node name="server">
@@ -142,7 +142,7 @@
</node>
<node name="dhcpv6">
<properties>
- <help>Restart DHCPv6 processes</help>
+ <help>Restart DHCPv6 server processes</help>
</properties>
<children>
<node name="server">
diff --git a/op-mode-definitions/dns-forwarding.xml.in b/op-mode-definitions/dns-forwarding.xml.in
index 23de97704..36fe6b5ef 100644
--- a/op-mode-definitions/dns-forwarding.xml.in
+++ b/op-mode-definitions/dns-forwarding.xml.in
@@ -45,7 +45,7 @@
<children>
<node name="dns">
<properties>
- <help>Restart a DNS service</help>
+ <help>Restart specific DNS service</help>
</properties>
<children>
<leafNode name="forwarding">
diff --git a/op-mode-definitions/flow-accounting-op.xml.in b/op-mode-definitions/flow-accounting-op.xml.in
index 912805d59..b847338f9 100644
--- a/op-mode-definitions/flow-accounting-op.xml.in
+++ b/op-mode-definitions/flow-accounting-op.xml.in
@@ -55,7 +55,7 @@
<children>
<leafNode name="flow-accounting">
<properties>
- <help>Restart flow-accounting service</help>
+ <help>Restart (net)flow accounting process</help>
</properties>
<command>${vyos_op_scripts_dir}/flow_accounting_op.py --action restart</command>
</leafNode>
diff --git a/op-mode-definitions/ipoe-server.xml.in b/op-mode-definitions/ipoe-server.xml.in
index c20d3aa2a..18178f0b0 100644
--- a/op-mode-definitions/ipoe-server.xml.in
+++ b/op-mode-definitions/ipoe-server.xml.in
@@ -72,7 +72,7 @@
<children>
<leafNode name="ipoe-server">
<properties>
- <help>show ipoe-server status</help>
+ <help>Restart IPoE server process</help>
</properties>
<command>${vyos_op_scripts_dir}/ipoe-control.py --action="restart"</command>
</leafNode>
diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in
index fbf6489ba..28f5b1aad 100644
--- a/op-mode-definitions/ipv6-route.xml.in
+++ b/op-mode-definitions/ipv6-route.xml.in
@@ -24,44 +24,6 @@
<command>ip -f inet6 neigh list</command>
</leafNode>
- <node name="route">
- <properties>
- <help>Show IPv6 routes</help>
- </properties>
- <children>
- <node name="cache">
- <properties>
- <help>Show kernel IPv6 route cache</help>
- </properties>
- <command>ip -s -f inet6 route list cache</command>
- </node>
- <tagNode name="cache">
- <properties>
- <help>Show kernel IPv6 route cache for a given route</help>
- <completionHelp>
- <list>&lt;h:h:h:h:h:h:h:h&gt; &lt;h:h:h:h:h:h:h:h/x&gt;</list>
- </completionHelp>
- </properties>
- <command>ip -s -f inet6 route list cache $5</command>
- </tagNode>
- <node name="forward">
- <properties>
- <help>Show kernel IPv6 route table</help>
- </properties>
- <command>ip -f inet6 route list</command>
- </node>
- <tagNode name="forward">
- <properties>
- <help>Show kernel IPv6 route table for a given route</help>
- <completionHelp>
- <list>&lt;h:h:h:h:h:h:h:h&gt; &lt;h:h:h:h:h:h:h:h/x&gt;</list>
- </completionHelp>
- </properties>
- <command>ip -s -f inet6 route list $5</command>
- </tagNode>
- </children>
- </node>
-
</children>
</node>
</children>
diff --git a/op-mode-definitions/pppoe-server.xml.in b/op-mode-definitions/pppoe-server.xml.in
index 5ac9d9497..6d89b3e77 100644
--- a/op-mode-definitions/pppoe-server.xml.in
+++ b/op-mode-definitions/pppoe-server.xml.in
@@ -33,7 +33,7 @@
<children>
<leafNode name="pppoe-server">
<properties>
- <help>Restarts pppoe-server</help>
+ <help>Restart PPPoE server process</help>
</properties>
<command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="restart"</command>
</leafNode>
diff --git a/op-mode-definitions/restart-snmp.xml.in b/op-mode-definitions/restart-snmp.xml.in
new file mode 100644
index 000000000..7de27df64
--- /dev/null
+++ b/op-mode-definitions/restart-snmp.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="restart">
+ <children>
+ <node name="snmp">
+ <properties>
+ <help>Restart SNMP service</help>
+ </properties>
+ <command>if cli-shell-api existsActive service snmp; then sudo systemctl restart snmpd.service; else echo "Service SNMP not configured"; fi</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/restart-ssh.xml.in b/op-mode-definitions/restart-ssh.xml.in
new file mode 100644
index 000000000..6504cc18a
--- /dev/null
+++ b/op-mode-definitions/restart-ssh.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="restart">
+ <children>
+ <node name="ssh">
+ <properties>
+ <help>Restart SSH service</help>
+ </properties>
+ <command>if cli-shell-api existsActive service ssh; then sudo systemctl restart ssh.service; else echo "Service SSH not configured"; fi</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-ipv6-route.xml.in b/op-mode-definitions/show-ipv6-route.xml.in
index 9ac8687ab..065ea6f1f 100644
--- a/op-mode-definitions/show-ipv6-route.xml.in
+++ b/op-mode-definitions/show-ipv6-route.xml.in
@@ -19,12 +19,42 @@
</properties>
<command>vtysh -c "show ipv6 route bgp"</command>
</node>
+ <node name="cache">
+ <properties>
+ <help>Show kernel IPv6 route cache</help>
+ </properties>
+ <command>ip -s -f inet6 route list cache</command>
+ </node>
+ <tagNode name="cache">
+ <properties>
+ <help>Show kernel IPv6 route cache for a given route</help>
+ <completionHelp>
+ <list>&lt;h:h:h:h:h:h:h:h&gt; &lt;h:h:h:h:h:h:h:h/x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>ip -s -f inet6 route list cache $5</command>
+ </tagNode>
<node name="connected">
<properties>
<help>Show IPv6 connected routes</help>
</properties>
<command>vtysh -c "show ipv6 route connected"</command>
</node>
+ <node name="forward">
+ <properties>
+ <help>Show kernel IPv6 route table</help>
+ </properties>
+ <command>ip -f inet6 route list</command>
+ </node>
+ <tagNode name="forward">
+ <properties>
+ <help>Show kernel IPv6 route table for a given route</help>
+ <completionHelp>
+ <list>&lt;h:h:h:h:h:h:h:h&gt; &lt;h:h:h:h:h:h:h:h/x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>ip -s -f inet6 route list $5</command>
+ </tagNode>
<node name="isis">
<properties>
<help>Show IPv6 IS-IS routes</help>
diff --git a/op-mode-definitions/vrrp.xml.in b/op-mode-definitions/vrrp.xml.in
index 856fb440d..34484c706 100644
--- a/op-mode-definitions/vrrp.xml.in
+++ b/op-mode-definitions/vrrp.xml.in
@@ -28,7 +28,7 @@
<children>
<node name="vrrp">
<properties>
- <help>Restart the VRRP (Virtual Router Redundancy Protocol) process</help>
+ <help>Restart VRRP (Virtual Router Redundancy Protocol) process</help>
</properties>
<command>sudo systemctl restart keepalived.service</command>
</node>
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 1af4f8e72..7e3f9565a 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -48,31 +48,42 @@ class _Tunnel(Interface):
},
}
+ default = {
+ 'local' : '',
+ 'remote': '',
+ 'dev' : '',
+ 'ttl' : '',
+ 'tos' : '',
+ 'key' : '',
+ }
+ options = Interface.options + list(default.keys())
+
# TODO: This is surely used for more than tunnels
# TODO: could be refactored elsewhere
- _command_set = {**Interface._command_set, **{
- 'multicast': {
- 'validate': lambda v: assert_list(v, ['enable', 'disable']),
- 'convert': enable_to_on,
- 'shellcmd': 'ip link set dev {ifname} multicast {value}',
- },
- 'allmulticast': {
- 'validate': lambda v: assert_list(v, ['enable', 'disable']),
- 'convert': enable_to_on,
- 'shellcmd': 'ip link set dev {ifname} allmulticast {value}',
- },
- }}
-
+ _command_set = {
+ **Interface._command_set,
+ **{
+ 'multicast': {
+ 'validate': lambda v: assert_list(v, ['enable', 'disable']),
+ 'convert': enable_to_on,
+ 'shellcmd': 'ip link set dev {ifname} multicast {value}',
+ },
+ 'allmulticast': {
+ 'validate': lambda v: assert_list(v, ['enable', 'disable']),
+ 'convert': enable_to_on,
+ 'shellcmd': 'ip link set dev {ifname} allmulticast {value}',
+ },
+ }
+ }
_create_cmd = 'ip tunnel add {ifname} mode {type}'
- def __init__(self, ifname, **config):
- self.config = deepcopy(config) if config else {}
- super().__init__(ifname, **config)
-
def _create(self):
# add " option-name option-name-value ..." for all options set
- options = " ".join(["{} {}".format(k, self.config[k])
- for k in self.options if k in self.config and self.config[k]])
+ options = ' '.join(['{} {}'.format(k, self.config[k])
+ for k,v in self.config.items() if v and k not in
+ ['ifname', 'type', 'raw']])
+ if 'raw' in self.config:
+ options += ' ' + ' '.join(self.config['raw'])
self._cmd('{} {}'.format(self._create_cmd.format(**self.config), options))
self.set_admin_state('down')
@@ -80,14 +91,13 @@ class _Tunnel(Interface):
change = 'ip tunnel change {ifname} mode {type}'
# add " option-name option-name-value ..." for all options set
- options = " ".join(["{} {}".format(k, self.config[k])
- for k in self.options if k in self.config and self.config[k]])
+ options = ' '.join(['{} {}'.format(k, self.config[k])
+ for k,v in self.config.items() if v and k not in
+ ['ifname', 'type', 'raw']])
+ if 'raw' in self.config:
+ options += ' ' + ' '.join(self.config['raw'])
self._cmd('{} {}'.format(change.format(**self.config), options))
- @classmethod
- def get_config(cls):
- return dict(zip(cls.options, ['']*len(cls.options)))
-
def get_mac(self):
"""
Get current interface MAC (Media Access Contrl) address used.
@@ -141,8 +151,13 @@ class GREIf(_Tunnel):
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_gre.c
"""
- default = {'type': 'gre'}
- options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key']
+ default = {
+ **_Tunnel.default,
+ **{
+ 'type': 'gre',
+ 'raw' : ['pmtudisc'], # parameters that we can pass raw to ip command
+ },
+ }
# GreTap also called GRE Bridge
class GRETapIf(_Tunnel):
@@ -151,18 +166,20 @@ class GRETapIf(_Tunnel):
https://en.wikipedia.org/wiki/TUN/TAP
"""
-
# no multicast, ttl or tos for gretap
-
definition = {
**_Tunnel.definition,
**{
'bridgeable': True,
},
}
-
- default = {'type': 'gretap'}
- options = ['local', 'remote', 'ttl',]
+ default = {
+ 'type': 'gretap',
+ 'local': '',
+ 'remote': '',
+ 'dev': '',
+ 'raw' : ['pmtudisc'], # parameters that we can pass raw to ip command
+ }
_create_cmd = 'ip link add name {ifname} type {type}'
@@ -177,10 +194,16 @@ class IP6GREIf(_Tunnel):
https://tools.ietf.org/html/rfc7676
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_gre6.c
"""
-
- default = {'type': 'ip6gre'}
- options = ['local', 'remote', 'dev', 'encaplimit',
- 'hoplimit', 'tclass', 'flowlabel']
+ default = {
+ **_Tunnel.default,
+ **{
+ 'type': 'ip6gre',
+ 'encaplimit': '',
+ 'hoplimit': '',
+ 'tclass': '',
+ 'flowlabel': '',
+ },
+ }
class IPIPIf(_Tunnel):
"""
@@ -189,12 +212,15 @@ class IPIPIf(_Tunnel):
For more information please refer to:
https://tools.ietf.org/html/rfc2003
"""
-
# IPIP does not allow to pass multicast, unlike GRE
# but the interface itself can be set with multicast
-
- default = {'type': 'ipip'}
- options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key']
+ default = {
+ **_Tunnel.default,
+ **{
+ 'type': 'ipip',
+ 'raw' : ['pmtudisc'], # parameters that we can pass raw to ip command
+ },
+ }
class IPIP6If(_Tunnel):
"""
@@ -203,10 +229,16 @@ class IPIP6If(_Tunnel):
For more information please refer to:
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_ip6tnl.c
"""
-
- default = {'type': 'ipip6'}
- options = ['local', 'remote', 'dev', 'encaplimit',
- 'hoplimit', 'tclass', 'flowlabel']
+ default = {
+ **_Tunnel.default,
+ **{
+ 'type': 'ipip6',
+ 'encaplimit': '',
+ 'hoplimit': '',
+ 'tclass': '',
+ 'flowlabel': '',
+ },
+ }
class IP6IP6If(IPIP6If):
"""
@@ -215,7 +247,12 @@ class IP6IP6If(IPIP6If):
For more information please refer to:
https://tools.ietf.org/html/rfc2473
"""
- default = {'type': 'ip6ip6'}
+ default = {
+ **_Tunnel.default,
+ **{
+ 'type': 'ip6ip6',
+ },
+ }
class SitIf(_Tunnel):
@@ -225,9 +262,12 @@ class SitIf(_Tunnel):
For more information please refer to:
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_iptnl.c
"""
-
- default = {'type': 'sit'}
- options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key']
+ default = {
+ **_Tunnel.default,
+ **{
+ 'type': 'sit',
+ },
+ }
class Sit6RDIf(SitIf):
"""
@@ -236,7 +276,14 @@ class Sit6RDIf(SitIf):
https://en.wikipedia.org/wiki/IPv6_rapid_deployment
"""
# TODO: check if key can really be used with 6RD
- options = ['remote', 'ttl', 'tos', 'key', '6rd-prefix', '6rd-relay-prefix']
+ default = {
+ **_Tunnel.default,
+ **{
+ 'type': '6rd',
+ '6rd_prefix' : '',
+ '6rd_relay_prefix' : '',
+ },
+ }
def _create(self):
# do not call _Tunnel.create, building fully here
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index ffeb57784..d2fcf3121 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -126,6 +126,15 @@ def verify(tunnel):
if 'source_interface' in tunnel:
verify_interface_exists(tunnel['source_interface'])
+ # TTL != 0 and nopmtudisc are incompatible, parameters and ip use default
+ # values, thus the keys are always present.
+ if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None:
+ if dict_search('parameters.ip.ttl', tunnel) != '0':
+ raise ConfigError('Disabled PMTU requires TTL set to "0"!')
+ if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
+ raise ConfigError('Can not disable PMTU discovery for given encapsulation')
+
+
def generate(tunnel):
return None
@@ -174,7 +183,10 @@ def apply(tunnel):
if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
mappingv6 = {
# this : get_config()
- 'parameters.ipv6.encaplimit' : 'encaplimit'
+ 'parameters.ipv6.encaplimit' : 'encaplimit',
+ 'parameters.ipv6.flowlabel' : 'flowlabel',
+ 'parameters.ipv6.hoplimit' : 'hoplimit',
+ 'parameters.ipv6.tclass' : 'flowlabel'
}
mapping.update(mappingv6)
@@ -182,6 +194,11 @@ def apply(tunnel):
if dict_search(our_key, tunnel) and their_key in conf:
conf[their_key] = dict_search(our_key, tunnel)
+ if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None:
+ if 'pmtudisc' in conf['raw']:
+ conf['raw'].remove('pmtudisc')
+ conf['raw'].append('nopmtudisc')
+
tun = klass(tunnel['ifname'], **conf)
tun.change_options()
tun.update(tunnel)
diff --git a/vyos-configtest b/vyos-configtest
deleted file mode 100644
index e69de29bb..000000000
--- a/vyos-configtest
+++ /dev/null