Page MenuHomeVyOS Platform

No OneTemporary

Size
32 KB
Referenced Files
None
Subscribers
None
diff --git a/data/templates/dhcp-server/dhcpdv6.conf.tmpl b/data/templates/dhcp-server/dhcpdv6.conf.tmpl
index de7c9b29c..25e5fa592 100644
--- a/data/templates/dhcp-server/dhcpdv6.conf.tmpl
+++ b/data/templates/dhcp-server/dhcpdv6.conf.tmpl
@@ -1,117 +1,123 @@
### Autogenerated by dhcpv6_server.py ###
# For options please consult the following website:
# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
log-facility local7;
{% if preference is defined and preference is not none %}
option dhcp6.preference {{ preference }};
{% endif %}
+{% if global_parameters is defined and global_parameters.name_server is defined and global_parameters.name_server is not none %}
+{% for nameserver in global_parameters.name_server %}
+option dhcp6.name-servers {{ nameserver }};
+{% endfor %}
+{% endif %}
+
# Shared network configration(s)
{% if shared_network_name is defined and shared_network_name is not none %}
{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %}
shared-network {{ network | replace('_','-') }} {
{% if network_config.common_options is defined and network_config.common_options is not none %}
{% if network_config.common_options.info_refresh_time is defined and network_config.common_options.info_refresh_time is not none %}
option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }};
{% endif %}
{% if network_config.common_options.domain_search is defined and network_config.common_options.domain_search is not none %}
option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}";
{% endif %}
{% if network_config.common_options.name_server is defined and network_config.common_options.name_server is not none %}
option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }};
{% endif %}
{% endif %}
{% if network_config.subnet is defined and network_config.subnet is not none %}
{% for subnet, subnet_config in network_config.subnet.items() %}
subnet6 {{ subnet }} {
{% if subnet_config.address_range is defined and subnet_config.address_range is not none %}
{% if subnet_config.address_range.prefix is defined and subnet_config.address_range.prefix is not none %}
{% for prefix, prefix_config in subnet_config.address_range.prefix.items() %}
range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is defined }};
{% endfor %}
{% endif %}
{% if subnet_config.address_range.start is defined and subnet_config.address_range.start is not none %}
{% for address, address_config in subnet_config.address_range.start.items() %}
range6 {{ address }} {{ address_config.stop }};
{% endfor %}
{% endif %}
{% endif %}
{% if subnet_config.domain_search is defined and subnet_config.domain_search is not none %}
option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}";
{% endif %}
{% if subnet_config.lease_time is defined and subnet_config.lease_time is not none %}
{% if subnet_config.lease_time.default is defined and subnet_config.lease_time.default is not none %}
default-lease-time {{ subnet_config.lease_time.default }};
{% endif %}
{% if subnet_config.lease_time.maximum is defined and subnet_config.lease_time.maximum is not none %}
max-lease-time {{ subnet_config.lease_time.maximum }};
{% endif %}
{% if subnet_config.lease_time.minimum is defined and subnet_config.lease_time.minimum is not none %}
min-lease-time {{ subnet_config.lease_time.minimum }};
{% endif %}
{% endif %}
{% if subnet_config.name_server is defined and subnet_config.name_server is not none %}
option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }};
{% endif %}
{% if subnet_config.nis_domain is defined and subnet_config.nis_domain is not none %}
option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}";
{% endif %}
{% if subnet_config.nis_server is defined and subnet_config.nis_server is not none %}
option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }};
{% endif %}
{% if subnet_config.nisplus_domain is defined and subnet_config.nisplus_domain is not none %}
option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}";
{% endif %}
{% if subnet_config.nisplus_server is defined and subnet_config.nisplus_server is not none %}
option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }};
{% endif %}
{% if subnet_config.sip_server is defined and subnet_config.sip_server is not none %}
{% set server_ip = [] %}
{% set server_fqdn = [] %}
{% for address in subnet_config.sip_server %}
{% if address | is_ipv6 %}
{% set server_ip = server_ip.append(address) %}
{% else %}
{% set server_fqdn = server_fqdn.append(address) %}
{% endif %}
{% endfor %}
{% if server_ip is defined and server_ip | length > 0 %}
option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }};
{% endif %}
{% if server_fqdn is defined and server_fqdn | length > 0 %}
option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}";
{% endif %}
{% endif %}
{% if subnet_config.sntp_server is defined and subnet_config.sntp_server is not none %}
option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }};
{% endif %}
{% if subnet_config.prefix_delegation is defined and subnet_config.prefix_delegation.start is defined and subnet_config.prefix_delegation.start is not none %}
{% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %}
prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }};
{% endfor %}
{% endif %}
{% if subnet_config.static_mapping is defined and subnet_config.static_mapping is not none %}
# begin configuration of static client mappings
{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not defined %}
host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} {
{% if host_config.identifier is defined and host_config.identifier is not none %}
host-identifier option dhcp6.client-id {{ host_config.identifier }};
{% endif %}
{% if host_config.ipv6_address is defined and host_config.ipv6_address is not none %}
fixed-address6 {{ host_config.ipv6_address }};
{% endif %}
}
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
on commit {
set shared-networkname = "{{ network | replace('_','-') }}";
}
}
{% endfor %}
{% endif %}
diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in
index fb0e79c47..a3cca06da 100644
--- a/interface-definitions/dhcpv6-server.xml.in
+++ b/interface-definitions/dhcpv6-server.xml.in
@@ -1,353 +1,373 @@
<?xml version="1.0"?>
<!-- DHCPv6 server configuration -->
<interfaceDefinition>
<node name="service">
<children>
<node name="dhcpv6-server" owner="${vyos_conf_scripts_dir}/dhcpv6_server.py">
<properties>
<help>DHCP for IPv6 (DHCPv6) server</help>
<priority>900</priority>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
+ <node name="global-parameters">
+ <properties>
+ <help>Additional global parameters for DHCPv6 server</help>
+ </properties>
+ <children>
+ <leafNode name="name-server">
+ <properties>
+ <help>IPv6 address of a Recursive DNS Server</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of DNS name server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="preference">
<properties>
<help>Preference of this DHCPv6 server compared with others</help>
<valueHelp>
<format>0-255</format>
<description>DHCPv6 server preference (0-255)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-255"/>
</constraint>
<constraintErrorMessage>Preference must be between 0 and 255</constraintErrorMessage>
</properties>
</leafNode>
<tagNode name="shared-network-name">
<properties>
<help>DHCPv6 shared network name [REQUIRED]</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_</constraintErrorMessage>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
<node name="common-options">
<properties>
<help>Common options to distribute to all clients, including stateless clients</help>
</properties>
<children>
<leafNode name="info-refresh-time">
<properties>
<help>Time (in seconds) that stateless clients should wait between refreshing the information they were given</help>
<valueHelp>
<format>1-4294967295</format>
<description>DHCPv6 information refresh time</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
#include <include/dhcp-server-domain-search.xml.i>
<leafNode name="name-server">
<properties>
<help>IPv6 address of a Recursive DNS Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of DNS name server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
</children>
</node>
<tagNode name="subnet">
<properties>
<help>IPv6 DHCP subnet for this shared network [REQUIRED]</help>
<valueHelp>
<format>ipv6net</format>
<description>IPv6 address and prefix length</description>
</valueHelp>
<constraint>
<validator name="ipv6-prefix"/>
</constraint>
</properties>
<children>
<node name="address-range">
<properties>
<help>Parameters setting ranges for assigning IPv6 addresses</help>
</properties>
<children>
<tagNode name="prefix">
<properties>
<help>IPv6 prefix defining range of addresses to assign</help>
<valueHelp>
<format>ipv6net</format>
<description>IPv6 address and prefix length</description>
</valueHelp>
<constraint>
<validator name="ipv6-prefix"/>
</constraint>
</properties>
<children>
<leafNode name="temporary">
<properties>
<help>Address range will be used for temporary addresses</help>
<valueless/>
</properties>
</leafNode>
</children>
</tagNode>
<tagNode name="start">
<properties>
<help>First in range of consecutive IPv6 addresses to assign</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
<children>
<leafNode name="stop">
<properties>
<help>Last in range of consecutive IPv6 addresses</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
#include <include/dhcp-server-domain-search.xml.i>
<node name="lease-time">
<properties>
<help>Parameters relating to the lease time</help>
</properties>
<children>
<leafNode name="default">
<properties>
<help>Default time (in seconds) that will be assigned to a lease</help>
<valueHelp>
<format>1-4294967295</format>
<description>DHCPv6 valid lifetime</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
<leafNode name="maximum">
<properties>
<help>Maximum time (in seconds) that will be assigned to a lease</help>
<valueHelp>
<format>1-4294967295</format>
<description>Maximum lease time in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
<leafNode name="minimum">
<properties>
<help>Minimum time (in seconds) that will be assigned to a lease</help>
<valueHelp>
<format>1-4294967295</format>
<description>Minimum lease time in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
<leafNode name="name-server">
<properties>
<help>IPv6 address of a Recursive DNS Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of DNS name server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<leafNode name="nis-domain">
<properties>
<help>NIS domain name for client to use</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid NIS domain name</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="nis-server">
<properties>
<help>IPv6 address of a NIS Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of NIS server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<leafNode name="nisplus-domain">
<properties>
<help>NIS+ domain name for client to use</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid NIS+ domain name. May only contain letters, numbers and .-_</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="nisplus-server">
<properties>
<help>IPv6 address of a NIS+ Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of NIS+ server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<node name="prefix-delegation">
<properties>
<help>Parameters relating to IPv6 prefix delegation</help>
</properties>
<children>
<tagNode name="start">
<properties>
<help>First in range of IPv6 addresses to be used in prefix delegation</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address used in prefix delegation</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
<children>
<leafNode name="prefix-length">
<properties>
<help>Length in bits of prefixes to be delegated</help>
<valueHelp>
<format>32-64</format>
<description>Delagated prefix length (32-64)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 32-64"/>
</constraint>
<constraintErrorMessage>Delegated prefix length must be between 32 and 64</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="stop">
<properties>
<help>Last in range of IPv6 addresses to be used in prefix delegation</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address used in prefix delegation</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
<leafNode name="sip-server">
<properties>
<help>IPv6 address of SIP server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of SIP server</description>
</valueHelp>
<valueHelp>
<format>hostname</format>
<description>FQDN of SIP server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
<validator name="fqdn"/>
</constraint>
<multi/>
</properties>
</leafNode>
<leafNode name="sntp-server">
<properties>
<help>IPv6 address of an SNTP server for client to use</help>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<tagNode name="static-mapping">
<properties>
<help>Name of static mapping</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid static mapping name. May only contain letters, numbers and .-_</constraintErrorMessage>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
<leafNode name="identifier">
<properties>
<help>Client identifier (DUID) for this static mapping</help>
<valueHelp>
<format>h[[:h]...]</format>
<description>DUID: colon-separated hex list (as used by isc-dhcp option dhcpv6.client-id)</description>
</valueHelp>
<constraint>
<regex>([0-9A-Fa-f]{1,2}[:])*([0-9A-Fa-f]{1,2})</regex>
</constraint>
<constraintErrorMessage>Invalid DUID, must be in the format h[[:h]...]</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="ipv6-address">
<properties>
<help>Client IPv6 address for this static mapping</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address for this static mapping</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</tagNode>
</children>
</tagNode>
</children>
</node>
</children>
</node>
</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 319891a94..a364eee11 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -1,155 +1,176 @@
#!/usr/bin/env python3
#
# Copyright (C) 2020 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import os
import unittest
from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.template import inc_ip
from vyos.util import cmd
from vyos.util import process_named_running
from vyos.util import read_file
PROCESS_NAME = 'dhcpd'
DHCPD_CONF = '/run/dhcp-server/dhcpdv6.conf'
base_path = ['service', 'dhcpv6-server']
subnet = '2001:db8:f00::/64'
dns_1 = '2001:db8::1'
dns_2 = '2001:db8::2'
domain = 'vyos.net'
nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2']
interface = 'eth1'
interface_addr = inc_ip(subnet, 1) + '/64'
class TestServiceDHCPServer(unittest.TestCase):
def setUp(self):
self.session = ConfigSession(os.getpid())
self.session.set(['interfaces', 'ethernet', interface, 'address', interface_addr])
def tearDown(self):
self.session.delete(base_path)
self.session.delete(['interfaces', 'ethernet', interface, 'address', interface_addr])
self.session.commit()
del self.session
def test_single_pool(self):
shared_net_name = 'SMOKE-1'
search_domains = ['foo.vyos.net', 'bar.vyos.net']
lease_time = '1200'
max_lease_time = '72000'
min_lease_time = '600'
preference = '10'
sip_server = 'sip.vyos.net'
sntp_server = inc_ip(subnet, 100)
range_start = inc_ip(subnet, 256) # ::100
range_stop = inc_ip(subnet, 65535) # ::ffff
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.session.set(base_path + ['preference', preference])
# we use the first subnet IP address as default gateway
self.session.set(pool + ['name-server', dns_1])
self.session.set(pool + ['name-server', dns_2])
self.session.set(pool + ['name-server', dns_2])
self.session.set(pool + ['lease-time', 'default', lease_time])
self.session.set(pool + ['lease-time', 'maximum', max_lease_time])
self.session.set(pool + ['lease-time', 'minimum', min_lease_time])
self.session.set(pool + ['nis-domain', domain])
self.session.set(pool + ['nisplus-domain', domain])
self.session.set(pool + ['sip-server', sip_server])
self.session.set(pool + ['sntp-server', sntp_server])
self.session.set(pool + ['address-range', 'start', range_start, 'stop', range_stop])
for server in nis_servers:
self.session.set(pool + ['nis-server', server])
self.session.set(pool + ['nisplus-server', server])
for search in search_domains:
self.session.set(pool + ['domain-search', search])
client_base = 1
for client in ['client1', 'client2', 'client3']:
cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base)
self.session.set(pool + ['static-mapping', client, 'identifier', cid])
self.session.set(pool + ['static-mapping', client, 'ipv6-address', inc_ip(subnet, client_base)])
client_base += 1
# commit changes
self.session.commit()
config = read_file(DHCPD_CONF)
self.assertIn(f'option dhcp6.preference {preference};', config)
self.assertIn(f'subnet6 {subnet}' + r' {', config)
search = '"' + '", "'.join(search_domains) + '"'
nissrv = ', '.join(nis_servers)
self.assertIn(f'range6 {range_start} {range_stop};', config)
self.assertIn(f'default-lease-time {lease_time};', config)
self.assertIn(f'default-lease-time {lease_time};', config)
self.assertIn(f'max-lease-time {max_lease_time};', config)
self.assertIn(f'min-lease-time {min_lease_time};', config)
self.assertIn(f'option dhcp6.domain-search {search};', config)
self.assertIn(f'option dhcp6.name-servers {dns_1}, {dns_2};', config)
self.assertIn(f'option dhcp6.nis-domain-name "{domain}";', config)
self.assertIn(f'option dhcp6.nis-servers {nissrv};', config)
self.assertIn(f'option dhcp6.nisp-domain-name "{domain}";', config)
self.assertIn(f'option dhcp6.nisp-servers {nissrv};', config)
self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
client_base = 1
for client in ['client1', 'client2', 'client3']:
cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base)
ip = inc_ip(subnet, client_base)
self.assertIn(f'host {shared_net_name}_{client}' + ' {', config)
self.assertIn(f'fixed-address6 {ip};', config)
self.assertIn(f'host-identifier option dhcp6.client-id {cid};', config)
client_base += 1
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
def test_prefix_delegation(self):
shared_net_name = 'SMOKE-2'
range_start = inc_ip(subnet, 256) # ::100
range_stop = inc_ip(subnet, 65535) # ::ffff
delegate_start = '2001:db8:ee::'
delegate_stop = '2001:db8:ee:ff00::'
delegate_len = '56'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.session.set(pool + ['address-range', 'start', range_start, 'stop', range_stop])
self.session.set(pool + ['prefix-delegation', 'start', delegate_start, 'stop', delegate_stop])
self.session.set(pool + ['prefix-delegation', 'start', delegate_start, 'prefix-length', delegate_len])
# commit changes
self.session.commit()
config = read_file(DHCPD_CONF)
self.assertIn(f'subnet6 {subnet}' + r' {', config)
self.assertIn(f'range6 {range_start} {range_stop};', config)
self.assertIn(f'prefix6 {delegate_start} {delegate_stop} /{delegate_len};', config)
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_global_nameserver(self):
+ shared_net_name = 'SMOKE-3'
+ ns_global_1 = '2001:db8::1111'
+ ns_global_2 = '2001:db8::2222'
+
+ self.session.set(base_path + ['global-parameters', 'name-server', ns_global_1])
+ self.session.set(base_path + ['global-parameters', 'name-server', ns_global_2])
+ self.session.set(base_path + ['shared-network-name', shared_net_name, 'subnet', subnet])
+
+ # commit changes
+ self.session.commit()
+
+ config = read_file(DHCPD_CONF)
+ self.assertIn(f'option dhcp6.name-servers {ns_global_1};', config)
+ self.assertIn(f'option dhcp6.name-servers {ns_global_2};', config)
+ self.assertIn(f'subnet6 {subnet}' + r' {', config)
+ self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
if __name__ == '__main__':
unittest.main(verbosity=2)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 6, 10:27 AM (1 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4654210
Default Alt Text
(32 KB)

Event Timeline