Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F118305670
vyos-hostsd
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
vyos-hostsd
View Options
#!/usr/bin/env python3
#
# Copyright (C) 2019 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
os
import
sys
import
time
import
json
import
signal
import
traceback
import
re
import
zmq
import
jinja2
debug
=
True
DATA_DIR
=
"/var/lib/vyos/"
STATE_FILE
=
os
.
path
.
join
(
DATA_DIR
,
"hostsd.state"
)
SOCKET_PATH
=
"ipc:///run/vyos-hostsd.sock"
RESOLV_CONF_FILE
=
'/etc/resolv.conf'
HOSTS_FILE
=
'/etc/hosts'
hosts_tmpl_source
=
"""
### Autogenerated by VyOS ###
### Do not edit, your changes will get overwritten ###
# Local host
127.0.0.1 localhost
127.0.1.1 {{ host_name }}{
% i
f domain_name %}.{{ domain_name }} {{ host_name }}{
% e
ndif %}
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# From DHCP and "system static host-mapping"
{
%- i
f hosts %}
{
% f
or h in hosts -%}
{{hosts[h]['address']}}
\t
{{h}}
\t
{
% f
or a in hosts[h]['aliases'] %} {{a}} {
% e
ndfor %}
{
% e
ndfor %}
{
%- e
ndif %}
"""
hosts_tmpl
=
jinja2
.
Template
(
hosts_tmpl_source
)
resolv_tmpl_source
=
"""
### Autogenerated by VyOS ###
### Do not edit, your changes will get overwritten ###
{
% f
or ns in name_servers -%}
nameserver {{ns}}
{
% e
ndfor -%}
{
%- i
f domain_name %}
domain {{ domain_name }}
{
%- e
ndif %}
{
%- i
f search_domains %}
search {{ search_domains | join(" ") }}
{
%- e
ndif %}
"""
resolv_tmpl
=
jinja2
.
Template
(
resolv_tmpl_source
)
# The state data includes a list of name servers
# and a list of hosts entries.
#
# Name servers have the following structure:
# {"server": {"tag": <str>}}
#
# Hosts entries are similar:
# {"host": {"tag": <str>, "address": <str>, "aliases": <str list>}}
#
# The tag is either "static" or "dhcp-<intf>"
# It's used to distinguish entries created
# by different scripts so that they can be removed
# and re-created without having to track what needs
# to be changed
STATE
=
{
"name_servers"
:
{},
"hosts"
:
{},
"host_name"
:
"vyos"
,
"domain_name"
:
""
,
"search_domains"
:
[]}
def
make_resolv_conf
(
data
):
resolv_conf
=
resolv_tmpl
.
render
(
data
)
print
(
"Writing /etc/resolv.conf"
)
with
open
(
RESOLV_CONF_FILE
,
'w'
)
as
f
:
f
.
write
(
resolv_conf
)
def
make_hosts_file
(
state
):
print
(
"Writing /etc/hosts"
)
hosts
=
hosts_tmpl
.
render
(
state
)
with
open
(
HOSTS_FILE
,
'w'
)
as
f
:
f
.
write
(
hosts
)
def
add_hosts
(
data
,
entries
,
tag
):
hosts
=
data
[
'hosts'
]
if
not
entries
:
return
for
e
in
entries
:
host
=
e
[
'host'
]
hosts
[
host
]
=
{}
hosts
[
host
][
'tag'
]
=
tag
hosts
[
host
][
'address'
]
=
e
[
'address'
]
hosts
[
host
][
'aliases'
]
=
e
[
'aliases'
]
def
delete_hosts
(
data
,
tag
):
hosts
=
data
[
'hosts'
]
keys_for_deletion
=
[]
# You can't delete items from a dict while iterating over it,
# so we build a list of doomed items first
for
h
in
hosts
:
if
hosts
[
h
][
'tag'
]
==
tag
:
keys_for_deletion
.
append
(
h
)
for
k
in
keys_for_deletion
:
del
hosts
[
k
]
def
add_name_servers
(
data
,
entries
,
tag
):
name_servers
=
data
[
'name_servers'
]
if
not
entries
:
return
for
e
in
entries
:
name_servers
[
e
]
=
{}
name_servers
[
e
][
'tag'
]
=
tag
def
delete_name_servers
(
data
,
tag
):
name_servers
=
data
[
'name_servers'
]
regex_filter
=
re
.
compile
(
tag
)
for
ns
in
list
(
name_servers
.
keys
()):
if
regex_filter
.
match
(
name_servers
[
ns
][
'tag'
]):
del
name_servers
[
ns
]
def
set_host_name
(
state
,
data
):
if
data
[
'host_name'
]:
state
[
'host_name'
]
=
data
[
'host_name'
]
if
'domain_name'
in
data
:
state
[
'domain_name'
]
=
data
[
'domain_name'
]
if
'search_domains'
in
data
:
state
[
'search_domains'
]
=
data
[
'search_domains'
]
def
get_name_servers
(
state
,
tag
):
ns
=
[]
data
=
state
[
'name_servers'
]
regex_filter
=
re
.
compile
(
tag
)
for
n
in
data
:
if
regex_filter
.
match
(
data
[
n
][
'tag'
]):
ns
.
append
(
n
)
return
ns
def
get_option
(
msg
,
key
):
if
key
in
msg
:
return
msg
[
key
]
else
:
raise
ValueError
(
"Missing required option
\"
{0}
\"
"
.
format
(
key
))
def
handle_message
(
msg_json
):
msg
=
json
.
loads
(
msg_json
)
op
=
get_option
(
msg
,
'op'
)
_type
=
get_option
(
msg
,
'type'
)
if
op
==
'delete'
:
tag
=
get_option
(
msg
,
'tag'
)
if
_type
==
'name_servers'
:
delete_name_servers
(
STATE
,
tag
)
elif
_type
==
'hosts'
:
delete_hosts
(
STATE
,
tag
)
else
:
raise
ValueError
(
"Unknown message type
{0}
"
.
format
(
_type
))
elif
op
==
'add'
:
tag
=
get_option
(
msg
,
'tag'
)
entries
=
get_option
(
msg
,
'data'
)
if
_type
==
'name_servers'
:
add_name_servers
(
STATE
,
entries
,
tag
)
elif
_type
==
'hosts'
:
add_hosts
(
STATE
,
entries
,
tag
)
else
:
raise
ValueError
(
"Unknown message type
{0}
"
.
format
(
_type
))
elif
op
==
'set'
:
# Host name/domain name/search domain are set without a tag,
# there can be only one anyway
data
=
get_option
(
msg
,
'data'
)
if
_type
==
'host_name'
:
set_host_name
(
STATE
,
data
)
else
:
raise
ValueError
(
"Unknown message type
{0}
"
.
format
(
_type
))
elif
op
==
'get'
:
tag
=
get_option
(
msg
,
'tag'
)
if
_type
==
'name_servers'
:
result
=
get_name_servers
(
STATE
,
tag
)
else
:
raise
ValueError
(
"Unimplemented"
)
return
result
else
:
raise
ValueError
(
"Unknown operation
{0}
"
.
format
(
op
))
make_resolv_conf
(
STATE
)
make_hosts_file
(
STATE
)
print
(
"Saving state to
{0}
"
.
format
(
STATE_FILE
))
with
open
(
STATE_FILE
,
'w'
)
as
f
:
json
.
dump
(
STATE
,
f
)
def
exit_handler
(
sig
,
frame
):
""" Clean up the state when shutdown correctly """
print
(
"Cleaning up state"
)
os
.
unlink
(
STATE_FILE
)
sys
.
exit
(
0
)
if
__name__
==
'__main__'
:
signal
.
signal
(
signal
.
SIGTERM
,
exit_handler
)
# Create a directory for state checkpoints
os
.
makedirs
(
DATA_DIR
,
exist_ok
=
True
)
if
os
.
path
.
exists
(
STATE_FILE
):
with
open
(
STATE_FILE
,
'r'
)
as
f
:
try
:
data
=
json
.
load
(
f
)
STATE
=
data
except
:
print
(
traceback
.
format_exc
())
print
(
"Failed to load the state file, using default"
)
context
=
zmq
.
Context
()
socket
=
context
.
socket
(
zmq
.
REP
)
socket
.
bind
(
SOCKET_PATH
)
while
True
:
# Wait for next request from client
message
=
socket
.
recv
()
.
decode
()
print
(
"Received a configuration change request"
)
if
debug
:
print
(
"Request data:
{0}
"
.
format
(
message
))
resp
=
{}
try
:
result
=
handle_message
(
message
)
resp
[
'data'
]
=
result
except
ValueError
as
e
:
resp
[
'error'
]
=
str
(
e
)
except
:
print
(
traceback
.
format_exc
())
resp
[
'error'
]
=
"Internal error"
if
debug
:
print
(
"Sent response:
{0}
"
.
format
(
resp
))
# Send reply back to client
socket
.
send
(
json
.
dumps
(
resp
)
.
encode
())
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:06 AM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4351543
Default Alt Text
vyos-hostsd (7 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment