rm -f $(TARGETS) $(OBJ) | rm -f $(TARGETS) $(OBJ) | ||||
install: | install: | ||||
systemctl stop dhcp_protect | |||||
systemctl stop dhcp_protect || true | |||||
cp dhcp_protect /usr/local/bin/ | cp dhcp_protect /usr/local/bin/ | ||||
cp dhcp_protect.conf /usr/local/etc/ | cp dhcp_protect.conf /usr/local/etc/ | ||||
cp dhcp_protect.service /etc/systemd/system/ | cp dhcp_protect.service /etc/systemd/system/ | ||||
systemctl start dhcp_protect | systemctl start dhcp_protect | ||||
uninstall: | uninstall: | ||||
systemctl stop dhcp_protect | |||||
systemctl disable dhcp_protect | |||||
systemctl stop dhcp_protect || true | |||||
systemctl disable dhcp_protect || true | |||||
rm -f /usr/local/bin/dhcp_protect | rm -f /usr/local/bin/dhcp_protect | ||||
rm -f /usr/local/etc/dhcp_protect.conf | rm -f /usr/local/etc/dhcp_protect.conf | ||||
rm -f /etc/systemd/system/dhcp_protect.service | rm -f /etc/systemd/system/dhcp_protect.service |
configfile = argv[1]; | configfile = argv[1]; | ||||
} | } | ||||
else { | else { | ||||
usage(argv[0]); | |||||
dp_usage(argv[0]); | |||||
return EXIT_FAILURE; | return EXIT_FAILURE; | ||||
} | } | ||||
if ( load_config(&conf, configfile) == NULL ) | |||||
if ( dp_load_config(&conf, configfile) == NULL ) | |||||
return EXIT_FAILURE; | return EXIT_FAILURE; | ||||
openlog("dhcp_protect", LOG_PID, LOG_DAEMON); | openlog("dhcp_protect", LOG_PID, LOG_DAEMON); | ||||
nfq_start(&conf); | |||||
dp_nfq_start(&conf); | |||||
return 0; | return 0; | ||||
} | } | ||||
} | } | ||||
// start netfilter queue | // start netfilter queue | ||||
void nfq_start(dp_conf *conf) { | |||||
void dp_nfq_start(dp_conf *conf) { | |||||
struct nfq_handle *h; | struct nfq_handle *h; | ||||
struct nfq_q_handle *qh; | struct nfq_q_handle *qh; | ||||
int fd; | int fd; | ||||
} | } | ||||
// display usage | // display usage | ||||
void usage(char *prog) { | |||||
void dp_usage(char *prog) { | |||||
fprintf(stderr,"Usage: %s <configuration file>\n",prog); | fprintf(stderr,"Usage: %s <configuration file>\n",prog); | ||||
} | } | ||||
// load configuration file | // load configuration file | ||||
dp_conf *load_config(dp_conf *conf, char *file) { | |||||
dp_conf *dp_load_config(dp_conf *conf, char *file) { | |||||
FILE *fh; | FILE *fh; | ||||
char *line = NULL; | char *line = NULL; | ||||
size_t len = 0; | size_t len = 0; | ||||
} | } | ||||
// decode dhcp packet | // decode dhcp packet | ||||
int dhcp_check(struct nfq_data *nfa, dp_conf *conf) { | |||||
int dp_dhcp_check(struct nfq_data *nfa, dp_conf *conf) { | |||||
unsigned char *pkt; | unsigned char *pkt; | ||||
int pktlen; | int pktlen; | ||||
int offset = 0; | int offset = 0; | ||||
unsigned char *remoteid = NULL; | unsigned char *remoteid = NULL; | ||||
int remoteidlen = 0; | int remoteidlen = 0; | ||||
int found = 0; | |||||
uint8_t ipver = 0; | |||||
uint8_t ihl = 0; | |||||
//int i; | |||||
int rv = NF_ACCEPT; | int rv = NF_ACCEPT; | ||||
pktlen = nfq_get_payload(nfa, &pkt); | pktlen = nfq_get_payload(nfa, &pkt); | ||||
// can we read the IP proto and IP header length ? | // can we read the IP proto and IP header length ? | ||||
if ( pktlen > 0 ) { | if ( pktlen > 0 ) { | ||||
uint8_t ipver; | |||||
uint8_t ihl; | |||||
ipver = pkt[offset]; | ipver = pkt[offset]; | ||||
ipver >>= 4; | ipver >>= 4; | ||||
ihl = pkt[offset]; | ihl = pkt[offset]; | ||||
if ( ipver == 4 ) { | if ( ipver == 4 ) { | ||||
// jump to DHCPv4 | // jump to DHCPv4 | ||||
offset += ( ihl * 4 ) + 8; | offset += ( ihl * 4 ) + 8; | ||||
dhcpv4_check(conf, pkt, pktlen, offset, &remoteid, &remoteidlen); | |||||
dp_dhcpv4_check(conf, pkt, pktlen, offset, &remoteid, &remoteidlen); | |||||
} | } | ||||
else if ( ipver == 6 ) { | else if ( ipver == 6 ) { | ||||
// jump to DHCPv6 | // jump to DHCPv6 | ||||
offset += 48 + 8; | |||||
dhcpv6_check(conf, pkt, pktlen, offset, &remoteid, &remoteidlen); | |||||
offset += 40 + 8; | |||||
dp_dhcpv6_check(conf, pkt, pktlen, offset, &remoteid, &remoteidlen); | |||||
} | } | ||||
else { | else { | ||||
if ( conf->debug ) printf("not an IPv4 packet\n"); | if ( conf->debug ) printf("not an IPv4 packet\n"); | ||||
} | } | ||||
if ( remoteidlen>0 ) { | if ( remoteidlen>0 ) { | ||||
if ( conf->debug ) { | |||||
int i; | |||||
printf("remoteid: "); | |||||
for(i=0; i<remoteidlen; i++) | |||||
printf("%02x", remoteid[i]); | |||||
printf("\n"); | |||||
} | |||||
// count the packet, even when blacklisted. | // count the packet, even when blacklisted. | ||||
dp_accounting_add(conf, remoteid, remoteidlen); | dp_accounting_add(conf, remoteid, remoteidlen); | ||||
return rv; | return rv; | ||||
} | } | ||||
void dhcpv6_check(dp_conf *conf, unsigned char *pkt, int pktlen, int offset, unsigned char *remoteid, int *remoteidlen) { | |||||
void dp_dhcpv6_check(dp_conf *conf, unsigned char *pkt, int pktlen, int offset, unsigned char **remoteid, int *remoteidlen) { | |||||
while(offset<pktlen) { | while(offset<pktlen) { | ||||
uint8_t msgtype = (uint8_t)pkt[offset]; | uint8_t msgtype = (uint8_t)pkt[offset]; | ||||
uint8_t code = (uint8_t)pkt[offset]; | uint8_t code = (uint8_t)pkt[offset]; | ||||
uint8_t len = (uint8_t)pkt[offset+1]; | uint8_t len = (uint8_t)pkt[offset+1]; | ||||
offset+2; | |||||
offset+=2; | |||||
if ( code == 1 ) { // Client identifier / DUID | if ( code == 1 ) { // Client identifier / DUID | ||||
remoteid = pkt+offset; | |||||
*remoteidlen = len; | |||||
break; | |||||
// make sure there's enough space | |||||
if ( offset + len <= pktlen ) { | |||||
*remoteid = pkt+offset; | |||||
*remoteidlen = len; | |||||
break; | |||||
} | |||||
} | } | ||||
offset+=len; | offset+=len; | ||||
} | } | ||||
} | } | ||||
void dhcpv4_check(dp_conf *conf, unsigned char *pkt, int pktlen, int offset, unsigned char *remoteid, int *remoteidlen) { | |||||
void dp_dhcpv4_check(dp_conf *conf, unsigned char *pkt, int pktlen, int offset, unsigned char **remoteid, int *remoteidlen) { | |||||
int hwaddrpos = offset + 28; // remember where the hw addr is if we need to fallback to it | int hwaddrpos = offset + 28; // remember where the hw addr is if we need to fallback to it | ||||
int hwlenpos = offset + 2; // remember where the hw addr len is if we need to fallback to it | int hwlenpos = offset + 2; // remember where the hw addr len is if we need to fallback to it | ||||
// parse TLV options | // parse TLV options | ||||
while(offset<pktlen && !found) { | |||||
while(offset<pktlen && remoteidlen==0) { | |||||
uint8_t type = pkt[offset]; | uint8_t type = pkt[offset]; | ||||
uint8_t len; | uint8_t len; | ||||
int o82off = 0; | int o82off = 0; | ||||
// loop until the end, +2 to ensure we can read type and length | // loop until the end, +2 to ensure we can read type and length | ||||
while(o82off+2<len && !found) { | |||||
while(o82off+2<len && remoteidlen==0) { | |||||
uint8_t otype = o82[o82off]; | uint8_t otype = o82[o82off]; | ||||
uint8_t olen = o82[o82off+1]; | uint8_t olen = o82[o82off+1]; | ||||
o82off+=2; | o82off+=2; | ||||
break; | break; | ||||
} | } | ||||
else { | else { | ||||
remoteid = o82 + o82off; | |||||
*remoteid = o82 + o82off; | |||||
*remoteidlen = olen; | *remoteidlen = olen; | ||||
} | } | ||||
} | } | ||||
if ( *remoteidlen == 0 ) { | if ( *remoteidlen == 0 ) { | ||||
// nope, we won't overflow | // nope, we won't overflow | ||||
if ( (uint8_t)pkt[hwlenpos] <= 16 ) { | if ( (uint8_t)pkt[hwlenpos] <= 16 ) { | ||||
remoteid = pkt+hwaddrpos; | |||||
remoteidlen = (uint8_t)pkt[hwlenpos]; | |||||
*remoteid = pkt+hwaddrpos; | |||||
*remoteidlen = (uint8_t)pkt[hwlenpos]; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
if ( conf->debug ) printf ("received packet with id %d\n", id); | if ( conf->debug ) printf ("received packet with id %d\n", id); | ||||
} | } | ||||
verdict = dhcp_check(nfa, conf); /* Treat packet */ | |||||
verdict = dp_dhcp_check(nfa, conf); /* Treat packet */ | |||||
// override decision for dryrun | // override decision for dryrun | ||||
if ( conf->dryrun ) | if ( conf->dryrun ) |
struct nfq_data*, | struct nfq_data*, | ||||
void*); | void*); | ||||
void usage (char*); | |||||
int dhcp_check (struct nfq_data*, dp_conf*); | |||||
dp_conf *load_config (dp_conf*, char*); | |||||
void nfq_start (dp_conf*); | |||||
void dp_usage (char*); | |||||
dp_conf *dp_load_config (dp_conf*, char*); | |||||
void dp_nfq_start (dp_conf*); | |||||
void dp_accounting_add (dp_conf*, unsigned char *, int); | void dp_accounting_add (dp_conf*, unsigned char *, int); | ||||
int dp_accounting_check (dp_conf*, unsigned char *, int); | int dp_accounting_check (dp_conf*, unsigned char *, int); | ||||
void dp_blacklist_add (dp_conf*, unsigned char *, int); | void dp_blacklist_add (dp_conf*, unsigned char *, int); | ||||
int dp_blacklist_check (dp_conf*, unsigned char *, int); | int dp_blacklist_check (dp_conf*, unsigned char *, int); | ||||
void dp_hash_cleanup (dp_conf*); | void dp_hash_cleanup (dp_conf*); | ||||
void dp_log (unsigned char *, int, char *, ...); | void dp_log (unsigned char *, int, char *, ...); | ||||
int dp_dhcp_check (struct nfq_data*, dp_conf*); | |||||
void dp_dhcpv4_check ( | |||||
dp_conf*, | |||||
unsigned char*, | |||||
int, | |||||
int, | |||||
unsigned char**, | |||||
int*); | |||||
void dp_dhcpv6_check ( | |||||
dp_conf*, | |||||
unsigned char*, | |||||
int, | |||||
int, | |||||
unsigned char**, | |||||
int*); |
use strict; | use strict; | ||||
use IO::Socket::INET; | use IO::Socket::INET; | ||||
use IO::Socket::INET6; | |||||
use Time::HiRes qw(sleep); | use Time::HiRes qw(sleep); | ||||
use warnings; | use warnings; | ||||
exit 1; | exit 1; | ||||
} | } | ||||
if ( $dest !~ /^(\d+\.\d+\.\d+\.\d+)$/ ) { | |||||
print STDERR "destination IP must be a valid IPv4 address\n"; | |||||
if ( $dest !~ /^(\d+\.\d+\.\d+\.\d+)$/ && $dest !~ /^[0-9a-fA-F:]+$/ ) { | |||||
print STDERR "destination IP must be a valid IPv4/IPv6 address\n"; | |||||
exit 1; | exit 1; | ||||
} | } | ||||
sub main { | sub main { | ||||
my($clients, $leasetime, $floodclients, $floodpps, $dest, $destport)=@_; | my($clients, $leasetime, $floodclients, $floodpps, $dest, $destport)=@_; | ||||
# pseudo packet, the only really relevant thing | |||||
# is that option 82 is present and at the right place | |||||
my $hdr = ""; | my $hdr = ""; | ||||
my $sock; | |||||
if ( $dest =~ /:/ ) { # IPv6 | |||||
$sock = IO::Socket::INET6->new( | |||||
Proto => 'udp', | |||||
PeerAddr => $dest, | |||||
PeerPort => $destport, | |||||
); | |||||
$hdr .= pack("H2", "01"); # msg-type | |||||
$hdr .= pack("H6", "000000"); # transaction-id | |||||
$hdr .= pack("C", 1); # DUID option | |||||
$hdr .= pack("C", 6); # DUID length | |||||
} | |||||
else { # IPv4 | |||||
$sock = IO::Socket::INET->new( | |||||
Proto => 'udp', | |||||
PeerAddr => "$dest:$destport", | |||||
); | |||||
# pseudo packet, the only really relevant thing | |||||
# is that option 82 is present and at the right place | |||||
$hdr .= pack("H2","01"); # Opcode, bootrequest | |||||
$hdr .= pack("H2","01"); # Hardware type, ethernet | |||||
$hdr .= pack("H2","06"); # HW addr len, MAC = 6 | |||||
$hdr .= pack("H2","01"); # hops, 1 relay | |||||
$hdr .= pack("H8","00000000"); # transaction ID | |||||
$hdr .= pack("H4","0000"); # secs | |||||
$hdr .= pack("H4","0000"); # flags | |||||
$hdr .= pack("H8","00000000"); # ciaddr | |||||
$hdr .= pack("H8","00000000"); # yiaddr | |||||
$hdr .= pack("H8","00000000"); # siaddr | |||||
$hdr .= pack("H8","00000000"); # giaddr | |||||
$hdr .= pack("H32","0"x32); # cihwaddr | |||||
$hdr .= pack("H128","0"x128); # hostname | |||||
$hdr .= pack("H256","0"x256); # boot filename | |||||
$hdr .= pack("H8","63825363"); # magic cookie | |||||
$hdr .= pack("C", 82); # opt 82 | |||||
$hdr .= pack("C", 21); # opt 82 len | |||||
$hdr .= pack("C", 1); # subopt 1 (circuitid) | |||||
$hdr .= pack("C", 11); # subopt 1 len | |||||
$hdr .= "Hello World"; | |||||
$hdr .= pack("C", 2); # subopt 2 (remoteid) | |||||
$hdr .= pack("C", 6); # subopt 2 len (mac) | |||||
} | |||||
$hdr .= pack("H2","01"); # Opcode, bootrequest | |||||
$hdr .= pack("H2","01"); # Hardware type, ethernet | |||||
$hdr .= pack("H2","06"); # HW addr len, MAC = 6 | |||||
$hdr .= pack("H2","01"); # hops, 1 relay | |||||
$hdr .= pack("H8","00000000"); # transaction ID | |||||
$hdr .= pack("H4","0000"); # secs | |||||
$hdr .= pack("H4","0000"); # flags | |||||
$hdr .= pack("H8","00000000"); # ciaddr | |||||
$hdr .= pack("H8","00000000"); # yiaddr | |||||
$hdr .= pack("H8","00000000"); # siaddr | |||||
$hdr .= pack("H8","00000000"); # giaddr | |||||
$hdr .= pack("H32","0"x32); # cihwaddr | |||||
$hdr .= pack("H128","0"x128); # hostname | |||||
$hdr .= pack("H256","0"x256); # boot filename | |||||
$hdr .= pack("H8","63825363"); # magic cookie | |||||
$hdr .= pack("C", 82); # opt 82 | |||||
$hdr .= pack("C", 21); # opt 82 len | |||||
$hdr .= pack("C", 1); # subopt 1 (circuitid) | |||||
$hdr .= pack("C", 11); # subopt 1 len | |||||
$hdr .= "Hello World"; | |||||
$hdr .= pack("C", 2); # subopt 2 (remoteid) | |||||
$hdr .= pack("C", 6); # subopt 2 len (mac) | |||||
if ( $floodclients > 0 ) { | if ( $floodclients > 0 ) { | ||||
if ( !$pid ) { | if ( !$pid ) { | ||||
my $int = 1/$floodpps/$floodclients; | my $int = 1/$floodpps/$floodclients; | ||||
my $sock = IO::Socket::INET->new( | |||||
Proto => 'udp', | |||||
PeerAddr => "$dest:$destport", | |||||
); | |||||
while(1) { | while(1) { | ||||
for(my $i=0; $i<$floodclients; $i++) { | for(my $i=0; $i<$floodclients; $i++) { | ||||
} | } | ||||
my $int = $leasetime/3/$clients; | my $int = $leasetime/3/$clients; | ||||
my $sock = IO::Socket::INET->new( | |||||
Proto => 'udp', | |||||
PeerAddr => "$dest:$destport", | |||||
); | |||||
while(1) { | while(1) { | ||||
for(my $i=0; $i<$clients; $i++) { | for(my $i=0; $i<$clients; $i++) { |