Recently I came across an issue with Windows DHCP & DNS, specifically related to Cisco AP’s and DDNS. By default Cisco AP’s have period in the hostname (APxxxx.yyyy.zzzz
), and this apparently causes issues for Windows DHCP/DNS regarding DDNS. If you have a scope with option 15 (Domain Name) set to foo.bar
, and you have clients that only returns option 12 (hostname) and no FQDN (option 81) you’d expect Windows to append option 15 to the hostname. In the case for Cisco AP’s, they seem to only return option 12. You’d then expect Windows DHCP to use APxxxx.yyyy.zzzz.foo.bar
as the FQDN for the DDNS update, but this is not the case. In stead, it tries to update the DNS with APxxxx.yyyy.zzzz
as the FQDN (where yyyy.zzzz
is considered a domain due to the period), hence it will obviously fail, as you don’t have any zone yyyy.zzzz
configured in your DNS.
To make things even worse, when Windows tries to do these updates (that fails), a queue fills up with failed attempts. When this queue is full, legit DDNS-updates is discarded (i.e. clients that sends FQDN or similar, that would actually be successfully updated). If you have enough AP’s (as I did in this scenario), this actually becomes a problem, causing normal clients (Windows clients and the like) to have incorrect IP addresses in DNS (their attempt to update with the new IP they recently got just gets discarded as the queue is full).
Microsoft apparently has a workaround to try mitigating the issue, which involves raising the amount of entries it can have in the queue (which involves changing the regedit value of DynamicDNSQueueLength
).
isc-bind and isc-dhcpd behaves as expected with this scenario. It honors the ddns-domainname
option (set globally or per scope), and appends this to the hostname (regardless if the client sends a FQDN). The period would only be considered as “subdomains”, but lets say that the period hypothetically would cause issues, we could easily solve this by just rewriting the hostname on-the-fly by replacing periods with hyphens. In other words; this issue is only with Windows DNS/DHCP.
Since I don’t like Windows at all, I decided that I didn’t want to wait/participate in solving this on the DNS/DHCP-server side, but rather do another approach; rename all the AP’s. Replace period with hyphens. I’ve done AP renames on Cisco WLC earlier, but that was a one-time-thing whacked together, utilizing Net::Telnet
. In this scenario, it would be used in production, running regularly (i.e. several times per hour) to rename newly added AP’s, hence I didn’t want to use this Telnet “hack”, so SNMP was the way to go. Some minutes later, some SNMP-write-magic, and voil?, problem solved. The up-to-date script can always be found here.
#!/usr/bin/env perl
use strict;
use warnings;
use Net::SNMP;
use Net::SNMP::Util;
# wlc-rename-aps-dns.pl
# Written by Joachim Tingvold
#
# This script renames AP's on Cisco WLC. It replaces all periods (.) with hyphens (-) regardless of the AP name.
# It utilizes SNMP-write to achieve this
# Use the script however you want, and modify it any way you want. However I take no responsibility for any damage it might do :-P
# variables
my $community = "secret"; # Read-Write
my $snmpver = "2c";
my $timeout = 3;
my $retries = 0;
my %oids = (
# http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?objectInput=cLApName&translate=Translate&submitValue=SUBMIT&submitClicked=true
# "This object represents the administrative name
# assigned to the AP by the user. If an AP is not configured,
# its factory default name will be ap: of MACAddress> eg. ap:af:12:be."
'cLApName' => '1.3.6.1.4.1.9.9.513.1.1.1.1.5',
);
my %wlcs = (
1 => {
name => "foo",
ip => "10.10.10.10",
},
2 => {
name => "bar",
ip => "10.20.20.20",
},
);
# We only want 1 instance of this script running
# Check if already running -- if so, abort.
unless (flock(DATA, LOCK_EX|LOCK_NB)) {
die("$0 is already running. Exiting.");
}
# iterate through all WLC's
foreach my $wlcid (sort keys %wlcs){
print "Checking AP's on WLC '$wlcs{$wlcid}{name}' ($wlcs{$wlcid}{ip})...\n";
my ($session, $error) = Net::SNMP->session(
Hostname => $wlcs{$wlcid}{ip},
Community => $community,
Version => $snmpver,
Timeout => $timeout,
Retries => $retries,
);
if ($session){
my ($result, $error) = snmpwalk(snmp => $session,
oids => \%oids );
unless(keys %$result){
print "Could not poll $wlcs{$wlcid}{name}: $error\n";
$session->close();
next;
}
foreach my $ap (keys %{$result->{cLApName}}){
my $apname = $result->{cLApName}{$ap};
if ($apname =~ m/\./g){
# if AP have . in the name
(my $newapname = $apname) =~ s/\./-/g;
print "Found AP with '.' in the name ($apname). Renaming to '$newapname'.\n";
my $apoid = $oids{cLApName} . "." . $ap;
my $write_result = $session->set_request(
-varbindlist => [$apoid, OCTET_STRING, $newapname]
);
unless (keys %$write_result){
print "Could not set new AP-name for ap '$apname'.\n";
next;
}
}
}
# close after checking all AP
$session->close();
} else {
print "Could not connect to $wlcs{$wlcid}{name}: $error\n";
$session->close();
next;
}
}
__DATA__
Do not remove. Makes sure flock() code above works as it should.