Tor源码文件分析 — Connection_OR

  作为连接类型中最重要的一种连接,OR连接对OP与OR之间,OR与OR之间的通信负全责。也就是说,OR连接的存在,是解决底层两机之间通信的必要条件。所以,有必要对OR连接的源码文件进行深入分析,并对OR连接先做简要介绍。后期,在介绍Tor系统的全部连接之时,会更加详细地介绍各种连接及他们的作用。

  OR连接,即Onion Router连接,是用以连接Tor系统内两结点的连接。该连接的主要功能包括:管理结点间TLS连接;管理活动链接;管理CELL的发送和接收等。OR连接处于Tor系统的最底层,其下层即为TLS层。所以,OR连接负责的主要部分就是可靠的CELL传递以及与其上层链接层的交互。

                DIR连接……            应用层

              ------------------------

                AP连接,EXIT连接……

              ------------------

                Circuit链路……           Tor协议层

              ------------------

                OR连接……          

              ------------------------

                TLS连接              传输层

  同时,在此处值得一提的是,Tor系统的应用连接AP,链路Circuit,OR连接三者之间的复用情况:(多AP连接复用Circuit,多Circuit复用OR连接)

               AP Stream 1    –>

               AP Stream 2    –>    Circuit 1    ->

               … …   3    –>

                           Circuit 2   ->    OR Connection 1

                           Circuit 3   ->

0. 全局变量

  1)orconn_identity_map

/** Map from identity digest of connected OR or desired OR to a connection_t
 * with that identity digest.  If there is more than one such connection_t,
 * they form a linked list, with next_with_same_id as the next pointer. */
static digestmap_t *orconn_identity_map = NULL;

  该全局变量是个链地址法处理冲突的哈希表,哈希表的键为OR的ID摘要,哈希表的值为OR连接链表(可能有多个OR连接连往同一个OR,或不同的OR可能有相同的ID摘要)。该哈希表所能提供的功能便是插入删除OR键值对。其主要用途是为OP建立链路第一跳与OR建立链接下一跳服务。详细可见下列代码:

// OP建立链路第一跳时使用本地OR连接复用服务
/** Start establishing the first hop of our circuit. Figure out what
 * OR we should connect to, and if necessary start the connection to
 * it. If we're already connected, then send the 'create' cell.
 * Return 0 for ok, -reason if circ should be marked-for-close. */
int
circuit_handle_first_hop(origin_circuit_t *circ)
{
  ......

  /* now see if we're already connected to the first OR in 'route' */
  log_debug(LD_CIRC,"Looking for firsthop '%s:%u'",
            fmt_addr(&firsthop->extend_info->addr),
            firsthop->extend_info->port);

  n_conn = connection_or_get_for_extend(firsthop->extend_info->identity_digest,
                                        &firsthop->extend_info->addr,
                                        &msg,
                                        &should_launch);

  if (!n_conn) { /* it's not open. create one */
    ......
  } else { /* it's already open. use it. */
    ......
  }

  return 0;
}
// OR扩展链路时使用本地OR连接复用服务
/** Take the 'extend' cell, pull out addr/port plus the onion
 * skin and identity digest for the next hop. If we're already connected,
 * pass the onion skin to the next hop using a create cell; otherwise
 * launch a new OR connection, and circ will notice when the
 * connection succeeds or fails.
 *
 * Return -1 if we want to warn and tear down the circuit, else return 0.
 */
int
circuit_extend(cell_t *cell, circuit_t *circ)
{
  ......
  n_conn = connection_or_get_for_extend(id_digest,
                                        &n_addr,
                                        &msg,
                                        &should_launch);

  if (!n_conn) { /* it's not open. create one */
    ......
    if (should_launch) {
      /* we should try to open a connection */
      n_conn = connection_or_connect(&n_addr, n_port, id_digest);
      ......
    }
    /* return success. The onion/circuit/etc will be taken care of
     * automatically (may already have been) whenever n_conn reaches
     * OR_CONN_STATE_OPEN.
     */
    return 0;
  }

  tor_assert(!circ->n_hop); /* Connection is already established. */
  circ->n_conn = n_conn;
  log_debug(LD_CIRC,"n_conn is %s:%u",
            n_conn->_base.address,n_conn->_base.port);

  if (circuit_deliver_create_cell(circ, CELL_CREATE, onionskin) < 0)
    return -1;
  return 0;
}

  2)broken_connection_counts

/** Map from a string describing what a non-open OR connection was doing when
 * failed, to an intptr_t describing the count of connections that failed that
 * way.  Note that the count is stored _as_ the pointer.
 */
static strmap_t *broken_connection_counts;

/** If true, do not record information in broken_connection_counts. */
static int disable_broken_connection_counts = 0;

  该部分全局变量是用来记录Tor系统错误信息的变量,是个字符串到整形数据的哈希表(对应表,无需链表解决冲突)。对该哈希表的添加删除操作,就可以记录或抹去系统中连接发生错误的相关状态信息。最终信息的报告位置为函数connection_or_report_broken_states。变量主要的处理函数如下:

/** Record that an OR connection failed in state. */
static void
note_broken_connection(const char *state)
{
  void *ptr;
  intptr_t val;
  if (disable_broken_connection_counts)
    return;

  if (!broken_connection_counts)
    broken_connection_counts = strmap_new();

  ptr = strmap_get(broken_connection_counts, state);
  val = (intptr_t)ptr;
  val++;
  ptr = (void*)val;
  strmap_set(broken_connection_counts, state, ptr);
}

/** Forget all recorded states for failed connections.  If
 * stop_recording is true, don't record any more. */
void
clear_broken_connection_map(int stop_recording)
{
  if (broken_connection_counts)
    strmap_free(broken_connection_counts, NULL);
  broken_connection_counts = NULL;
  if (stop_recording)
    disable_broken_connection_counts = 1;
}

  3)version

/** Array of recognized link protocol versions. */
static const uint16_t or_protocol_versions[] = { 1, 2, 3 };
/** Number of versions in or_protocol_versions. */
static const int n_or_protocol_versions =
  (int)( sizeof(or_protocol_versions)/sizeof(uint16_t) );

  该部分全局变量用于存储系统会使用到的协议版本号与协议版本数,基本是固定值,只有在新增版本时才会改变。引用处少且简单,所以暂时略去。

1. 全局变量控制函数

  connection_or_set_identity_digest

  connection_or_remove_from_identity_map

  connection_or_clear_identity_map

  ID摘要到OR连接的哈希表条目的增删及清除操作;

  note_broken_connection  <– 2  connection_or_note_state_when_broken  1 –>  connection_or_get_state_description

  clear_broken_connection_map

  connection_or_report_broken_states

  错误状态字符串到错误连接个数的哈希表(对应表)条目的增加,清除操作以及综合信息输出操作;

2. OR连接的Cell控制

  cell_pack

  cell_unpack

  本机cell与网络内传输的cell存在字节序上的差别,所以需要特地用两个分别的结构体进行表示和转换,简单称之为打包和解包;(网络:packed_cell_t;本机:cell_t)

  var_cell_pack_header

  var_cell_new

  var_cell_free

  变长cell的相关操作;

3. OR连接的读写控制

  connection_or_process_inbuf  –>  connection_or_process_cells_from_inbuf

  connection_or_reached_eof

  OR连接处理输入缓冲区内的数据,不同数据的处理方式不同,一般为cell数据或eof数据;

  connection_or_flushed_some    // 目的:从活动链接中获取更多的需要写出的数据

  connection_or_finished_flushing   

  connection_or_finished_connecting  // 目的:普通socket连接完成后,需要调用此函数进行下一步的TLS连接

  OR连接完成某些操作时调用的函数操作,操作包括完成输出部分数据,完成输出数据,完成连接等;

  connection_or_write_cell_to_buf

  connection_or_write_var_cell_to_buf

  将cell或者变长cell写入到OR输出缓冲区内等待输出;

4. OR连接的管理

  connection_or_about_to_close

  进行连接关闭前的扫尾工作,并关闭OR连接;

  connection_or_update_token_buckets

  重新设置OR连接的令牌桶;

  connection_or_digest_is_known_relay

  判断选定的OR是否为已知的可以使用的Relay服务器;

  connection_or_set_state_open

  结束TLS握手及OR握手之后,设置连接的状态为开启状态,并通知其他所有相关子系统;

5. OR连接的使用

  connection_or_connect

  发起到指定OR的OR连接,成功返回新建的OR连接结构体;其内容包括创建连接,开启socket连接,其后开启TLS连接等;

  connection_tls_start_handshake

  connection_tls_continue_handshake

  在OR连接开启了非阻塞的socket连接之后,系统根据读写规则,会在适当的时机开启OR连接的TLS握手过程;

  

  connection_init_or_handshake_state

  or_handshake_state_free

  or_handshake_state_record_cell

  or_handshake_state_record_var_cell

  在OR连接进行OR层握手时,handshake_state用于记录整个过程中的握手包的摘要信息,以保证握手正确;(握手的层次:socket握手,TLS握手,OR握手)

  connection_or_send_destroy

  connection_or_send_versions

  connection_or_send_versions

  connection_or_send_certs_cell

  connection_or_send_auth_challenge_cell

  connection_or_send_authenticate_cell  –>  connection_or_compute_authenticate_cell_body

  构造OR握手相关握手包,利用函数connection_or_write_cell_to_buf或者connection_or_write_var_cell_to_buf写入OR连接缓冲区等待输出;

  从整个以上OR连接的使用函数中就可以窥见整个OR连接创建的过程:

  1)建立OR连接;

  2)建立socket连接,进行socket握手;(三次握手)

  3)建立TLS连接,进行TLS握手;(TLS握手协议)

  4)进行OR握手,握手过程中要保证所有的握手包计算摘要的正确性;(OR握手协议参见tor_spec.txt)