184 lines
6.0 KiB
Python
184 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# DNSAggregatorInstance.py - Class implementing a DNS aggregator and run by pvcd
|
|
# Part of the Parallel Virtual Cluster (PVC) system
|
|
#
|
|
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# 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 <https://www.gnu.org/licenses/>.
|
|
#
|
|
###############################################################################
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import sqlite3
|
|
|
|
import pvcd.log as log
|
|
import pvcd.zkhandler as zkhandler
|
|
import pvcd.common as common
|
|
|
|
# A barebones PowerDNS sqlite schema (relative to the active dir)
|
|
sql_schema_file = 'pvcd/powerdns-aggregator-schema.sql'
|
|
|
|
class DNSAggregatorInstance(object):
|
|
# Initialization function
|
|
def __init__(self, zk_conn, config, logger, d_network):
|
|
self.zk_conn = zk_conn
|
|
self.config = config
|
|
self.logger = logger
|
|
self.d_network = d_network
|
|
|
|
self.active = False
|
|
self.database_file = self.config['pdns_dynamic_directory'] + '/pdns-aggregator.sqlite3'
|
|
|
|
self.dns_server_daemon = None
|
|
|
|
self.prepare_db()
|
|
|
|
# Preparation function
|
|
def prepare_db(self):
|
|
# Connect to the database
|
|
sql_conn = sqlite3.connect(self.database_file)
|
|
sql_curs = sql_conn.cursor()
|
|
|
|
# Try to access the domains table
|
|
try:
|
|
sql_curs.execute(
|
|
'select * from domains'
|
|
)
|
|
write_schema = False
|
|
# The table doesn't exist, so we should create it
|
|
except sqlite3.OperationalError:
|
|
write_schema = True
|
|
|
|
if write_schema:
|
|
with open('{}/{}'.format(os.getcwd(), sql_schema_file), 'r') as schema_file:
|
|
schema = ''.join(schema_file.readlines())
|
|
sql_curs.executescript(schema)
|
|
|
|
sql_conn.close()
|
|
|
|
# Add a new network to the aggregator database
|
|
def add_client_network(self, network):
|
|
network_domain = self.d_network[network].domain
|
|
network_gateway = self.d_network[network].ip_gateway
|
|
|
|
self.logger.out(
|
|
'Adding entry for client domain {}'.format(
|
|
network_domain
|
|
),
|
|
prefix='DNS aggregator',
|
|
state='o'
|
|
)
|
|
# Connect to the database
|
|
sql_conn = sqlite3.connect(self.database_file)
|
|
sql_curs = sql_conn.cursor()
|
|
# Try to access the domains entry
|
|
sql_curs.execute(
|
|
'select * from domains where name=?',
|
|
(network_domain,)
|
|
)
|
|
results = sql_curs.fetchall()
|
|
if results:
|
|
write_domain = False
|
|
else:
|
|
write_domain = True
|
|
|
|
if write_domain:
|
|
sql_curs.execute(
|
|
'insert into domains (name, master, type, account) values (?, ?, "SLAVE", "internal")',
|
|
(network_domain, network_gateway)
|
|
)
|
|
sql_conn.commit()
|
|
|
|
sql_conn.close()
|
|
|
|
# Remove a deleted network from the aggregator database
|
|
def remove_client_network(self, network):
|
|
network_domain = self.d_network[network].domain
|
|
|
|
self.logger.out(
|
|
'Removing entry for client domain {}'.format(
|
|
network_domain
|
|
),
|
|
prefix='DNS aggregator',
|
|
state='o'
|
|
)
|
|
# Connect to the database
|
|
sql_conn = sqlite3.connect(self.database_file)
|
|
sql_curs = sql_conn.cursor()
|
|
print(network_domain)
|
|
sql_curs.execute(
|
|
'delete from domains where name=?',
|
|
(network_domain,)
|
|
)
|
|
sql_conn.commit()
|
|
sql_conn.close()
|
|
|
|
# Force AXFR
|
|
def get_axfr(self, network):
|
|
self.logger.out(
|
|
'Perform AXFR for {}'.format(
|
|
self.d_network[network].domain
|
|
),
|
|
prefix='DNS aggregator',
|
|
state='o'
|
|
)
|
|
common.run_os_command('/usr/bin/pdns_control --socket-dir={} retrieve {}'.format(self.config['pdns_dynamic_directory'], self.d_network[network].domain))
|
|
|
|
# Start up the PowerDNS instance
|
|
def start_aggregator(self):
|
|
self.logger.out(
|
|
'Starting PowerDNS zone aggregator',
|
|
state='o'
|
|
)
|
|
# Define the PowerDNS config
|
|
dns_configuration = [
|
|
'--no-config',
|
|
'--daemon=no',
|
|
'--disable-syslog=yes',
|
|
'--disable-axfr=no',
|
|
'--guardian=yes',
|
|
'--local-address=0.0.0.0',
|
|
'--local-port=10053',
|
|
'--log-dns-details=on',
|
|
'--loglevel=3',
|
|
'--master=no',
|
|
'--slave=yes',
|
|
'--slave-cycle-interval=5',
|
|
'--version-string=powerdns',
|
|
'--socket-dir={}'.format(self.config['pdns_dynamic_directory']),
|
|
'--launch=gsqlite3',
|
|
'--gsqlite3-database={}'.format(self.database_file),
|
|
'--gsqlite3-dnssec=no'
|
|
]
|
|
# Start the pdns process in a thread
|
|
self.dns_server_daemon = common.run_os_daemon(
|
|
'/usr/sbin/pdns_server {}'.format(
|
|
' '.join(dns_configuration)
|
|
),
|
|
environment=None,
|
|
logfile='{}/pdns-aggregator.log'.format(self.config['pdns_log_directory'])
|
|
)
|
|
|
|
# Stop the PowerDNS instance
|
|
def stop_aggregator(self):
|
|
if self.dns_server_daemon:
|
|
self.logger.out(
|
|
'Stopping PowerDNS zone aggregator',
|
|
state='o'
|
|
)
|
|
self.dns_server_daemon.signal('term')
|