Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save gregarendse/ba65577e38881efdff721c0b66343f28 to your computer and use it in GitHub Desktop.
Save gregarendse/ba65577e38881efdff721c0b66343f28 to your computer and use it in GitHub Desktop.
DNSmasq to MikroTik DNS Static Converter
#!/usr/bin/env python3
"""
DNSmasq to MikroTik DNS Static Converter
Converts dnsmasq server configurations to MikroTik RouterOS DNS static entries.
Supports various dnsmasq directive formats and generates appropriate MikroTik commands.
"""
import re
import sys
import argparse
from pathlib import Path
from typing import List, Dict, Tuple, Optional
class DNSmasqConverter:
def __init__(self):
self.static_entries = []
self.comments = []
def parse_dnsmasq_line(self, line: str) -> Optional[Dict]:
"""Parse a single dnsmasq configuration line."""
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith('#'):
if line.startswith('#'):
return {'type': 'comment', 'content': line}
return None
# Handle server= directives (DNS forwarding)
# Examples:
# server=/example.com/192.168.1.10
# server=/local/10.0.0.1
# server=/company.internal/192.168.100.53#5353
server_match = re.match(r'server=/(.*?)/(.*?)(?:#(\d+))?$', line)
if server_match:
domain = server_match.group(1)
dns_server = server_match.group(2)
port = server_match.group(3)
# Handle port specification
if port:
dns_server = f"{dns_server}#{port}"
return {
'type': 'server',
'domain': domain,
'dns_server': dns_server,
'original': line
}
# Handle address= directives (static DNS entries)
# Examples:
# address=/doubleclick.net/127.0.0.1
# address=/ads.google.com/0.0.0.0
address_match = re.match(r'address=/(.*?)/(.*?)$', line)
if address_match:
domain = address_match.group(1)
ip_address = address_match.group(2)
return {
'type': 'address',
'domain': domain,
'ip_address': ip_address,
'original': line
}
# Handle host-record= directives
# Example: host-record=router.local,192.168.1.1
host_record_match = re.match(r'host-record=(.*?),(.*?)$', line)
if host_record_match:
hostname = host_record_match.group(1)
ip_address = host_record_match.group(2)
return {
'type': 'host_record',
'domain': hostname,
'ip_address': ip_address,
'original': line
}
# Handle cname= directives
# Example: cname=alias.example.com,target.example.com
cname_match = re.match(r'cname=(.*?),(.*?)$', line)
if cname_match:
alias = cname_match.group(1)
target = cname_match.group(2)
return {
'type': 'cname',
'alias': alias,
'target': target,
'original': line
}
# Return unparsed line for reference
return {
'type': 'unparsed',
'content': line,
'original': line
}
def convert_to_mikrotik(self, entry: Dict) -> str:
"""Convert a parsed dnsmasq entry to MikroTik format."""
if entry['type'] == 'comment':
return f"# {entry['content'][1:].strip()}"
elif entry['type'] == 'server':
domain = entry['domain']
dns_server = entry['dns_server']
# Handle wildcard domains
if not domain.startswith('*.'):
domain = f"*.{domain}"
return f'/ip dns static add name={domain} type=FWD address={dns_server} comment="Forward {entry["domain"]} to {dns_server}"'
elif entry['type'] == 'address':
domain = entry['domain']
ip_address = entry['ip_address']
# Handle wildcard domains
if not domain.startswith('*.'):
domain = f"*.{domain}"
# Check if this is a blocking entry (0.0.0.0 or 127.0.0.1)
if ip_address in ['0.0.0.0', '127.0.0.1']:
return f'/ip dns static add name={domain} address={ip_address} comment="Block {entry["domain"]}"'
else:
return f'/ip dns static add name={domain} address={ip_address} comment="Static entry for {entry["domain"]}"'
elif entry['type'] == 'host_record':
hostname = entry['domain']
ip_address = entry['ip_address']
return f'/ip dns static add name={hostname} address={ip_address} comment="Host record for {hostname}"'
elif entry['type'] == 'cname':
alias = entry['alias']
target = entry['target']
return f'/ip dns static add name={alias} type=CNAME cname={target} comment="CNAME alias {alias} -> {target}"'
elif entry['type'] == 'unparsed':
return f"# UNPARSED: {entry['content']}"
return f"# UNKNOWN ENTRY TYPE: {entry}"
def process_file(self, input_file: str) -> List[str]:
"""Process a dnsmasq configuration file."""
mikrotik_commands = []
try:
with open(input_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
except FileNotFoundError:
print(f"Error: File '{input_file}' not found.")
return []
except Exception as e:
print(f"Error reading file: {e}")
return []
mikrotik_commands.append("# Converted from dnsmasq configuration")
mikrotik_commands.append(f"# Source file: {input_file}")
mikrotik_commands.append("")
for line_num, line in enumerate(lines, 1):
try:
parsed = self.parse_dnsmasq_line(line)
if parsed:
mikrotik_cmd = self.convert_to_mikrotik(parsed)
mikrotik_commands.append(mikrotik_cmd)
except Exception as e:
mikrotik_commands.append(f"# ERROR processing line {line_num}: {line.strip()} - {e}")
return mikrotik_commands
def process_text(self, dnsmasq_text: str) -> List[str]:
"""Process dnsmasq configuration from text string."""
lines = dnsmasq_text.strip().split('\n')
mikrotik_commands = []
mikrotik_commands.append("# Converted from dnsmasq configuration")
mikrotik_commands.append("")
for line_num, line in enumerate(lines, 1):
try:
parsed = self.parse_dnsmasq_line(line)
if parsed:
mikrotik_cmd = self.convert_to_mikrotik(parsed)
mikrotik_commands.append(mikrotik_cmd)
except Exception as e:
mikrotik_commands.append(f"# ERROR processing line {line_num}: {line.strip()} - {e}")
return mikrotik_commands
def main():
parser = argparse.ArgumentParser(
description='Convert dnsmasq server configuration to MikroTik DNS static entries',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s -f /etc/dnsmasq.conf -o mikrotik_dns.rsc
%(prog)s -f dnsmasq.conf
%(prog)s -t "server=/local/192.168.1.1\\naddress=/ads.com/0.0.0.0"
Supported dnsmasq directives:
server=/domain/dns_server -> DNS forwarding
address=/domain/ip_address -> Static DNS entries
host-record=hostname,ip -> Host records
cname=alias,target -> CNAME records
"""
)
parser.add_argument('-f', '--file', help='Input dnsmasq configuration file')
parser.add_argument('-t', '--text', help='Dnsmasq configuration as text string')
parser.add_argument('-o', '--output', help='Output file for MikroTik commands')
parser.add_argument('--no-comments', action='store_true', help='Skip comment lines in output')
args = parser.parse_args()
converter = DNSmasqConverter()
if args.file:
commands = converter.process_file(args.file)
elif args.text:
commands = converter.process_text(args.text)
else:
# Interactive mode
print("Enter dnsmasq configuration (press Ctrl+D when done):")
try:
dnsmasq_text = sys.stdin.read()
commands = converter.process_text(dnsmasq_text)
except KeyboardInterrupt:
print("\nOperation cancelled.")
return
# Filter out comments if requested
if args.no_comments:
commands = [cmd for cmd in commands if not cmd.strip().startswith('#')]
# Output results
if args.output:
try:
with open(args.output, 'w', encoding='utf-8') as f:
for cmd in commands:
f.write(cmd + '\n')
print(f"MikroTik commands written to: {args.output}")
except Exception as e:
print(f"Error writing output file: {e}")
return
else:
for cmd in commands:
print(cmd)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment