A userspace application that filters DHCP floods to protect a DHCP server. It uses the Netfilter userspace packet queuing API.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

dhcp_protect.c 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. // DHCP Protect
  2. //
  3. // Copyright 2019 Pascal Gloor
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <unistd.h>
  20. #include <stdint.h>
  21. #include <errno.h>
  22. #include <time.h>
  23. #include <stdarg.h>
  24. #include <syslog.h>
  25. #include <arpa/inet.h>
  26. #include <linux/netfilter.h>
  27. #include <libnetfilter_queue/libnetfilter_queue.h>
  28. #include <uthash.h>
  29. #include "dhcp_protect.h"
  30. // main function
  31. int main(int argc, char **argv) {
  32. char *configfile;
  33. dp_conf conf;
  34. if ( argc == 2 ) {
  35. configfile = argv[1];
  36. }
  37. else {
  38. dp_usage(argv[0]);
  39. return EXIT_FAILURE;
  40. }
  41. if ( dp_load_config(&conf, configfile) == NULL )
  42. return EXIT_FAILURE;
  43. openlog("dhcp_protect", LOG_PID, LOG_DAEMON);
  44. dp_nfq_start(&conf);
  45. return 0;
  46. }
  47. // syslog function
  48. void dp_log(unsigned char *remoteid, int remoteidlen, char *fmt, ...) {
  49. va_list argList;
  50. char buf[1000];
  51. int offset = remoteidlen*2;
  52. int i;
  53. for(i=0; i<remoteidlen; i++) {
  54. sprintf(buf+(i*2), "%02x", remoteid[i]);
  55. }
  56. buf[offset]=':'; offset++;
  57. buf[offset]=' '; offset++;
  58. va_start(argList, fmt);
  59. vsnprintf(buf+offset, sizeof(buf)-offset-1, fmt, argList);
  60. va_end(argList);
  61. syslog(LOG_DAEMON|LOG_INFO, "%s", buf);
  62. }
  63. // start netfilter queue
  64. void dp_nfq_start(dp_conf *conf) {
  65. struct nfq_handle *h;
  66. struct nfq_q_handle *qh;
  67. int fd;
  68. if ( ( h = nfq_open() ) == NULL ) {
  69. fprintf(stderr,"error during nfq_open() %s\n", strerror(errno));
  70. return;
  71. }
  72. if ( ( qh = nfq_create_queue(h, conf->queue, &dp_callback, conf) ) == NULL ) {
  73. fprintf(stderr, "error during nfq_create_queue() %s\n", strerror(errno));
  74. return;
  75. }
  76. if ( nfq_set_mode(qh, NFQNL_COPY_PACKET, 1500) < 0 ) {
  77. fprintf(stderr,"error during nfq_set_mode() %s\n", strerror(errno));
  78. return;
  79. }
  80. if ( nfq_set_queue_flags(qh, NFQA_CFG_F_FAIL_OPEN, NFQA_CFG_F_FAIL_OPEN) < 0 ) {
  81. fprintf(stderr,"error during nfq_set_queue_flags() %s\n", strerror(errno));
  82. return;
  83. }
  84. fd = nfq_fd(h);
  85. while(1) {
  86. static char buf[65536];
  87. int rv;
  88. rv = recv(fd, buf, sizeof(buf), 0);
  89. switch(rv) {
  90. case -1:
  91. fprintf(stderr, "recv() error: %s\n", strerror(errno));
  92. return;
  93. case 0:
  94. fprintf(stderr,"socket is closed!?\n");
  95. return;
  96. default:
  97. // send packet to callback
  98. nfq_handle_packet(h, buf, rv);
  99. break;
  100. }
  101. }
  102. }
  103. // display usage
  104. void dp_usage(char *prog) {
  105. fprintf(stderr,"Usage: %s <configuration file>\n",prog);
  106. }
  107. // load configuration file
  108. dp_conf *dp_load_config(dp_conf *conf, char *file) {
  109. FILE *fh;
  110. char *line = NULL;
  111. size_t len = 0;
  112. int error = 0;
  113. printf("Loading configuration %s\n",file);
  114. if ( ( fh = fopen(file,"r") ) == NULL ) {
  115. fprintf(stderr,"Failed to open configuration file '%s': %s\n", file, strerror(errno));
  116. return NULL;
  117. }
  118. while (getline(&line, &len, fh) != -1) {
  119. char *name, *value;
  120. name = strtok(line, "=\r\n ");
  121. value = strtok(NULL,"=\r\n ");
  122. if ( name == NULL || value == NULL || name[0] == '#' )
  123. continue;
  124. if ( strcmp(name,"max_pkt_per_interval")==0 )
  125. conf->pktint = atoi(value);
  126. else if ( strcmp(name,"interval")==0 )
  127. conf->interval = atoi(value);
  128. else if ( strcmp(name, "debug")==0 )
  129. conf->debug = atoi(value) ? 1 : 0;
  130. else if ( strcmp(name, "blacklist_time")==0 )
  131. conf->bltime = atoi(value);
  132. else if ( strcmp(name, "queue")==0 )
  133. conf->queue = atoi(value);
  134. else if ( strcmp(name, "dryrun")==0 )
  135. conf->dryrun = atoi(value) ? 1 : 0;
  136. else
  137. fprintf(stderr,"unknown directive '%s', ignored\n", name);
  138. free(line);
  139. }
  140. fclose(fh);
  141. if ( conf->pktint < 1 || conf->pktint > 1000 ) {
  142. fprintf(stderr,"max_pkt_per_interval value invalid (min 1, max 1000)\n");
  143. error=1;
  144. }
  145. if ( conf->interval < 5 || conf->interval > 900 ) {
  146. fprintf(stderr,"interval value invalid (min 5, max 900)\n");
  147. error=1;
  148. }
  149. if ( conf->debug < 0 || conf->debug > 1 ) {
  150. fprintf(stderr,"debug value invalid (0 or 1)\n");
  151. error=1;
  152. }
  153. if ( conf->bltime < 10 || conf->bltime > 900 ) {
  154. fprintf(stderr,"blacklist_time value invalid (min 10, max 900)\n");
  155. error=1;
  156. }
  157. if ( conf->queue < 0 ) {
  158. fprintf(stderr,"queue must be a positive integer\n");
  159. error=1;
  160. }
  161. if ( conf->dryrun < 0 || conf->dryrun > 1 ) {
  162. fprintf(stderr, "dryrun value invalid (0 or 1)\n");
  163. error=1;
  164. }
  165. if ( error )
  166. return NULL;
  167. printf("Configuration:\n");
  168. printf("\t%-20s = %4s\n", "dryrun", conf->dryrun ? "Yes" : "No");
  169. printf("\t%-20s = %4s\n", "debug", conf->debug ? "Yes" : "No" );
  170. printf("\t%-20s = %4is\n", "interval", conf->interval);
  171. printf("\t%-20s = %4i\n", "max_pkt_per_interval", conf->pktint);
  172. printf("\t%-20s = %4is\n", "blacklist_time", conf->bltime);
  173. printf("\t%-20s = %4i\n", "queue", conf->queue);
  174. return conf;
  175. }
  176. // decode dhcp packet
  177. int dp_dhcp_check(struct nfq_data *nfa, dp_conf *conf) {
  178. unsigned char *pkt;
  179. int pktlen;
  180. int offset = 0;
  181. unsigned char *remoteid = NULL;
  182. int remoteidlen = 0;
  183. int rv = NF_ACCEPT;
  184. pktlen = nfq_get_payload(nfa, &pkt);
  185. if ( conf->debug ) printf("got a packet, len = %i\n", pktlen);
  186. // can we read the IP proto and IP header length ?
  187. if ( pktlen > 0 ) {
  188. uint8_t ipver;
  189. uint8_t ihl;
  190. ipver = pkt[offset];
  191. ipver >>= 4;
  192. ihl = pkt[offset];
  193. ihl &= 0x0f;
  194. if ( ipver == 4 ) {
  195. // jump to DHCPv4
  196. offset += ( ihl * 4 ) + 8;
  197. dp_dhcpv4_check(conf, pkt, pktlen, offset, &remoteid, &remoteidlen);
  198. }
  199. else if ( ipver == 6 ) {
  200. // jump to DHCPv6
  201. offset += 40 + 8;
  202. dp_dhcpv6_check(conf, pkt, pktlen, offset, &remoteid, &remoteidlen);
  203. }
  204. else {
  205. if ( conf->debug ) printf("not an IPv4 packet\n");
  206. goto end;
  207. }
  208. }
  209. if ( remoteidlen>0 ) {
  210. if ( conf->debug ) {
  211. int i;
  212. printf("remoteid: ");
  213. for(i=0; i<remoteidlen; i++)
  214. printf("%02x", remoteid[i]);
  215. printf("\n");
  216. }
  217. // count the packet, even when blacklisted.
  218. dp_accounting_add(conf, remoteid, remoteidlen);
  219. // check if already in the blacklist
  220. if ( dp_blacklist_check(conf, remoteid, remoteidlen) == NF_DROP )
  221. rv = NF_DROP;
  222. // check if it must be added to the blacklist
  223. else if ( dp_accounting_check(conf, remoteid, remoteidlen) == NF_DROP ) {
  224. dp_blacklist_add(conf, remoteid, remoteidlen);
  225. rv = NF_DROP;
  226. }
  227. }
  228. end:
  229. dp_hash_cleanup(conf);
  230. return rv;
  231. }
  232. void dp_dhcpv6_check(dp_conf *conf, unsigned char *pkt, int pktlen, int offset, unsigned char **remoteid, int *remoteidlen) {
  233. while(offset<pktlen) {
  234. uint8_t msgtype = (uint8_t)pkt[offset];
  235. switch(msgtype) {
  236. case 11: // RELAY-FORW
  237. case 12: // RELAY-REPL
  238. offset += 2 + 16 + 16; // msg-type, hop-count, link-addr, peer-addr
  239. break;
  240. default: // all other msgtypes
  241. offset += 2; // msg-type, hop-count
  242. }
  243. while(offset+1<pktlen) {
  244. uint8_t code = (uint8_t)pkt[offset];
  245. uint8_t len = (uint8_t)pkt[offset+1];
  246. offset+=2;
  247. if ( code == 1 ) { // Client identifier / DUID
  248. // make sure there's enough space
  249. if ( offset + len <= pktlen ) {
  250. *remoteid = pkt+offset;
  251. *remoteidlen = len;
  252. break;
  253. }
  254. }
  255. offset+=len;
  256. }
  257. if ( *remoteidlen>0 )
  258. break;
  259. }
  260. }
  261. void dp_dhcpv4_check(dp_conf *conf, unsigned char *pkt, int pktlen, int offset, unsigned char **remoteid, int *remoteidlen) {
  262. int hwaddrpos = offset + 28; // remember where the hw addr is if we need to fallback to it
  263. int hwlenpos = offset + 2; // remember where the hw addr len is if we need to fallback to it
  264. // jump DHCP header
  265. offset += 28 + 16 + 64 + 128;
  266. // minimum packet size, fixed header + magic cookie (4 octets)
  267. if ( pktlen < offset + 4 ) {
  268. if ( conf->debug ) printf("packet too small\n");
  269. return;
  270. }
  271. // check magic cookie
  272. if ( pkt[offset] != 99 || pkt[offset+1] != 130 || pkt[offset+2] != 83 || pkt[offset+3] != 99 ) {
  273. if ( conf->debug )
  274. printf(
  275. "invalid magic cookie %02x%02x%02x%02x\n",
  276. pkt[offset], pkt[offset+1],
  277. pkt[offset+2], pkt[offset+3]);
  278. return;
  279. }
  280. offset+=4;
  281. // parse TLV options
  282. while(offset<pktlen && remoteidlen==0) {
  283. uint8_t type = pkt[offset];
  284. uint8_t len;
  285. offset++;
  286. // padding of 1 octet
  287. if ( type == 0 ) {
  288. continue;
  289. }
  290. // end of options
  291. if ( type == 255 )
  292. break;
  293. // make sure we can read len
  294. if ( offset>=pktlen )
  295. break;
  296. len = pkt[offset];
  297. offset++;
  298. // can the value be read
  299. if ( offset+len>pktlen )
  300. break;
  301. // option 82 parser
  302. if ( type == 82 ) {
  303. unsigned char *o82 = pkt+offset;
  304. int o82off = 0;
  305. // loop until the end, +2 to ensure we can read type and length
  306. while(o82off+2<len && remoteidlen==0) {
  307. uint8_t otype = o82[o82off];
  308. uint8_t olen = o82[o82off+1];
  309. o82off+=2;
  310. // printf("o82 type=%i len=%i\n", otype, olen);
  311. // remoteid
  312. if ( otype == 2 ) {
  313. // make sure we don't overflow and can read all data
  314. if ( o82off + olen > len ) {
  315. if ( conf->debug) printf("option 82.2 data too long\n");
  316. break;
  317. }
  318. else {
  319. *remoteid = o82 + o82off;
  320. *remoteidlen = olen;
  321. }
  322. }
  323. o82off+=olen;
  324. }
  325. }
  326. offset+=len;
  327. }
  328. // if we didn't find opt82.2, we fallback to the hw addr
  329. if ( *remoteidlen == 0 ) {
  330. // nope, we won't overflow
  331. if ( (uint8_t)pkt[hwlenpos] <= 16 ) {
  332. *remoteid = pkt+hwaddrpos;
  333. *remoteidlen = (uint8_t)pkt[hwlenpos];
  334. }
  335. }
  336. }
  337. // netfilter queue callback
  338. static int dp_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) {
  339. struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr(nfa);
  340. dp_conf *conf = (dp_conf*)data;
  341. int id = -1;
  342. int verdict;
  343. if ( ph ) {
  344. id = ntohl (ph->packet_id);
  345. if ( conf->debug ) printf ("received packet with id %d\n", id);
  346. }
  347. verdict = dp_dhcp_check(nfa, conf); /* Treat packet */
  348. // override decision for dryrun
  349. if ( conf->dryrun )
  350. verdict = NF_ACCEPT;
  351. return nfq_set_verdict(qh, id, verdict, 0, NULL); /* Verdict packet */
  352. }
  353. void dp_accounting_add(dp_conf *conf, unsigned char *remoteid, int len) {
  354. //int i;
  355. dp_accounting *ac;
  356. // does the element already exist
  357. HASH_FIND(hh, accountings, remoteid, len, ac);
  358. if ( conf->debug ) printf("AC: add item\n");
  359. // found it, increment the counter
  360. if ( ac ) {
  361. if ( conf->debug ) printf("AC: item found, incrementing\n");
  362. ac->count++;
  363. }
  364. // not found, create a new one
  365. else {
  366. if ( conf->debug ) printf("AC: item not found, creating\n");
  367. ac = malloc(sizeof(dp_accounting));
  368. memcpy(ac->remoteid, remoteid, len);
  369. ac->len = len;
  370. ac->count = 1;
  371. HASH_ADD(hh, accountings, remoteid, len, ac);
  372. }
  373. }
  374. void dp_blacklist_add(dp_conf *conf, unsigned char *remoteid, int len) {
  375. dp_blacklist *bl;
  376. // alrady exists?
  377. HASH_FIND(hh, blacklists, remoteid, len, bl);
  378. if ( conf->debug ) printf("BL: add item\n");
  379. // found an entry, push the expiration further
  380. if ( bl ) {
  381. if ( conf->debug ) printf("BL: item found -> pushing further\n");
  382. bl->expire = time(NULL) + conf->bltime;
  383. }
  384. // not found, create a new one
  385. else {
  386. if ( conf->debug ) printf("BL: item not found, new entry in BL\n");
  387. dp_log(remoteid, len, "blacklisting started");
  388. bl = malloc(sizeof(dp_blacklist));
  389. memcpy(bl->remoteid, remoteid, len);
  390. bl->len = len;
  391. bl->expire = time(NULL) + conf->bltime;
  392. HASH_ADD(hh, blacklists, remoteid, len, bl);
  393. }
  394. }
  395. void dp_hash_cleanup(dp_conf *conf) {
  396. dp_accounting *ac, *actmp;
  397. dp_blacklist *bl, *bltmp;
  398. // is it time to cleanup the list?
  399. // cleanup every conf->interval seconds
  400. if ( dp_accountingtime + conf->interval < time(NULL) ) {
  401. if ( conf->debug ) printf("cleanup interval\n");
  402. dp_accountingtime = time(NULL);
  403. HASH_ITER(hh, accountings, ac, actmp) {
  404. HASH_DEL(accountings, ac);
  405. free(ac);
  406. }
  407. }
  408. // blacklist cleanup check every 1 sec
  409. if ( dp_cleanuptime < time(NULL) ) {
  410. if ( conf->debug ) printf("cleanup BL\n");
  411. dp_cleanuptime = time(NULL);
  412. HASH_ITER(hh, blacklists, bl, bltmp) {
  413. if ( bl->expire < time(NULL) ) {
  414. dp_log(
  415. bl->remoteid, bl->len,
  416. "blacklisting ended");
  417. HASH_DEL(blacklists, bl);
  418. free(bl);
  419. }
  420. }
  421. }
  422. }
  423. int dp_accounting_check(dp_conf *conf, unsigned char *remoteid, int len) {
  424. dp_accounting *ac;
  425. HASH_FIND(hh, accountings, remoteid, len, ac);
  426. if ( conf->debug ) printf("AC Check\n");
  427. if ( ac ) {
  428. if(conf->debug) printf("AC Check: found item %i > %i ?\n", ac->count, conf->pktint);
  429. if ( ac->count > conf->pktint ) {
  430. if(conf->debug) printf("flood detected!\n");
  431. return NF_DROP;
  432. }
  433. }
  434. return NF_ACCEPT;
  435. }
  436. int dp_blacklist_check(dp_conf *conf, unsigned char *remoteid, int len) {
  437. dp_blacklist *bl;
  438. HASH_FIND(hh, blacklists, remoteid, len, bl);
  439. if ( conf->debug ) printf("BL Check\n");
  440. if ( bl ) {
  441. if ( bl->expire > time(NULL) ) {
  442. if ( conf->debug ) printf("blacklisted!\n");
  443. return NF_DROP;
  444. }
  445. }
  446. return NF_ACCEPT;
  447. }