成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

iptables 源碼分析

運維 系統運維
iptables 是與最新的 2.6.x 版本Linux 內核集成的 IP 信息包過濾系統。對于初學者,我們知道iptables的配置和命令就足夠了,但是對于大俠們,我們有必要知道下iptables 源碼。

iptables的源碼是比較繁瑣的,希望大家能懂得!

  一、規則的顯示

  選擇先來說明規則的顯示,因為他涉及到的東東簡單,而且又全面,了解了規則的顯示,對于其它操作的了解就顯得容易了。

  iptables version 1.2.7

  iptables有兩條線:ipv4 和ipv6,這里只分析v4的,因為v6偶暫時還用不著,沒有去看。

  iptables_standardone.c

  主函數:

  int main(int argc, char *argv[])

  {

  int ret;

  char *table = "filter"; /*默認的表是filter*/

  iptc_handle_t handle = NULL;

  program_name = "iptables";

  program_version = IPTABLES_VERSION;

  #ifdef NO_SHARED_LIBS

  init_extensions();

  #endif

  /*進入命令行處理函數*/

  ret = do_command(argc, argv, &table, &handle);

  if (ret)

  ret = iptc_commit(&handle);

  if (!ret)

  fprintf(stderr, "iptables: %s\n",

  iptc_strerror(errno));

  exit(!ret);

  }

  table表示表的名稱,就是iptables -t 后面跟的那個,默認是"filter"

  iptc_handle_t handle = NULL; 這個東東很重要,現在初始化NULL,后面他被用來存儲一個表的所有規則的快照。

  program_name = "iptables";

  program_version = IPTABLES_VERSION;

  設置名稱和版本。

  #ifdef NO_SHARED_LIBS

  init_extensions();

  #endif

  iptables很多東東,是用共享庫*.so的形式(我們安裝會,可以在諸如/lib/iptables下邊看到),如果不采用共享庫,則進行一個初始化操作。我們假設是采用共享庫的,忽略它。

  然后就進入核心處理模塊:

  do_command(argc, argv, &table, &handle);

  do_command 函數是整個系統的核心,負責處理整個用戶的輸入命令。函數首先對一些結構、變量進行初始化,初始化完畢后,進入while循環,分析用戶輸入的命令,設置相關的標志變量,然后根據相應標志,調用對應的處理函數。

  struct ipt_entry fw, *e = NULL;

  int invert = 0;

  unsigned int nsaddrs = 0, ndaddrs = 0;

  struct in_addr *saddrs = NULL, *daddrs = NULL;

  int c, verbose = 0;

  const char *chain = NULL;

  const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;

  const char *policy = NULL, *newname = NULL;

  unsigned int rulenum = 0, options = 0, command = 0;

  const char *pcnt = NULL, *bcnt = NULL;

  int ret = 1;

  struct iptables_match *m;

  struct iptables_target *target = NULL;

  struct iptables_target *t;

  const char *jumpto = "";

  char *protocol = NULL;

  const char *modprobe = NULL;

  /*初始化變量*/

  memset(&fw, 0, sizeof(fw));

  opts = original_opts;

  global_option_offset = 0;

  /* re-set optind to 0 in case do_command gets called

  * a second time */

  optind = 0;

  /*初始化兩個全局變量*/

  /* clear mflags in case do_command gets called a second time

  * (we clear the global list of all matches for security)*/

  for (m = iptables_matches; m; m = m->next) {

  m->mflags = 0;

  m->used = 0;

  }

  for (t = iptables_targets; t; t = t->next) {

  t->tflags = 0;

  t->used = 0;

  }

  ps:開頭一大堆的變量定義和初始化,可以在程序分析的時候看它們的作用,有兩個全局結構變量很重要:iptables_matches和iptables_targets?,F在來分析他們的作用會有一點困難,因為它們涉及到了太多方面的東東,這里,可以先把它們“想像成”用戶空間用來讀取內核規則的結構(當然,這有點錯誤)。

  /*開始化析命令行*/

  while ((c = getopt_long(argc, argv,

  "-A:C:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:",

  opts, NULL)) != -1)

  {

  }

  這個while循環處理所有的用戶輸入,對應規則輸出-L,有:

  case 'L':

  add_command(&command, CMD_LIST, CMD_ZERO,

  invert);

  if (optarg) chain = optarg;

  else if (optind < argc && argv[optind][0] != '-'

  && argv[optind][0] != '!')

  chain = argv[optind++];

  break;

  add_command函數負責將命令標志變量command與令標志 CMD_LIST求&運算, CMD_ZERO只是一個附加的判斷標志而已,invert);然后,從命令行中取得要顯示的鏈名(如果有的話)。

  與此相關的還有用t參數指定了表名:

  case 't':

  if (invert)

  exit_error(PARAMETER_PROBLEM,

  "unexpected ! flag before --table");

  *table = argv[optind-1];

  break;

  即,如果有’t’參數,則取’t’后跟的表名:*table = argv[optind-1],否則,它應該是主函數中默認的filter表。

  命令處理完畢后,即進入執行模塊:

  /*因為程序定義了共享庫的話,iptables_matches/iptables_target這兩個結構運行至此是NULL,并且target也是NULL,對于規則顯示而言,這一部份的處理目前沒有實際意義,回過頭再來看這一段更易理解。final_check成員函數的作用是作最終的標志檢查,如果檢測失則,則退出*/

  for (m = iptables_matches; m; m = m->next) {

  if (!m->used)

  continue;

  m->final_check(m->mflags);

  }

  if (target)

  target->final_check(target->tflags);

  接著對參數作一些必要的合法性檢查:

  /* Fix me: must put inverse options checking here --MN */

  if (optind < argc)

  exit_error(PARAMETER_PROBLEM,

  "unknown arguments found on commandline");

  if (!command)

  exit_error(PARAMETER_PROBLEM, "no command specified");

  if (invert)

  exit_error(PARAMETER_PROBLEM,

  "nothing appropriate following !");

  /*對于如果要進行(CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)處理來說,如果沒有設置來源/目的地址及掩碼,則給予它們一個默認值*/

  if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {

  if (!(options & OPT_DESTINATION))

  dhostnetworkmask = "0.0.0.0/0";

  if (!(options & OPT_SOURCE))

  shostnetworkmask = "0.0.0.0/0";

  }

  /*對來源/目的地址及掩碼進行拆分,它們總是以 addr/mask的形式來出現的,根據’/’前面的字符串取得地址值,根據’/’后面的掩碼位數,求得正確的掩碼值,值得注意的是,同時要處理主機地址和網絡地址的情況*/

  if (shostnetworkmask)

  parse_hostnetworkmask(shostnetworkmask, &saddrs,

  &(fw.ip.smsk), &nsaddrs);

  if (dhostnetworkmask)

  parse_hostnetworkmask(dhostnetworkmask, &daddrs,

  &(fw.ip.dmsk), &ndaddrs);

  /*然后檢查來源/目的網絡地址的合法性*/

  if ((nsaddrs > 1 || ndaddrs > 1) &&

  (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))

  exit_error(PARAMETER_PROBLEM, "! not allowed with multiple"

  " source or destination IP addresses");

  /*對命令行格式進行合法性檢查*/

  generic_opt_check(command, options);

  如果前面只是熱身的話,那么從現在開始,就進入實質性階段了:

  do_command函數最后一個參數handle,是一個指向了具體表,如filter、nat表的句柄,這里判斷,如果handle為空,則調用iptc_init,根據table的名稱,讓handle指針指向相應的表的地址空間,也就是把對應表的所有信息從內核中取出來:

  /* only allocate handle if we weren't called with a handle */

  if (!*handle)

  *handle = iptc_init(*table);

  /*如果獲取換敗,將試著插入模塊,再次獲取*/

  if (!*handle) {

  /* try to insmod the module if iptc_init failed */

  iptables_insmod("ip_tables", modprobe);

  *handle = iptc_init(*table);

  /*仍然失敗,則退出*/

  if (!*handle)

  exit_error(VERSION_PROBLEM,

  "can't initialize iptables table `%s': %s",

  *table, iptc_strerror(errno));

  /*繼續進行一些簡單的判斷*/

  if (command == CMD_APPEND

  || command == CMD_DELETE

  || command == CMD_INSERT

  || command == CMD_REPLACE) {

  /*List命令不在判斷之列,暫時不分析*/

  }

  /*判斷命令標志,調用相關函數進行處理*/

  switch (command) {

  case CMD_LIST:

  ret = list_entries(chain,

  options&OPT_VERBOSE,

  options&OPT_NUMERIC,

  options&OPT_EXPANDED,

  options&OPT_LINENUMBERS,

  handle);

  }

  list_entries是規則顯示的主要處理函數。

  Options是顯示的標志變量:

  OPT_VERBOSE:對應-v

  OPT_NUMERIC:對應-n

  OPT_EXPANDED:對應-x

  OPT_LINENUMBERS: -l

  看來很簡單,說了這么大一圈子,就是調用 iptc_init獲取表的規則信息,調用list_entries函數顯示規則。

#p#

  1.1 表的查找

  再回到iptc_init 函數上來,它根據表名,從內核獲取對應的表的相關信息,handle是一個iptc_handle_t類型的指針,在libiptc.c中,有如下定義:

  /* Transparent handle type. */

  typedef struct iptc_handle *iptc_handle_t;

  在Libip4tc中:

  #define STRUCT_TC_HANDLE struct iptc_handle

  在Libiptc.c中,可以找到STRUCT_TC_HANDLE的定義:

  STRUCT_TC_HANDLE

  {

  /* Have changes been made? */

  int changed;

  /* Size in here reflects original state. */

  STRUCT_GETINFO info;

  struct counter_map *counter_map;

  /* Array of hook names */

  const char **hooknames;

  /* Cached position of chain heads (NULL = no cache). */

  unsigned int cache_num_chains;

  unsigned int cache_num_builtins;

  /* Rule iterator: terminal rule */

  STRUCT_ENTRY *cache_rule_end;

  /* Number in here reflects current state. */

  unsigned int new_number;

  STRUCT_GET_ENTRIES entries;

  };

  再來看看iptc_init函數,同樣在在Libip4tc中,有如下定義:

  #define TC_INIT iptc_init

  在Libiptc.c中,可以看到函數的實現,基本上iptables與內核的交互,都是使用setsockopt函數來實現的,對于獲取取規是信息來說,標志位是SO_GET_INFO,而從內核返回回來的規則信息是一個STRUCT_GETINFO結構:

  TC_HANDLE_T TC_INIT(const char *tablename)

  {

  TC_HANDLE_T h;

  STRUCT_GETINFO info;

  unsigned int i;

  int tmp;

  socklen_t s;

  iptc_fn = TC_INIT;

  if (sockfd != -1)

  close(sockfd);

  /*為獲取信息打開一個套接字接口*/

  sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);

  if (sockfd < 0)

  return NULL;

  s = sizeof(info);

  if (strlen(tablename) >= TABLE_MAXNAMELEN) {

  errno = EINVAL;

  return NULL;

  }

  strcpy(info.name, tablename);

  /*獲取規則信息*/

  if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0)

  return NULL;

  if ((h = alloc_handle(info.name, info.size, info.num_entries))

  == NULL)

  return NULL;

  /* Too hard --RR */

  #if 0

  sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name);

  dynlib = dlopen(pathname, RTLD_NOW);

  if (!dynlib) {

  errno = ENOENT;

  return NULL;

  }

  h->hooknames = dlsym(dynlib, "hooknames");

  if (!h->hooknames) {

  errno = ENOENT;

  return NULL;

  }

  #else

  h->hooknames = hooknames;

  #endif

  /* Initialize current state */

  h->info = info;

  h->new_number = h->info.num_entries;

  for (i = 0; i < h->info.num_entries; i++)

  h->counter_map[i]

  = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i});

  h->entries.size = h->info.size;

  tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;

  if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries,

  &tmp) < 0) {

  free(h);

  return NULL;

  }

  CHECK(h);

  return h;

  }

  函數為h分配空間,然后賦予相應的值。要理解這個函數,還需要了解STRUCT_GETINFO結構和分配內存空間的函數alloc_handle。

  #define STRUCT_GETINFO struct ipt_getinfo

  /* The argument to IPT_SO_GET_INFO */

  struct ipt_getinfo

  {

  /* Which table: caller fills this in. */

  char name[IPT_TABLE_MAXNAMELEN];

  /* Kernel fills these in. */

  /* Which hook entry points are valid: bitmask */

  unsigned int valid_hooks;

  /* Hook entry points: one per netfilter hook. */

  unsigned int hook_entry[NF_IP_NUMHOOKS];

  /* Underflow points. */

  unsigned int underflow[NF_IP_NUMHOOKS];

  /* Number of entries */

  unsigned int num_entries;

  /* Size of entries. */

  unsigned int size;

  };

  /* Allocate handle of given size */

  static TC_HANDLE_T

  alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)

  {

  size_t len;

  TC_HANDLE_T h;

  len = sizeof(STRUCT_TC_HANDLE)

  + size

  + num_rules * sizeof(struct counter_map);

  if ((h = malloc(len)) == NULL) {

  errno = ENOMEM;

  return NULL;

  }

  h->changed = 0;

  h->cache_num_chains = 0;

  h->cache_chain_heads = NULL;

  h->counter_map = (void *)h

  + sizeof(STRUCT_TC_HANDLE)

  + size;

  strcpy(h->info.name, tablename);

  strcpy(h->entries.name, tablename);

  return h;

  }

  函數list_entries用于顯示表下邊的鏈:

  /*顯示某table下的chain*/

  static int

  list_entries(const ipt_chainlabel chain, int verbose, int numeric,

  int expanded, int linenumbers, iptc_handle_t *handle)

  {

  int found = 0;

  unsigned int format;

  const char *this;

  format = FMT_OPTIONS; /*設置輸出格式*/

  if (!verbose) /*詳細輸出模式,,對應-v ,顯示匹配的包的數目,包的大小等*/

  format |= FMT_NOCOUNTS;

  else

  format |= FMT_VIA;

  if (numeric) /*對應-n,以數字的形式輸出地址和端口*/

  format |= FMT_NUMERIC;

  if (!expanded) /*對應-x,expand numbers (display exact values)*/

  format |= FMT_KILOMEGAGIGA;

  if (linenumbers) /*輸出行的編號*/

  format |= FMT_LINENUMBERS;

  for (this = iptc_first_chain(handle); /*遍歷當前table的所有chain*/

  this;

  this = iptc_next_chain(handle))

  {

  const struct ipt_entry *i;

  unsigned int num;

  if (chain && strcmp(chain, this) != 0) /*匹配指定chain名,這里用chain &&,即若不指定chain,輸出所有chain*/

  continue;

  if (found) printf("\n");

  print_header(format, this, handle); /*輸出標頭*/

  i = iptc_first_rule(this, handle); /*移至當前chain的第一條規則*/

  num = 0;

  while (i) {

  print_firewall(i, /*輸出當前規則*/

  iptc_get_target(i, handle),

  num++,

  format,

  *handle);

  i = iptc_next_rule(i, handle); /*移至下一條規則*/

  }

  found = 1;

  }

  errno = ENOENT;

  return found;

  }

  可見,在函數中,由iptc_first_chain和iptc_next_chain實現了遍歷,iptc_first_rule和iptc_next_rule實現了鏈中規是的遍歷,print_firewall函數在遍歷到規則的時候,向終端輸出防火墻規則,其第二個參數iptc_get_target又用于獲取規則的target。

#p#

  前面提到過,在內核中,handler指針指向了從內核中返回的對應的表的信息,handler對應的結構中,涉及到鏈的結構成員主要有兩個:

  struct chain_cache *cache_chain_heads;

  struct chain_cache *cache_chain_iteration;

  前者用于指向第一個鏈,后者指向當前鏈。而struct chain_cache的定義如下:

  struct chain_cache

  {

  char name[TABLE_MAXNAMELEN]; /*鏈名*/

  STRUCT_ENTRY *start; /*該鏈的第一條規則*/

  STRUCT_ENTRY *end; /*該鏈的最后一條規則*/

  };

  理解了這兩個成員,和結構struct chain_cache,再來理解鏈的遍歷函數就不難了。所謂鏈的遍歷,就是將handler對應成員的值取出來。

  #define TC_FIRST_CHAIN iptc_first_chain

  #define TC_NEXT_CHAIN iptc_next_chain

  函數TC_FIRST_CHAIN用于返回第一個鏈:

  /* Iterator functions to run through the chains. */

  const char *

  TC_FIRST_CHAIN(TC_HANDLE_T *handle)

  {

  /*鏈首為空,則返回NULL*/

  if ((*handle)->cache_chain_heads == NULL

  && !populate_cache(*handle))

  return NULL;

  /*當前鏈的指針指向鏈表首部*/

  (*handle)->cache_chain_iteration

  = &(*handle)->cache_chain_heads[0];

  /*返回鏈的名稱*/

  return (*handle)->cache_chain_iteration->name;

  }

  /* Iterator functions to run through the chains. Returns NULL at end. */

  const char *

  TC_NEXT_CHAIN(TC_HANDLE_T *handle)

  {

  /*很簡單,用heads開始,用++就可以實現遍歷了*/

  (*handle)->cache_chain_iteration++;

  if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads

  == (*handle)->cache_num_chains)

  return NULL;

  return (*handle)->cache_chain_iteration->name;

  }

  規則的遍歷

  當遍歷到某個鏈的時候,接下來,就需要遍歷當前鏈下的所有規則了,輸出之了。前面敘述了鏈的遍歷,那么規則的遍歷,應該就是根據鏈的名稱,找到對應的成員結構struct chain_cache ,這里面包含了當前鏈的第一條規則與最后一條規則的指針:

  #define TC_FIRST_RULE iptc_first_rule

  #define TC_NEXT_RULE iptc_next_rule

  /* Get first rule in the given chain: NULL for empty chain. */

  const STRUCT_ENTRY *

  TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle)

  {

  struct chain_cache *c;

  c = find_label(chain, *handle); /*根據鏈名,返回對應的struct chain_cache結構*/

  if (!c) { /*沒有找到,返回NULL*/

  errno = ENOENT;

  return NULL;

  }

  /* Empty chain: single return/policy rule */

  if (c->start == c->end) /*如果是空鏈*/

  return NULL;

  (*handle)->cache_rule_end = c->end;

  return c->start; /*返回鏈的首條規則*/

  }

  /* Returns NULL when rules run out. */

  const STRUCT_ENTRY *

  TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)

  {

  if ((void *)prev + prev->next_offset

  == (void *)(*handle)->cache_rule_end)

  return NULL;

  return (void *)prev + prev->next_offset;

  }

  要更解TC_NEXT_RULE函數是如何實現查找下一條規則的,需要首先理解STRUCT_ENTRY結構:

  #define STRUCT_ENTRY struct ipt_entry

  ipt_entry結構用于存儲鏈的規則,每一個包過濾規則可以分成兩部份:條件和動作。前者在Netfilter中,稱為match,后者稱之為target。Match又分為兩部份,一部份為一些基本的元素,如來源/目的地址,進/出網口,協議等,對應了struct ipt_ip,我們常常將其稱為標準的match,另一部份match則以插件的形式存在,是動態可選擇,也允許第三方開發的,常常稱為擴展的match,如字符串匹配,p2p匹配等。同樣,規則的target也是可擴展的。這樣,一條規則占用的空間,可以分為:struct ipt_ip+n*match+n*target,(n表示了其個數,這里的match指的是可擴展的match部份)?;诖?,規則對應的結構如下:

  /* This structure defines each of the firewall rules. Consists of 3

  parts which are 1) general IP header stuff 2) match specific

  stuff 3) the target to perform if the rule matches */

  struct ipt_entry

  {

  struct ipt_ip ip; /*標準的match部份*/

  /* Mark with fields that we care about. */

  unsigned int nfcache;

  /* Size of ipt_entry + matches */

  u_int16_t target_offset; /*target的開始位置,是sizeof(ipt_entry+n*match)*/

  /* Size of ipt_entry + matches + target */

  u_int16_t next_offset; /*下一條規則相對于本條規則的位置,是sizeof(ipt_entry)加上所有的match,以及所有的target*/

  /* Back pointer */

  unsigned int comefrom;

  /* Packet and byte counters. */

  struct ipt_counters counters;

  /* The matches (if any), then the target. */

  unsigned char elems[0];

  };

  有了這樣的基礎,就不難理解遍歷規則中,尋找下一條規則語句:

  return (void *)prev + prev->next_offset;

  即是本條規則加上下一條規則的偏移值。

  輸出規則

  print_firewall 函數用于規則的輸出:

  print_firewall(i, iptc_get_target(i, handle), num++,format,*handle);

  i:當前的規則;

  iptc_get_target(i, handle):用于規則的target部份的處理;

  num:規則序號;

  format:輸出格式;

  handler:表的信息;

  /* e is called `fw' here for hysterical raisins */

  static void

  print_firewall(const struct ipt_entry *fw,

  const char *targname,

  unsigned int num,

  unsigned int format,

  const iptc_handle_t handle)

  {

  struct iptables_target *target = NULL;

  const struct ipt_entry_target *t;

  u_int8_t flags;

  char buf[BUFSIZ];

  if (!iptc_is_chain(targname, handle))

  target = find_target(targname, TRY_LOAD);

  else

  target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED);

  t = ipt_get_target((struct ipt_entry *)fw);

  flags = fw->ip.flags;

  if (format & FMT_LINENUMBERS) /*輸出行號*/

  printf(FMT("%-4u ", "%u "), num+1);

  if (!(format & FMT_NOCOUNTS)) { /*詳細模式,列出計數器*/

  print_num(fw->counters.pcnt, format); /*匹配當前規則的數據包個數*/

  print_num(fw->counters.bcnt, format); /*--------------------大小*/

  }

  /*輸出目標名稱*/

  if (!(format & FMT_NOTARGET)) /*目標名稱,即攔截、通過等動作*/

  printf(FMT("%-9s ", "%s "), targname);

  /*輸出協議名*/

  fputc(fw->ip.invflags & IPT_INV_PROTO ? '!' : ' ', stdout);

  {

  char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC);

  if (pname)

  printf(FMT("%-5s", "%s "), pname);

  else

  printf(FMT("%-5hu", "%hu "), fw->ip.proto);

  }

  /*輸出選項字段*/

  if (format & FMT_OPTIONS) {

  if (format & FMT_NOTABLE)

  fputs("opt ", stdout);

  fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout); //#define IP_FW_INV_FRAG 0x0080 /* Invert the sense of IP_FW_F_FRAG. */

  fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); //#define IP_FW_F_FRAG 0x0004 /* Set if rule is a fragment rule */

  fputc(' ', stdout);

  }

  if (format & FMT_VIA) {

  char iface[IFNAMSIZ+2];

  if (fw->ip.invflags & IPT_INV_VIA_IN) { /*輸入端口取反標志*/

  iface[0] = '!'; /*設置取反標志符*/

  iface[1] = '\0';

  }

  else iface[0] = '\0';

  if (fw->ip.iniface[0] != '\0') {

  strcat(iface, fw->ip.iniface);

  }

  else if (format & FMT_NUMERIC) strcat(iface, "*");

  else strcat(iface, "any");

  printf(FMT(" %-6s ","in %s "), iface); /*輸出輸入端口*/

  if (fw->ip.invflags & IPT_INV_VIA_OUT) { /*輸出端口取反標志*/

  iface[0] = '!'; /*設置取反標志符*/

  iface[1] = '\0';

  }

  else iface[0] = '\0';

  if (fw->ip.outiface[0] != '\0') {

  strcat(iface, fw->ip.outiface);

  }

  else if (format & FMT_NUMERIC) strcat(iface, "*");

  else strcat(iface, "any");

  printf(FMT("%-6s ","out %s "), iface); /*輸出輸出端口*/

  } /*end print in/out interface */

  /*輸出源地址及掩碼*/

  fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); /*源地址取反標志*/

  if (fw->ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) /*源地址為任意*/

  printf(FMT("%-19s ","%s "), "anywhere");

  else {

  if (format & FMT_NUMERIC)

  sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src)));

  else

  sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src)));

  strcat(buf, mask_to_dotted(&(fw->ip.smsk)));

  printf(FMT("%-19s ","%s "), buf);

  }

  /*輸出目的地址及掩碼*/

  fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);

  if (fw->ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))

  printf(FMT("%-19s","-> %s"), "anywhere");

  else {

  if (format & FMT_NUMERIC)

  sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst)));

  else

  sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst)));

  strcat(buf, mask_to_dotted(&(fw->ip.dmsk)));

  printf(FMT("%-19s","-> %s"), buf);

  }

  if (format & FMT_NOTABLE)

  fputs(" ", stdout);

  /*輸出擴展的MATCH*/

  IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);

  /*輸出擴展的TARGET*/

  if (target) {

  if (target->print)

  /* Print the target information. */

  target->print(&fw->ip, t, format & FMT_NUMERIC);

  } else if (t->u.target_size != sizeof(*t))

  printf("[%u bytes of unknown target data] ",

  t->u.target_size - sizeof(*t));

  if (!(format & FMT_NONEWLINE))

  fputc('\n', stdout);

  }

  函數分為三部份:

  輸出標準的match部份;

  輸出擴展的match部份,調用IPT_MATCH_ITERATE實現;

  調用對應的target的print函數輸出target部份。

  match的輸出

  IPT_MATCH_ITERATE 宏用于實現擴展match的遍歷。這個宏定義在內核include/Linux/Netfilter-ipv4/Ip_tables.h中:

  #define IPT_MATCH_ITERATE(e, fn, args...) \

  ({ \

  unsigned int __i; \

  int __ret = 0; \

  struct ipt_entry_match *__match; \

  \

  for (__i = sizeof(struct ipt_entry); \

  __i < (e)->target_offset; \

  __i += __match->u.match_size) { \

  __match = (void *)(e) + __i; \

  \

  __ret = fn(__match , ## args); \ /*每找到一個match,就交由fn函數來處理,在print_firewall中,傳遞過來的是函數print_match*/

  if (__ret != 0) \

  break; \

  } \

  __ret; \

  })

  要理解這個宏,需要先了解規則的存儲,前面提到過,因為match/target都是可變的,所以在內存中,采取了ip_entry+n*match+n*target,即在規則后,是連續的若干個match,而mathc后面,又是若干個target,在結構ip_entry中,成員u_int16_t target_offset;代表了target的偏移地址,即target的開始,match的結束。我們要查到當前規則對應的所有match,需要了解三個要素:

  1、match從哪里開始:起始地址應該是 [當前規則地址+sizeof(struct ipt_entry)];

  2、match從哪里結束:結束地址,應該是 [當前規則地址+target_offet];

  3、每一個match的大小,在內核中,match對應的結構是ipt_entry_match,其成員u.match_size指明了當前match的大小;

  這三點,對應了for循環:

  for (__i = sizeof(struct ipt_entry); __i < (e)->target_offset; __i += __match->u.match_size)

  這樣,i就對應了某個match的偏移植,通過:

  __match = (void *)(e) + __i;

  就得到了match的地址。

  再通過

  __ret = fn(__match , ## args);

  輸出之。

  fn函數是在print_firewall中,傳遞過來的是函數print_match。

  static int

  print_match(const struct ipt_entry_match *m,

  const struct ipt_ip *ip,

  int numeric)

  {

  /*根據match名稱進行查找,返回一個iptables_match結構,然后調用其中封裝的print函數輸出該match的信息*/

  struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD);

  if (match) {

  if (match->print)

  match->print(ip, m, numeric);

  else

  printf("%s ", match->name);

  } else {

  if (m->u.user.name[0])

  printf("UNKNOWN match `%s' ", m->u.user.name);

  }

  /* Don't stop iterating. */

  return 0;

  }

  這里涉及到兩個重要的結構:

  struct ipt_entry_match:在內核中用于存儲擴展match信息

  struct ipt_entry_match

  {

  union {

  struct {

  u_int16_t match_size;

  /* Used by userspace */

  char name[IPT_FUNCTION_MAXNAMELEN];

  } user;

  struct {

  u_int16_t match_size;

  /* Used inside the kernel */

  struct ipt_match *match;

  } kernel;

  /* Total length */

  u_int16_t match_size;

  } u;

  unsigned char data[0];

  };

  struct iptables_match:用于用戶級的match存儲:

  /* Include file for additions: new matches and targets. */

  struct iptables_match

  {

  /* Match鏈,初始為NULL */

  struct iptables_match *next;

  /* Match名,和核心模塊加載類似,作為動態鏈接庫存在的Iptables Extension的命名規則為libipt_'name'.so */

  ipt_chainlabel name;

  /*版本信息,一般設為NETFILTER_VERSION */

  const char *version;

  /* Match數據的大小,必須用IPT_ALIGN()宏指定對界*/

  size_t size;

  /*由于內核可能修改某些域,因此size可能與確切的用戶數據不同,這時就應該把不會被改變的數據放在數據區的前面部分,而這里就應該填寫被改變的數據區大小;一般來說,這個值和size相同*/

  size_t userspacesize;

  /*當iptables要求顯示當前match的信息時(比如iptables-m ip_ext -h),就會調用這個函數,輸出在iptables程序的通用信息之后. */

  void (*help)(void);

  /*初始化,在parse之前調用. */

  void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);

  /*掃描并接收本match的命令行參數,正確接收時返回非0,flags用于保存狀態信息*/

  int (*parse)(int c, char **argv, int invert, unsigned int *flags,

  const struct ipt_entry *entry,

  unsigned int *nfcache,

  struct ipt_entry_match **match);

  /* 前面提到過這個函數,當命令行參數全部處理完畢以后調用,如果不正確,應該

  退出(exit_error())*/

  void (*final_check)(unsigned int flags);

  /*當查詢當前表中的規則時,顯示使用了當前match的規則*/

  void (*print)(const struct ipt_ip *ip,

  const struct ipt_entry_match *match, int numeric);

  /*按照parse允許的格式將本match的命令行參數輸出到標準輸出,用于iptables-save命令. */

  void (*save)(const struct ipt_ip *ip,

  const struct ipt_entry_match *match);

  /* NULL結尾的參數列表,struct option與getopt(3)使用的結構相同*/

  const struct option *extra_opts;

  /* Ignore these men behind the curtain: */

  unsigned int option_offset;

  struct ipt_entry_match *m;

  unsigned int mflags;

  unsigned int used;

  #ifdef NO_SHARED_LIBS

  unsigned int loaded; /* simulate loading so options are merged properly */

  #endif

#p#

  理解了這兩個結構后,再來看find_match函數:

  然match是以可擴展的形式表現出來,那么,當然就需要find_match這樣的函數將它們一一找出來了。

  前面說過,在輸出規則的函數中:

  IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);

  用來遍歷每一個match,找到了后,就調用print_match來輸出。print_match是調用find_match來查找的:

  struct iptables_match *

  find_match(const char *name, enum ipt_tryload tryload)

  {

  struct iptables_match *ptr;

  for (ptr = iptables_matches; ptr; ptr = ptr->next) {

  if (strcmp(name, ptr->name) == 0)

  break;

  }

  #ifndef NO_SHARED_LIBS

  if (!ptr && tryload != DONT_LOAD) {

  char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")

  + strlen(name)];

  sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);

  if (dlopen(path, RTLD_NOW)) {

  /* Found library. If it didn't register itself,

  maybe they specified target as match. */

  ptr = find_match(name, DONT_LOAD);

  if (!ptr)

  exit_error(PARAMETER_PROBLEM,

  "Couldn't load match `%s'\n",

  name);

  } else if (tryload == LOAD_MUST_SUCCEED)

  exit_error(PARAMETER_PROBLEM,

  "Couldn't load match `%s':%s\n",

  name, dlerror());

  }

  #else

  if (ptr && !ptr->loaded) {

  if (tryload != DONT_LOAD)

  ptr->loaded = 1;

  else

  ptr = NULL;

  }

  if(!ptr && (tryload == LOAD_MUST_SUCCEED)) {

  exit_error(PARAMETER_PROBLEM,

  "Couldn't find match `%s'\n", name);

  }

  #endif

  if (ptr)

  ptr->used = 1;

  return ptr;

  }

  分析這個函數,不從開頭來看,先看這一段:

  if (!ptr && tryload != DONT_LOAD) {

  char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")

  + strlen(name)];

  sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);

  if (dlopen(path, RTLD_NOW)) {

  /* Found library. If it didn't register itself,

  maybe they specified target as match. */

  ptr = find_match(name, DONT_LOAD);

  if (!ptr)

  exit_error(PARAMETER_PROBLEM,

  "Couldn't load match `%s'\n",

  name);

  } else if (tryload == LOAD_MUST_SUCCEED)

  exit_error(PARAMETER_PROBLEM,

  "Couldn't load match `%s':%s\n",

  name, dlerror());

  }

  函數根據傳遞過來的match名稱,從指定位置,加載對應的共享庫,呵呵,這些共享庫的源碼,全部在Extensions目錄下邊:

  如果加載它們,那么其_init函數就會被調用。這個初始化函數用來向iptables_match全局結構注冊當前match的相關處理函數。(這樣,我們可以寫我們自己的用戶空間的擴展match處理工具了)。注冊好后,函數再來調用自己:

  ptr = find_match(name, DONT_LOAD);

  遞歸回來后,呵呵,就是開頭那一段了,我們需要從已經注冊好的全局結構中查找與當前match名稱相同的iptables_match成員,因為該成員中封裝了print函數,這樣就可以順利地輸出來了:

  比如,加載了libptc_tcp.so,它用來處理tcp的擴展,我們來看Extensions/libiptc_tcp.c:

  static

  struct iptables_match tcp

  = { NULL,

  "tcp",

  IPTABLES_VERSION,

  IPT_ALIGN(sizeof(struct ipt_tcp)),

  IPT_ALIGN(sizeof(struct ipt_tcp)),

  &help,

  &init,

  &parse,

  &final_check,

  &print,

  &save,

  opts };

  void

  _init(void)

  {

  register_match(&tcp);

  }

  構建了一個

  iptables_match結構,其間有其對應的所有用戶空間工具函數,如分析命令行、輸出、保存……

  然后,就調用register_match函數將其插入至全局結構iptables_match當中:

  void

  register_match(struct iptables_match *me)

  {

  struct iptables_match **i;

  if (strcmp(me->version, program_version) != 0) {

  fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n",

  program_name, me->name, me->version, program_version);

  exit(1);

  }

  if (find_match(me->name, DONT_LOAD)) {

  fprintf(stderr, "%s: match `%s' already registered.\n",

  program_name, me->name);

  exit(1);

  }

  if (me->size != IPT_ALIGN(me->size)) {

  fprintf(stderr, "%s: match `%s' has invalid size %u.\n",

  program_name, me->name, me->size);

  exit(1);

  }

  /* Append to list. */

  for (i = &iptables_matches; *i; i = &(*i)->next);

  me->next = NULL;

  *i = me;

  me->m = NULL;

  me->mflags = 0;

  }

  函數就是一個建立鏈表的過程。不進一步分析了。

通過文章的介紹,我們清楚的知道了iptables 的源碼情況,希望對大俠有所幫助!

【編輯推薦】

責任編輯:趙鵬 來源: 網絡轉載
相關推薦

2011-03-15 10:09:11

2014-08-26 11:11:57

AsyncHttpCl源碼分析

2011-05-26 10:05:48

MongoDB

2021-11-11 17:40:08

WatchdogAndroid源碼分析

2021-04-14 20:10:50

Netfileter Iptables 源碼

2011-05-26 16:18:51

Mongodb

2015-08-10 15:12:27

Java實例源碼分析

2020-11-19 07:41:51

ArrayBlocki

2021-03-23 09:17:58

SpringMVCHttpServletJavaEE

2011-08-16 09:34:34

Nginx

2021-09-06 10:34:48

Nacos復制源碼

2021-07-06 09:29:38

Cobar源碼AST

2021-08-09 07:58:36

Nacos 服務注冊源碼分析

2024-06-13 07:55:19

2020-11-25 14:28:56

DelayedWork

2021-05-17 09:50:06

Kubebuilde源碼CURD

2022-03-18 15:29:02

Harmony鴻蒙架構

2011-06-28 16:18:24

Qt QObject

2010-02-06 13:28:31

Android源碼

2015-07-27 14:57:32

OpenFlow協議Ryu
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品美女www | 天天色天天射天天干 | a级网站 | 一区二区高清 | 国产99久久久久 | 综合久久综合久久 | 欧美4p | 国产精品黄色 | a级大片| 国产精品揄拍一区二区 | 日本免费一区二区三区四区 | 国产高清在线精品一区二区三区 | 天天夜夜操 | 亚洲精品久久久蜜桃 | 91麻豆精品国产91久久久久久 | 国产二区av| 成年人网站在线观看视频 | 成人在线免费看 | www视频在线观看 | 成人毛片视频免费 | 亚洲一区二区不卡在线观看 | 久久久免费电影 | 一区二区三区在线免费观看视频 | 四虎影院免费在线 | 成人欧美一区二区三区视频xxx | 拍真实国产伦偷精品 | 色偷偷人人澡人人爽人人模 | 精品一区二区三区在线播放 | 久久小视频 | 亚洲 欧美 另类 日韩 | 在线免费小视频 | 国产欧美久久精品 | 天天操天天舔 | 在线观看免费观看在线91 | 蜜桃黄网 | 精品入口麻豆88视频 | 国产亚洲精品久久情网 | 国产人久久人人人人爽 | 国产精品99久久久久久动医院 | 丁香综合| 国产精品久久久久久久午夜片 |