/************************************** * AUTHOR: Federico Tomassini * * Copyright (C) Federico Tomassini * * Contact effetom@gmail.com * *********************************************** ******* BEGIN 3/2006 ******** ************************************************************************* * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * ************************************************************************/ /* * This code is written with my blood. * My hand was hurt. The keyboard was red. * In this code you can find my sacrifice. * * This code is a netfilter iptc library. * iptc is very bad documented: wisdom and * debuggers was my friends to understand * netfilter behavior. * I hope you'll never need to code netfilter * apps. * Memory dumpers are with you. */ #include "includes.h" #include "config.h" // to delete #include #include "iptunnel.h" #include "mark.h" #include "err_errno.h" #include "log.h" static int death_loop_rule; static int clean_on_exit; static rule_store rr, fr, dr; static int dumped; /* Table init: is too easy for comments. * Returns: * 0 * -1 */ int table_init(const char *table, iptc_handle_t * t) { *t = iptc_init(table); error("In table_int, t: %s and errno %d", table, errno); if (!(*t)) { error("In table_init, table %s: -> %s", table, iptc_strerror(errno)); err_ret(ERR_NETFIL, -1); } return 0; } /* * insert the rule -rule- on chain -chain- * at the position pos. * Returns: * 0 * -1 */ int insert_rule(const char *rule, iptc_handle_t * t, const char *chain, int pos) { int res; res = iptc_insert_entry(chain, (struct ipt_entry *) rule, 0, t); error ("res is: %d rule is: %p chain is: %s pos is: %d t is: %p", res, *rule, chain, pos, t); if (!res) { error("In insert_rule: %s.", iptc_strerror(errno)); err_ret(ERR_NETRUL, -1); } return 0; } /* * append the rule -rule- on chain -chain-. * Returns: * 0 * -1 */ int append_rule(const char *rule, iptc_handle_t * t, const char *chain) { int res; res = iptc_append_entry(chain, (struct ipt_entry *) rule, t); error("res is: %d, chain: %s, rule: %s, t: %s, Errno is: %d", res, chain, rule, t, errno); if (!res) { error("In append_rule: %s.", iptc_strerror(errno)); err_ret(ERR_NETRUL, -1); } return 0; } /* * commit modified rules and chains. * Returns: * 0 * -1 */ int commit_rules(iptc_handle_t * t) { int res; res = iptc_commit(t); error ("This is the value of res: %i This is the value of t is: %p errno is: %d", res, t, errno); if (!res) { error("In commit_rules: %s.", iptc_strerror(errno)); err_ret(ERR_NETCOM, -1); } return 0; } /* * Put in -rule- the netfilter rule: * * -A OUTPUT -o ntk_tunl+ -m conntrack \ * --ctstate RELATED,ESTABLISHED -j CONNMARK \ * --restore-mark * * -rule- has to be RESTORE_OUTPUT_RULE_SZ-sized */ void restore_output_rule_init(char *rule) { struct ipt_entry *ee; struct ipt_entry_match *em; struct ipt_entry_target *et; struct ipt_conntrack_info *ici; struct ipt_connmark_target_info *icmi; memset(rule, 0, RESTORE_OUTPUT_RULE_SZ); ee = (struct ipt_entry *) (rule); em = (struct ipt_entry_match *) (rule + OFFSET_MATCH); ici = (struct ipt_conntrack_info *) (rule + OFFSET_MATCH_INFO); et = (struct ipt_entry_target *) (rule + OFFSET_TARGET); icmi = (struct ipt_connmark_target_info *) (rule + OFFSET_TARGET_INFO); ee->next_offset = RESTORE_OUTPUT_RULE_SZ; ee->target_offset = OFFSET_TARGET; snprintf(ee->ip.outiface, IFNAMSIZ, "%s+", NTK_TUNL_PREFIX); memset(ee->ip.outiface_mask, 0xFF, strlen(ee->ip.outiface) - 1); strcpy(em->u.user.name, MOD_CONNTRACK); em->u.match_size = MATCH_SZ;; em->u.user.match_size = em->u.match_size; et->u.target_size = TARGET_SZ; et->u.user.target_size = et->u.target_size; strcpy(et->u.user.name, MOD_CONNMARK); ici->flags = 1; ici->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED); ici->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED); icmi->mode = IPT_CONNMARK_RESTORE; icmi->mask = 0xffffffffUL; } /* * Put in -rule- the netfilter rule: * * -A POSTROUTING -o ntk_tunl+ -m conntrack * --ctstate NEW -j ntk_mark_chain * * -rule- has to be NTK_FORWARD_RULE_SZ-sized */ void ntk_forward_rule_init(char *rule) { struct ipt_entry *ee; struct ipt_entry_match *em; struct ipt_entry_target *et; struct ipt_conntrack_info *ici; memset(rule, 0, NTK_FORWARD_RULE_SZ); ee = (struct ipt_entry *) (rule); em = (struct ipt_entry_match *) (rule + IPT_ENTRY_SZ); ici = (struct ipt_conntrack_info *) (rule + OFFSET_MATCH_INFO); et = (struct ipt_entry_target *) (rule + OFFSET_TARGET); ee->next_offset = NTK_FORWARD_RULE_SZ; ee->target_offset = OFFSET_TARGET; snprintf(ee->ip.outiface, IFNAMSIZ, "%s+", NTK_TUNL_PREFIX); memset(ee->ip.outiface_mask, 0xFF, strlen(ee->ip.outiface) - 1); strcpy(em->u.user.name, MOD_CONNTRACK); em->u.match_size = MATCH_SZ; em->u.user.match_size = em->u.match_size; ici->flags = 1; ici->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW); et->u.target_size = IPT_ENTRY_TARGET_SZ + 4; et->u.user.target_size = et->u.target_size; strcpy(et->u.user.name, NTK_MARK_CHAIN); } /* * Put in -rule- the netfilter rule: * * * -A ntk_mark_chain -o ntk_tunl * -j CONNMARK --set-mark * * -rule- has to be MARK_RULE_SZ-sized */ void mark_rule_init(char *rule, char *outiface, int outiface_num) { struct ipt_entry *ee; struct ipt_entry_target *et; struct ipt_connmark_target_info *icmi; memset(rule, 0, MARK_RULE_SZ); ee = (struct ipt_entry *) (rule); et = (struct ipt_entry_target *) (rule + IPT_ENTRY_SZ); icmi = (struct ipt_connmark_target_info *) (rule + IPT_ENTRY_SZ + IPT_ENTRY_TARGET_SZ); ee->next_offset = MARK_RULE_SZ; ee->target_offset = IPT_ENTRY_SZ; et->u.target_size = TARGET_SZ; et->u.user.target_size = et->u.target_size; strcpy(et->u.user.name, MOD_CONNMARK); icmi->mode = IPT_CONNMARK_SET; icmi->mask = 0xffffffffUL; snprintf(ee->ip.outiface, IFNAMSIZ, "%s%d", outiface, outiface_num); memset(ee->ip.outiface_mask, 0xFF, strlen(ee->ip.outiface)); icmi->mark = outiface_num + 1; } /* * Put in -rule- the netfilter rule: * * * -A PREROUTING -o ntk_tunl+ \ * -j CONNMARK --set-mark 25 * * -rule- has to be IGW_FILTER_RULE_SZ-sized */ void igw_mark_rule_init(char *rule) { int res; struct ipt_entry *e; struct ipt_entry_target *et; memset(rule, 0, IGW_FILTER_RULE_SZ); e = (struct ipt_entry *) rule; et = (struct ipt_entry_target *) (rule + IPT_ENTRY_SZ); e->next_offset = IGW_FILTER_RULE_SZ; e->target_offset = IPT_ENTRY_SZ; snprintf(e->ip.iniface, IFNAMSIZ, "%s+", NTK_TUNL_PREFIX); memset(e->ip.iniface_mask, 0xFF, strlen(e->ip.iniface) - 1); et->u.target_size = IPT_ENTRY_TARGET_SZ + 4; et->u.user.target_size = et->u.target_size; strcpy(et->u.user.name, MARK_TARGET); res = INET_MARK; memcpy(et->data, &res, 4); } /* * Build the chain ntk_mark_chain on * mangle table. */ int ntk_mark_chain_init(iptc_handle_t * t) { int res; res = iptc_is_chain(NTK_MARK_CHAIN, *t); if (res) { debug(DBG_NORMAL, "In mark_init: bizarre, ntk mangle" "chain is present yet. it will be flushed."); res = iptc_flush_entries(NTK_MARK_CHAIN, t); if (!res) goto dontwork; } else { res = iptc_create_chain(NTK_MARK_CHAIN, t); if (!res) goto dontwork; } return 0; dontwork: error("In ntk_mark_chain_init: -> %s", iptc_strerror(errno)); err_ret(ERR_NETCHA, -1) } /* * Takes committed rules and copies them * to structs. This is usefule to delete * the rules on exit, even if netfilter * was modified before the deletion/ * Returns: * 0 * -1 */ int store_rules() { int res; iptc_handle_t t; struct ipt_entry *r, *f, *d; res = table_init(MANGLE_TABLE, &t); if (res) { error(err_str); err_ret(ERR_NETSTO, -1); } r = (struct ipt_entry *) iptc_first_rule(CHAIN_OUTPUT, &t); f = (struct ipt_entry *) iptc_first_rule(CHAIN_POSTROUTING, &t); /* Not elegant style, but faster */ if (death_loop_rule) { d = (struct ipt_entry *) iptc_first_rule(CHAIN_PREROUTING, &t); if (r && f && d) { rr.sz = RESTORE_OUTPUT_RULE_SZ; memcpy(rr.e, r, rr.sz); rr.chain = CHAIN_OUTPUT; fr.sz = NTK_FORWARD_RULE_SZ; memcpy(fr.e, f, fr.sz); fr.chain = CHAIN_POSTROUTING; dr.sz = IGW_FILTER_RULE_SZ; memcpy(dr.e, d, dr.sz); dr.chain = CHAIN_PREROUTING; error("This is store_rules, And the value of t is: %p", t); commit_rules(&t); return 0; } else { error("This is store_rules else, And the value of t is: %p", t); commit_rules(&t); error("In store_rules: %s.", iptc_strerror(errno)); err_ret(ERR_NETSTO, -1); } } if (r && f) { rr.sz = RESTORE_OUTPUT_RULE_SZ; memcpy(rr.e, r, rr.sz); rr.chain = CHAIN_OUTPUT; fr.sz = NTK_FORWARD_RULE_SZ; memcpy(fr.e, f, fr.sz); fr.chain = CHAIN_POSTROUTING; commit_rules(&t); return 0; } commit_rules(&t); err_ret(ERR_NETSTO, -1); } /* Two debugging functions: to delete */ int dump_rules() { int fd; fd = open(DATA_DIR "/mark_rules", O_CREAT | O_WRONLY | O_TRUNC, 0540); if (fd == -1) { dumped = 0; error("Storing rules to fs: %s.", strerror(errno)); return -1; } write(fd, &rr, sizeof(rule_store)); write(fd, &fr, sizeof(rule_store)); write(fd, &dr, sizeof(rule_store)); close(fd); dumped = 1; return 0; } int load_dump_rules() { int fd; rule_store d_rr, d_fr, d_dr; if (!dumped) return 0; fd = open("/usr/share/netsukuku/mark_rules", O_RDONLY); if (fd == -1) return -1; read(fd, &d_rr, sizeof(rule_store)); read(fd, &d_fr, sizeof(rule_store)); read(fd, &d_dr, sizeof(rule_store)); close(fd); if (memcmp(&rr, &d_rr, sizeof(rule_store))) error("Stored rule rr differs from original."); if (memcmp(&fr, &d_fr, sizeof(rule_store))) error("Stored rule fr differs from original."); if (memcmp(&dr, &d_dr, sizeof(rule_store))) error("Stored rule dr differs from original."); return 0; } /* * This function builds: * - OUTPUT rule * - POSTROUTING rule * - PREROUTING rule * - ntk_mark_chain * and store rules for future deletion. * * Returns: * 0 * -1 * * If -1, any rule will be committed. */ int mark_init(int igw) { int res; iptc_handle_t t; char rule[MAX_RULE_SZ]; /*res=inet_aton(NTK_NET_STR,&inet_dst); if (!res) { error("Can not convert str to addr."); goto cannot_init; } res=inet_aton(NTK_NET_MASK_STR,&inet_dst_mask); if (!res) { error("Can not convert str to addr."); goto cannot_init; } */ res = table_init(MANGLE_TABLE, &t); if (res) { error(err_str); goto cannot_init; } res = ntk_mark_chain_init(&t); if (res) { error(err_str); error("Unable to create netfilter ntk_mark_chain."); goto cannot_init; } restore_output_rule_init(rule); error("Rule in mark_init: %s", *rule); res = insert_rule(rule, &t, CHAIN_OUTPUT, 0); if (res) { error(err_str); error("Unable to create netfilter restore-marking rule."); goto cannot_init; } ntk_forward_rule_init(rule); res = insert_rule(rule, &t, CHAIN_POSTROUTING, 0); if (res) { error(err_str); error("Unable to create netfilter forwarding rule."); goto cannot_init; } if (igw) { death_loop_rule = 1; igw_mark_rule_init(rule); res = insert_rule(rule, &t, CHAIN_PREROUTING, 0); if (res) { error(err_str); error("Unable to create netfilter igw death loop rule."); death_loop_rule = 0; goto cannot_init; } } else death_loop_rule = 0; res = commit_rules(&t); if (res) { error(err_str); error("Netfilter mangle table was not altered!"); goto cannot_init; } res = store_rules(); if (res) { error(err_str); error ("Rules storing failed: autocleaning netfilter on exit disable."); clean_on_exit = 0; } else clean_on_exit = 1; dump_rules(); debug(DBG_NORMAL, "Netfilter chain ntk_mark_chain created (mangle)."); debug(DBG_NORMAL, "Netfilter restoring rule created (mangle->output)."); debug(DBG_NORMAL, "Netfilter forwarding rule created (mangle->postrouting)."); if (igw) debug(DBG_NORMAL, "Netfilter death loop igw rule created."); debug(DBG_NORMAL, "mark_init(), netfilter mangle table initialized."); loginfo("Netfilter mangle table modified."); return 0; cannot_init: err_ret(ERR_MRKINI, -1); } /* * Count the number of rules in ntk_mangle_chain. * * Returns the number of rules present in * this chain. */ int count_ntk_mark_chain(iptc_handle_t * t) { int nchain = 0; const struct ipt_entry *e; e = iptc_first_rule(NTK_MARK_CHAIN, t); while (e) { nchain++; e = iptc_next_rule(e, t); } return nchain; } /* * This function build the rules: * * -A ntk_mark_chain -o ntk_tunl * -j CONNMARK --set-mark m * * If: * * s= n-number_of_rules_present * then: * if s>0, will be created s rules, * else: * nothing. * * Returns: * 0 * -1 */ int create_mark_rules(int n) { int nchain; int res, i; char rule[MARK_RULE_SZ]; iptc_handle_t t; res = table_init(MANGLE_TABLE, &t); if (res) { error(err_str); err_ret(ERR_NETRUL, -1); } nchain = count_ntk_mark_chain(&t); if (nchain == -1) { error("In create_mark_rules: can not read ntk_mark_chain."); err_ret(ERR_NETRUL, -1); } if (nchain >= n) { debug(DBG_NORMAL, "In create_mark_rules: rules present yet."); return 0; } for (i = nchain; i < n; i++) { mark_rule_init(rule, NTK_TUNL_PREFIX, i); res = append_rule(rule, &t, NTK_MARK_CHAIN); if (res) { error(err_str); err_ret(ERR_NETRUL, -1); } } res = commit_rules(&t); if (res) { error(err_str); err_ret(ERR_NETRUL, -1); } debug(DBG_NORMAL, "Created %d marking rules.", n - nchain); return 0; } /* * Deltion function: * this delete the chain ntk_mark_chain * Returns: * 0 * -1 */ int delete_ntk_forward_chain(iptc_handle_t * t) { int res; res = iptc_is_chain(NTK_MARK_CHAIN, *t); if (!res) return 0; res = iptc_flush_entries(NTK_MARK_CHAIN, t); if (!res) goto cannot_delete; res = iptc_delete_chain(NTK_MARK_CHAIN, t); if (!res) goto cannot_delete; return 0; cannot_delete: error("In delete_ntk_forward_chain: -> %s", iptc_strerror(errno)); err_ret(ERR_NETDEL, -1); } /* delete the first rule of a chain. * Unused. */ int delete_first_rule(iptc_handle_t * t, const char *chain) { int res; const struct ipt_entry *e; e = iptc_first_rule(chain, t); if (!e) return 0; res = iptc_delete_num_entry(chain, 0, t); if (!res) goto cannot_delete; return 0; cannot_delete: error("In delete_first_rule: -> %s", iptc_strerror(errno)); err_ret(ERR_NETDEL, -1); } /* * Search for the position of rule -rule.rule- * on the chain rule.chain * Returns: * pos if rule was found * -1 if rule wasn't found */ int rule_position(rule_store * rule, iptc_handle_t * t) { const struct ipt_entry *e; int res, count = -1, found = 0; e = iptc_first_rule(rule->chain, t); while (e) { count++; res = memcmp(e, rule->e, rule->sz); if (!res) { found = 1; break; } e = iptc_next_rule(e, t); } return found ? count : -1; } /* * Delete rule -rule.rule- on chain rule.chain. * Returns * 0 if deletion is Ok or if nothing * has to be deleted * -1 error */ int delete_rule(rule_store * rule, iptc_handle_t * t) { int pos, res; pos = rule_position(rule, t); if (pos == -1) { debug(DBG_NORMAL, "No rule in %s to be deleted.", rule->chain); return 0; } res = iptc_delete_num_entry(rule->chain, pos, t); if (!res) { debug(DBG_NORMAL, "Unable to delete rule in chain %s.", rule->chain); err_ret(ERR_NETDEL, -1); } return 0; } /* * clean the rules committed by: * - mark_init * - create_mark_rules() * Returns: * 0 * -1 */ int mark_close() { iptc_handle_t t; int res; if (!clean_on_exit) { debug(DBG_NORMAL, "mark_close: cleaning is not my task."); return 0; } load_dump_rules(); res = table_init(MANGLE_TABLE, &t); if (res) goto reset_error; res = 0; res += delete_rule(&rr, &t); res += delete_rule(&fr, &t); if (death_loop_rule) { debug(DBG_INSANE, "In mark_close: I'm an IGW: deleting death loop rule."); res += delete_rule(&dr, &t); } if (res) goto reset_error; res = delete_ntk_forward_chain(&t); if (res) goto reset_error; res = commit_rules(&t); if (res) goto reset_error; debug(DBG_NORMAL, "Netfilter completely restored."); return 0; reset_error: error(err_str); loginfo("Netfilter was not restored. To clean, run:\n" "\tiptables -t mangle -F\n" "\tiptables -t mangle -X %s", NTK_MARK_CHAIN); err_ret(ERR_NETRST, -1); }