本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源: http://yfydz.cublog.cn

8. 事件处理

8.1 概述

pluto使用"事件"的方式来定义各种超时,将超时操作作为一个未来将要发生的事件。当进入IKE连接中的各种状态时,需要定义对方无响应的超时处理情况,就将这种超时操作作为一个事件加入到系统的事件链表中,然后将最近将发生的一个事件的事件作为select系统调用的超时(server.c),如果没有数据触发select,select将超时,从而从事件队列中取出该事件进行相关的操作;而如果select被触发,不需要超时操作了,需要将相关事件从事件链表中删除。这和Windows的事件定义不同的,Windows的事件是真正发生的事件,如键盘鼠标的输入等,而pluto的事件是实际没有真正事件(如数据包的到来)发生超时的时候才处理的虚拟事件。

通过事件的实现,pluto实现了自己的定时器处理,相关函数在programs/pluto/timer.c中定义。

8.2 事件结构
/* programs/pluto/timer.h */
struct event
{
// 事件发生时间,秒为单位
    time_t          ev_time;
// 事件类型
    enum event_type ev_type;        /* Event type */
// 状态指针,定义了该事件的状态
    struct state   *ev_state;       /* Pointer to relevant state (if any) */
// 事件链表中的下一项
    struct event   *ev_next;        /* Pointer to next event */
};

8.3 基本函数

8.3.1 获取当前时间

// 基本就是time(3)函数, 但考虑了系统时间发生停顿等异常的情况,
// 使得该函数的返回值肯定是递增的
time_t
now(void)
{
// 这两个数都是静态量, 用于处理时间异常的情况
// last_time保存上次调用该函数的时间
    static time_t delta = 0
 , last_time = 0;
// 获取系统时间
    time_t n = time((time_t)NULL);
    passert(n != (time_t)-1);
// 如果上次调用时间还超过当前时间,系统时间发生后向调整了
    if (last_time > n)
    {
 openswan_log("time moved backwards %ld seconds", (long)(last_time - n));
// 记录这种偏移值
 delta += last_time - n;
    }
    last_time = n;
// 对返回值进行时间调整
    return n + delta;
}

8.3.2 事件调度

// 该函数就是定义状态的超时处理事件,然后添加到系统事件链表
// tm是超时时间
void
event_schedule(enum event_type type, time_t tm, struct state *st)
{
// 分配事件结构
    struct event *ev = alloc_thing(struct event, "struct event in event_schedule()");
    passert(tm >= 0);
// 事件类型
    ev->ev_type = type;
// 事件发生时间: 当前时间加超时时间
    ev->ev_time = tm + now();
// 定义该事件的状态, 可能为空
    ev->ev_state = st;
    /* If the event is associated with a state, put a backpointer to the
     * event in the state object, so we can find and delete the event
     * if we need to (for example, if we receive a reply).
     */
    if (st != NULL)
    {
// 事件非空时,将事件指针和状态关联起来
            if(type == EVENT_DPD || type == EVENT_DPD_TIMEOUT)
            {
                    passert(st->st_dpd_event == NULL);
// 如果是DPD事件, 将其赋值到状态的DPD事件指针
                    st->st_dpd_event = ev;
            } else {
                    passert(st->st_event == NULL);
// 否则赋值到状态的事件指针
                    st->st_event = ev;
            }
    }
    DBG(DBG_CONTROL,
 if (st == NULL)
     DBG_log("inserting event %s, timeout in %lu seconds"
  , enum_show(&timer_event_names, type), (unsigned long)tm);
 else
     DBG_log("inserting event %s, timeout in %lu seconds for #%lu"
  , enum_show(&timer_event_names, type), (unsigned long)tm
  , ev->ev_state->st_serialno));
// 添加到系统事件链表
    if (evlist == (struct event *) NULL
    || evlist->ev_time >= ev->ev_time)
    {
// 事件表为空或事件时间比链表中所有时间都更近, 作为链表头
 ev->ev_next = evlist;
 evlist = ev;
    }
    else
    {
 struct event *evt;
// 根据事件发生时间将事件插入到链表中合适位置, 链表头是最先发生的,链表尾是最后发生的
// 先查找链表中合适位置
 for (evt = evlist; evt->ev_next != NULL; evt = evt->ev_next)
     if (evt->ev_next->ev_time >= ev->ev_time)
  break;
#ifdef NEVER /* this seems to be overkill */
 DBG(DBG_CONTROL,
     if (evt->ev_state == NULL)
  DBG_log("event added after event %s"
      , enum_show(&timer_event_names, evt->ev_type));
     else
  DBG_log("event added after event %s for #%lu"
      , enum_show(&timer_event_names, evt->ev_type)
      , evt->ev_state->st_serialno));
#endif /* NEVER */
// 插入节点
 ev->ev_next = evt->ev_next;
 evt->ev_next = ev;
    }
}
 
8.3.3 获取下一个事件发生时间

也就是计算select()需要的超时是多少
/*
 * Return the time until the next event in the queue
 * expires (never negative), or -1 if no jobs in queue.
 */
long
next_event(void)
{
    time_t tm;
// 事件链表空, 返回-1表示不需要有超时处理
    if (evlist == (struct event *) NULL)
 return -1;
// 获取当前时间
    tm = now();
    DBG(DBG_CONTROL,
 if (evlist->ev_state == NULL)
     DBG_log("next event %s in %ld seconds"
  , enum_show(&timer_event_names, evlist->ev_type)
  , (long)evlist->ev_time - (long)tm);
 else
     DBG_log("next event %s in %ld seconds for #%lu"
  , enum_show(&timer_event_names, evlist->ev_type)
  , (long)evlist->ev_time - (long)tm
  , evlist->ev_state->st_serialno));
// 如果链表头元素的时间小于当前时间,说明当前还有数据需要处理,返回0表示
// 此时不执行select函数,而是直接进行事件处理,由于事件定义是按秒为单位,
// 所以同一秒内发生多个事件是很可能的;
// 如果链表头元素的时间大于当前时间,返回差值作为select()函数的超时时间
    if (evlist->ev_time - tm <= 0)
 return 0;
    else
 return evlist->ev_time - tm;
}

8.3.4 删除事件

// 当在超时发生前状态收到相应的包不需要该超时处理时, 需要删除该状态原来定义事件
void
delete_event(struct state *st)
{
// 状态结构不为空才有删除意义
    if (st->st_event != (struct event *) NULL)
    {
 struct event **ev;
// 遍历事件表
 for (ev = &evlist; ; ev = &(*ev)->ev_next)
 {
     if (*ev == NULL)
     {
  DBG(DBG_CONTROL, DBG_log("event %s to be deleted not found",
      enum_show(&timer_event_names, st->st_event->ev_type)));
  break;
     }
// 直接比较事件结构本身的地址是否相同
     if ((*ev) == st->st_event)
     {
// 找到,从链表断开
  *ev = (*ev)->ev_next;
// 如果是重传事件,标志清零
  if (st->st_event->ev_type == EVENT_RETRANSMIT)
      st->st_retransmit = 0;
// 释放空间, 状态结构中的事件指针清空
  pfree(st->st_event);
  st->st_event = (struct event *) NULL;
  break;
     }
 }
    }
}

8.3.5 删除DPD事件

// 和前一节删除函数结构上完全相同, 只是比较时使用的是状态的DPD事件指针来查找事件
void
_delete_dpd_event(struct state *st, const char *file, int lineno)
{
    DBG(DBG_DPD|DBG_CONTROL
 , DBG_log("state: %ld requesting event %s to be deleted by %s:%d"
    , st->st_serialno
    , (st->st_dpd_event!=NULL
     ? enum_show(&timer_event_names, st->st_dpd_event->ev_type)
     : "none")
    , file, lineno));
 
    if (st->st_dpd_event != (struct event *) NULL)
    {
        struct event **ev;
        for (ev = &evlist; ; ev = &(*ev)->ev_next)
        {
            if (*ev == NULL)
            {
                DBG(DBG_DPD|DBG_CONTROL
      , DBG_log("event %s to be deleted not found",
         enum_show(&timer_event_names
     , st->st_dpd_event->ev_type)));
                break;
            }
// 用的是DPD事件指针来查找
            if ((*ev) == st->st_dpd_event)
            {
                *ev = (*ev)->ev_next;
                pfree(st->st_dpd_event);
                st->st_dpd_event = (struct event *) NULL;
                break;
            }
        }
    }
}

8.3.6 向whack输出当前所有事件

void
timer_list(void)
{
    time_t tm;
// 事件链表头
    struct event *ev = evlist;
    int type;
    struct state *st;
// 链表为空, 返回
    if (ev == (struct event *) NULL)    /* Just paranoid */
    {
 whack_log(RC_LOG, "no events are queued");
 return;
    }
// 获取当前时间
    tm = now();
    whack_log(RC_LOG, "It is now: %ld seconds since epoch", (unsigned long)tm);
// 遍历事件表
    while(ev) {
 type = ev->ev_type;
 st = ev->ev_state;
// 输出whack日志
 whack_log(RC_LOG, "event %s is schd: %ld (in %lds) state:%ld"
    , enum_show(&timer_event_names, type)
    , (unsigned long)ev->ev_time
    , (unsigned long)(ev->ev_time - tm)
    , st != NULL ? (long signed)st->st_serialno : -1);
 if(st && st->st_connection) {
     whack_log(RC_LOG, "    connection: \"%s\"", st->st_connection->name);
 }
 ev = ev->ev_next;
    }
}

8.4 事件处理

// 从事件链表中取出第一个事件进行处理, 这是在select()超时发生时进行的处理
// 这个函数不需要返回值
void
handle_timer_event(void)
{
    time_t tm;
// 事件头
   struct event *ev = evlist;
    int type;
    struct state *st;
    ip_address peer;
// 事件链表空, 返回
    if (ev == (struct event *) NULL)    /* Just paranoid */
    {
 DBG(DBG_CONTROL, DBG_log("empty event list, yet we're called"));
 return;
    }
// 事件类型和对应的状态
    type = ev->ev_type;
    st = ev->ev_state;
    tm = now();
// 如果事件时间还没到, 返回, 也算一种异常了
    if (tm < ev->ev_time)
    {
 DBG(DBG_CONTROL, DBG_log("called while no event expired (%lu/%lu, %s)"
     , (unsigned long)tm, (unsigned long)ev->ev_time
     , enum_show(&timer_event_names, type)));
 /* This will happen if the most close-to-expire event was
  * a retransmission or cleanup, and we received a packet
  * at the same time as the event expired. Due to the processing
  * order in call_server(), the packet processing will happen first,
  * and the event will be removed.
  */
 return;
    }
// 将链表头节点从链表中断开
    evlist = evlist->ev_next;  /* Ok, we'll handle this event */
    DBG(DBG_CONTROL, DBG_log("handling event %s"
        , enum_show(&timer_event_names, type)));
    if(DBGP(DBG_CONTROL)) {
 if (evlist != (struct event *) NULL) {
     DBG_log("event after this is %s in %ld seconds"
      , enum_show(&timer_event_names, evlist->ev_type)
      , (long) (evlist->ev_time - tm));
 }
 else {
     DBG_log("no more events are scheduled");
 }
    
    }
    /* for state-associated events, pick up the state pointer
     * and remove the backpointer from the state object.
     * We'll eventually either schedule a new event, or delete the state.
     */
    passert(GLOBALS_ARE_RESET());
// 如果状态非空, 将其结构中的事件指针清空
    if (st != NULL)
    {
 struct connection *c;
// 状态对应的连接
 c = st->st_connection;
        if( type  == EVENT_DPD || type == EVENT_DPD_TIMEOUT)
        {
// 如果是DPD事件,清空的是状态中的DPD事件指针
                passert(st->st_dpd_event == ev);
                st->st_dpd_event = NULL;
        } else {
// 否则清空的是状态中的事件指针
     passert(st->st_event == ev);
     st->st_event = NULL;
        }
// 连接对方的IP地址, 是否会有连接指针为空的异常?
 peer = c->spd.that.host_addr;
 set_cur_state(st);
    }
// 根据事件类型进行相关处理
    switch (type)
    {
// 重协商密钥事件
 case EVENT_REINIT_SECRET:
     passert(st == NULL);
     DBG(DBG_CONTROL, DBG_log("event EVENT_REINIT_SECRET handled"));
// 重新初始化密钥
     init_secret();
     break;
#ifdef KLIPS
// 扫描当前的/proc/net/ipsec_eroute,检查是否有异常的eroute
// 这时定时周期操作
 case EVENT_SHUNT_SCAN:
     passert(st == NULL);
     scan_proc_shunts();
     break;
#endif
// 扫描连接是否活动, 执行DPD
// 这时定时周期操作
        case EVENT_PENDING_PHASE2:
     passert(st == NULL);
     connection_check_phase2();
     break;
 
// 周期性记录日志
 case EVENT_LOG_DAILY:
     daily_log_event();
     break;
// 重新发送数据,刚才发送的数据没有回应
 case EVENT_RETRANSMIT:
     /* Time to retransmit, or give up.
      *
      * Generally, we'll only try to send the message
      * MAXIMUM_RETRANSMISSIONS times.  Each time we double
      * our patience.
      *
      * As a special case, if this is the first initiating message
      * of a Main Mode exchange, and we have been directed to try
      * forever, we'll extend the number of retransmissions to
      * MAXIMUM_RETRANSMISSIONS_INITIAL times, with all these
      * extended attempts having the same patience.  The intention
      * is to reduce the bother when nobody is home.
      *
      * Since IKEv1 is not reliable for the Quick Mode responder,
      * we'll extend the number of retransmissions as well to
      * improve the reliability.
      */
     {
  time_t delay = 0;
  struct connection *c;
  passert(st != NULL);
// 状态对应连接
  c = st->st_connection;
  DBG(DBG_CONTROL, DBG_log(
      "handling event EVENT_RETRANSMIT for %s \"%s\" #%lu"
      , ip_str(&peer), c->name, st->st_serialno));
// 如果重新发送次数没超过最大数, 计算超时时间, 超时时间每次翻倍
// 超过的话超时时间就还是初始值0了
  if (st->st_retransmit < MAXIMUM_RETRANSMISSIONS)
      delay = EVENT_RETRANSMIT_DELAY_0 << (st->st_retransmit + 1);
  else if ((st->st_state == STATE_MAIN_I1 || st->st_state == STATE_AGGR_I1)
  && c->sa_keying_tries == 0
  && st->st_retransmit < MAXIMUM_RETRANSMISSIONS_INITIAL)
      delay = EVENT_RETRANSMIT_DELAY_0 << MAXIMUM_RETRANSMISSIONS;
  else if (st->st_state == STATE_QUICK_R1
  && st->st_retransmit < MAXIMUM_RETRANSMISSIONS_QUICK_R1)
      delay = EVENT_RETRANSMIT_DELAY_0 << MAXIMUM_RETRANSMISSIONS;
  if (delay != 0)
  {
// 超时时间非0
// 重发次数增加
      st->st_retransmit++;
      whack_log(RC_RETRANSMISSION
   , "%s: retransmission; will wait %lus for response"
   , enum_name(&state_names, st->st_state)
   , (unsigned long)delay);
// 发送和状态相关的数据包
      send_packet(st, "EVENT_RETRANSMIT", TRUE);
// 重新设置状态超时
      event_schedule(EVENT_RETRANSMIT, delay, st);
  }
  else
  {
// 超时为0, 也就是重发次数超过了最大数
//
      /* check if we've tried rekeying enough times.
       * st->st_try == 0 means that this should be the only try.
       * c->sa_keying_tries == 0 means that there is no limit.
       */
// 是否需要重新协商参数
      unsigned long try = st->st_try;
      unsigned long try_limit = c->sa_keying_tries;
      const char *details = "";
// 设置不同状态下的消息信息可记录到日志
      switch (st->st_state)
      {
      case STATE_MAIN_I3:
   details = ".  Possible authentication failure:"
       " no acceptable response to our"
       " first encrypted message";
   break;
      case STATE_MAIN_I1:
   details = ".  No response (or no acceptable response) to our"
       " first IKE message";
   break;
      case STATE_QUICK_I1:
   if (c->newest_ipsec_sa == SOS_NOBODY)
       details = ".  No acceptable response to our"
    " first Quick Mode message:"
    " perhaps peer likes no proposal";
   break;
      default:
   break;
      }
// 记录重传次数过多的日志
      loglog(RC_NORETRANSMISSION
   , "max number of retransmissions (%d) reached %s%s"
   , st->st_retransmit
   , enum_show(&state_names, st->st_state), details);
      if (try != 0 && try != try_limit)
      {
// 如果要重新协商
   /* A lot like EVENT_SA_REPLACE, but over again.
    * Since we know that st cannot be in use,
    * we can delete it right away.
    */
   char story[80]; /* arbitrary limit */
// 增加计数
   try++;
   snprintf(story, sizeof(story), try_limit == 0
       ? "starting keying attempt %ld of an unlimited number"
       : "starting keying attempt %ld of at most %ld"
       , try, try_limit);
// 释放whack接口
   if (st->st_whack_sock != NULL_FD)
   {
       /* Release whack because the observer will get bored. */
       loglog(RC_COMMENT, "%s, but releasing whack"
    , story);
       release_pending_whacks(st, story);
   }
   else
   {
       /* no whack: just log to syslog */
       openswan_log("%s", story);
   }
// 重协商操作
   ipsecdoi_replace(st, try);
      }
// 删除状态
      delete_state(st);
  }
     }
     break;
// SA替换事件
 case EVENT_SA_REPLACE:
 case EVENT_SA_REPLACE_IF_USED:
     {
  struct connection *c;
  so_serial_t newest;
  passert(st != NULL);
// 状态对应连接
  c = st->st_connection;
// 连接中最新状态序号
  newest = IS_PHASE1(st->st_state)
      ? c->newest_isakmp_sa : c->newest_ipsec_sa;
  if (newest != st->st_serialno
  && newest != SOS_NOBODY)
  {
// 状态非最新的而且非0, 基本正常情况
      /* not very interesting: no need to replace */
      DBG(DBG_LIFECYCLE
   , openswan_log("not replacing stale %s SA: #%lu will do"
       , IS_PHASE1(st->st_state)? "ISAKMP" : "IPsec"
       , newest));
  }
  else if (type == EVENT_SA_REPLACE_IF_USED
  && st->st_outbound_time <= tm - c->sa_rekey_margin)
  {
// 最新序号的状态, 但从时间还不需要替换
      /* we observed no recent use: no need to replace
       *
       * The sampling effects mean that st_outbound_time
       * could be up to SHUNT_SCAN_INTERVAL more recent
       * than actual traffic because the sampler looks at change
       * over that interval.
       * st_outbound_time could also not yet reflect traffic
       * in the last SHUNT_SCAN_INTERVAL.
       * We expect that SHUNT_SCAN_INTERVAL is smaller than
       * c->sa_rekey_margin so that the effects of this will
       * be unimportant.
       * This is just an optimization: correctness is not
       * at stake.
       *
       * Note: we are abusing the DBG mechanism to control
       * normal log output.
       */
      DBG(DBG_LIFECYCLE
   , openswan_log("not replacing stale %s SA: inactive for %lus"
       , IS_PHASE1(st->st_state)? "ISAKMP" : "IPsec"
       , (unsigned long)(tm - st->st_outbound_time)));
  }
  else
  {
// 进行状态替换操作
      DBG(DBG_LIFECYCLE
   , openswan_log("replacing stale %s SA"
       , IS_PHASE1(st->st_state)? "ISAKMP" : "IPsec"));
      ipsecdoi_replace(st, 1);
  }
// 删除状态相关的DPD事件
  delete_dpd_event(st);
// 重新设置SA超时
  event_schedule(EVENT_SA_EXPIRE, st->st_margin, st);
     }
     break;
// SA超时事件
 case EVENT_SA_EXPIRE:
     {
  const char *satype;
// 要删除状态对应的连接的最新状态序号
  so_serial_t latest;
  struct connection *c;
  passert(st != NULL);
// 状态对应连接
  c = st->st_connection;
// 检查状态阶段
  if (IS_PHASE1(st->st_state))
  {
// 第一阶段状态, 是ISAKMP的状态
      satype = "ISAKMP";
      latest = c->newest_isakmp_sa;
  }
  else
  {
// 第2阶段状态, 是IPSEC的状态
      satype = "IPsec";
      latest = c->newest_ipsec_sa;
  }
  if (st->st_serialno != latest)
  {
// 如果当前状态序号不是最新的, 基本是正常情况
      /* not very interesting: already superseded */
      DBG(DBG_LIFECYCLE
   , openswan_log("%s SA expired (superseded by #%lu)"
       , satype, latest));
  }
  else
  {
// 这时删除最新序号的状态, 有点异常了
      openswan_log("%s SA expired (%s)", satype
   , (c->policy & POLICY_DONT_REKEY)
       ? "--dontrekey"
       : "LATEST!"
   );
  }
     }
// 继续下面的case操作
     /* FALLTHROUGH */
// 状态删除
 case EVENT_SO_DISCARD:
     /* Delete this state object.  It must be in the hash table. */
// 释放消息摘要结构
     if(st->st_suspended_md) {
  release_md(st->st_suspended_md);
  st->st_suspended_md=NULL;
     }
// 删除状态
     delete_state(st);
     break;
        case EVENT_DPD:
// DPD事件处理
            dpd_event(st);
            break;
    
        case EVENT_DPD_TIMEOUT:
// DPD超时操作
            dpd_timeout(st);
            break;

#ifdef NAT_TRAVERSAL
 case EVENT_NAT_T_KEEPALIVE:
// NAT穿越保活操作,发送保活消息
     nat_traversal_ka_event();
     break;
#endif
    
        case EVENT_CRYPTO_FAILED:
// 加密失败, 删除状态
     DBG(DBG_CONTROL
  , DBG_log("event crypto_failed on state #%lu, aborting"
     , st->st_serialno));
     delete_state(st);
     break;
    
// 非法事件类型了
 default:
     loglog(RC_LOG_SERIOUS, "INTERNAL ERROR: ignoring unknown expiring event %s"
  , enum_show(&timer_event_names, type));
    }
// 删除事件结构
    pfree(ev);
    reset_cur_state();
}

...... 待续 ......