A userspace application that filters DHCP floods to protect a DHCP server. It uses the Netfilter userspace packet queuing API.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

dhcp_protect.c 11KB

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