네트워크 보안/네트워크

strongswan #5 IKE SA INIT, IKE_AUTH, CHILD_SA 설정

uzguns 2024. 11. 10. 09:13

IKE SA 초기화 과정에서 task_manager, sender, receiver가 협력하여 패킷을 생성하고 전송하는 구조를 통해 IKEv2 초기화 패킷이 전송된다.

 

IKE SA 초기화

 

1. initiate_execute()

  • initiate_execute() 함수는 IKE SA 객체를 생성한 후, IKE SA의 initiate() 메서드를 호출하여 초기화 과정의 시작점이 된다.
METHOD(job_t, initiate_execute, job_requeue_t,
    interface_job_t *job)
{
    ike_sa_t *ike_sa;
    interface_listener_t *listener = &job->listener;
    peer_cfg_t *peer_cfg = listener->peer_cfg;

    ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager, peer_cfg);
    if (!ike_sa)
    {
        // 오류 처리
        return JOB_REQUEUE_NONE;
    }
    return ike_sa->initiate(ike_sa);
}
  • initiate_execute()는 IKE SA 객체를 peer_cfg 설정에 따라 checkout_by_config()를 통해 가져온다.
  • 성공적으로 ike_sa가 생성되면, ike_sa->initiate()를 호출하여 초기화 작업을 시작한다.

2. IKE SA의 initiate()

  • IKE SA의 초기화를 진행하며, 로컬과 원격 주소를 읽어들이고 다음 작업으로 task_manager의 initiate() 메서드를 호출한다.
METHOD(ike_sa_t, initiate, status_t,
    private_ike_sa_t *this)
{
    this->local_addr = get_local_address(this);
    this->remote_addr = get_remote_address(this);
    
    return this->task_manager->initiate(this->task_manager);
}
  • initiate()는 get_local_address()와 get_remote_address() 함수를 호출하여 IKE SA의 local_addr와 remote_addr 값을 설정한다.
  • 이후 task_manager의 initiate() 메서드를 호출하여 메시지 생성 및 전송 절차를 시작한다.

3. task_manager_v2.c의 initiate()

  • 메시지를 빌드하고 패킷 데이터를 생성하며, 마지막으로 retransmit() 메서드를 호출하여 패킷을 전송한다.
METHOD(task_manager_t, initiate, status_t,
    private_task_manager_t *this)
{
    message_t *message = build_message(this);
    if (!generate_message(this, message, &this->initiating.packets))
    {
        return FAILED;
    }
    return this->retransmit(this, message->id);
}
  • build_message()를 통해 전송할 메시지를 생성한다.
  • 이후 generate_message() 함수를 통해 메시지를 기반으로 패킷 데이터를 생성한 뒤,
  • retransmit() 함수를 호출하여 초기화 패킷을 전송

4. retransmit()

  • 초기화 과정에서 생성된 IKE SA 초기화 패킷을 전송
METHOD(task_manager_t, retransmit, status_t,
    private_task_manager_t *this, uint32_t message_id)
{
    // 패킷을 전송하기 위한 준비 작업
    send_packets(this);
    return SUCCESS;
}
  • send_packets()를 호출하여 준비된 패킷을 네트워크로 전송하는 역할
  • 이 단계에서 IKE SA INIT 패킷이 실제로 전송된다.

5. send_packets() 함수에서 charon->sender->send() 호출

  • charon->sender->send()를 호출하여 패킷 전송을 담당하는 sender 객체에 패킷 전송 작업을 위임
void send_packets(task_manager_t *this)
{
    packet_t *packet = get_packet(this); // 준비된 패킷을 가져옴
    charon->sender->send(charon->sender, packet);
}
  • get_packet()을 통해 전송할 패킷을 가져오고, charon->sender->send()를 호출하여 sender 객체에 패킷을 전달

6. charon->sender->send() 메서드 및 sender_receiver_cb() 콜백

  • charon->sender->send(): sender_receiver_cb() 콜백 함수에 의해 생성된 sender 객체를 통해 패킷을 네트워크로 전송
  • sender_receiver_cb(): load_plugins() 과정에서 sender와 receiver 객체를 생성
METHOD(sender_t, send, void,
    private_sender_t *this, packet_t *packet)
{
    this->mutex->lock(this->mutex);
    this->list->insert_last(this->list, packet);
    this->got->signal(this->got);
    this->mutex->unlock(this->mutex);
}
  • send() 메서드는 packet을 sender의 list(대기열)에 추가하고 signal을 통해 전송을 알림
  • 이때 sender_receiver_cb()에 의해 sender 객체가 생성되어 있으므로, 해당 객체가 패킷을 받아 네트워크로 전송

 

요약

 

  • initiate_execute() 함수가 IKE SA 객체를 생성하고 ike_sa->initiate() 호출.
  • ike_sa->initiate()는 로컬과 원격 주소를 설정하고 task_manager->initiate() 호출.
  • task_manager->initiate()는 메시지와 패킷을 생성하고 retransmit() 호출.
  • retransmit()는 send_packets()를 호출하여 패킷 전송을 시작.
  • send_packets()가 charon->sender->send()를 호출하여 sender 객체가 패킷을 전송.
  • sender 객체는 load_plugins()의 sender_receiver_cb() 콜백을 통해 생성됨.

 

 

IKE_SA_INIT 메시지 송신

1. Socket Manager의 등록 과정

  • socket_manager.c의 sender()가 this->socket->send()를 호출하여 패킷을 송신
  • this->socket은 add_socket()을 통해 socket_default_plugin의 feature에서 등록된 소켓을 가리킴
  • charon->socket->add_socket()은 socket_default_socket_create() 콜백을 호출하며,
  • 이 함수는 private_socket_default_socket_t 객체를 생성하고 UDP 소켓을 만든다.

2. 송신 과정

  • send_msg_v4() 또는 send_msg_v6()가 호출되어 IKE_SA_INIT 패킷이 목적지로 전송한다.
  • 이 과정에서 sendmsg() 시스템 호출을 통해 IKE_SA_INIT 메시지가 전달된다.

 

IKE_SA_INIT 응답 수신 및 처리

  • charon.ctl 소켓으로 부터 receiver 에서 패킷 수신
    • receiver는 수신된 IKE 메시지를 소켓을 통해 듣고, 새로운 메시지를 받을 때마다 이를 처리할 "process_message_job"을 생성하여 processor의 Jobqueue에 추가한다.
      • libcharon/daemon.c 의 initialize 에서 패킷을 받을 때의 이벤트로 sender_receiver_cb를 할당
      • sender_receiver_cb() -> this->public.receiver = receiver_create();
    • receiver는 독립적인 스레드가 없으며, "receiver" 작업으로 존재하여 Processor가 정기적으로 이 작업을 실행하도록 되어 있다.
      • lib->processor->queue_job(lib->processor,
        callback_job_create_with_prio((callback_job_cb_t)receive_packets, this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
    • 패킷을 처리한 후 process_message 작업을 위한 job을 생성하여 큐에 추가 한다.
      • recevie_packets() -> process_message_job_create()
      • process_message_job_create()  (lib->scheduler->schedule_job_mx(lib->scheduler, process_message_job_create(message), this->receive_delay))
  • ike_sa => processor(task manager) 에서 메세지 처리
    •  process_message.c의 execute()에서 IKE_SA를 찾아 ike_sa->process_message()를 호출한다.
      • job->execute 로 ike_sa->process_message(ike_sa, this->message) 처리
    • ike_sa->process_message()는   task_manager의 process_message 호출
      • ike_sa->process_message 에서 this->task_manager->process_message(this->task_manager, message)
  • Task Manager에서 응답 처리
    • task_manager는 process_message(), process_response, 무조건 둘중의 하나만 호출하게 된다.
      • msg->get_reqest의 값이 true면 process_message
      • false이면 process_response 쪽으로 호출
    • process_message()에서 메시지를 분석하고, process_response()를 호출하여 응답을 처리한다.
    • process_response()는 이전에 전송한 IKE_SA_INIT 메시지에 대한 응답 task를 pre_process 및 process 단계를 통해 실행한다.
    • process_request() (*1) -> build_response() -> 각 task->build 후 상태 return

 

IKE_INIT

queue_ike() 메서드를 통해 초기화 시 필요한 작업을 미리 준비하고, process_message()는 각 단계에서 작업을 실행하여 성공 또는 추가 작업 요구를 처리한다.

1. queue_ike

METHOD(task_manager_t, queue_ike, void, private_task_manager_t *this)
{
    if (!has_queued(this, TASK_IKE_INIT))
    {
        queue_task(this, (task_t*)ike_init_create(this->ike_sa, TRUE, NULL));
    }
    if (!has_queued(this, TASK_IKE_AUTH))
    {
        queue_task(this, (task_t*)ike_auth_create(this->ike_sa, TRUE));
    }
}
  • TASK_IKE_INIT 및 TASK_IKE_AUTH 같은 여러 IKE 작업들을 큐에 추가한다.
  • 이 작업들은 IKE_SA_INIT 단계에서 실행되어 초기화와 인증 과정을 진행한다.

2. process_message() 

enumerator = array_create_enumerator(this->active_tasks);
while (enumerator->enumerate(enumerator, &task))
{
    switch (task->process(task, message))
    {
        case SUCCESS:
            array_remove_at(this->active_tasks, enumerator);
            task->destroy(task);
            break;
        case NEED_MORE:
            break;
        default:
            break;
    }
}
  • 활성 작업 목록(active_tasks)에서 각 작업을 처리
  • 작업이 성공적으로 완료되면 목록에서 제거하고, 추가 작업이 필요하면 유지

3. TASK_IKE_INIT의 완료

  • TASK_IKE_INIT 작업이 성공적으로 완료되면 SUCCESS를 반환하고, active_tasks 목록에서 제거

4. IKE_AUTH 단계로 전환

  • TASK_IKE_AUTH 작업은 IKE 인증을 위한 작업으로 NEED_MORE 상태를 반환할 수 있다.
  • 이는 추가 메시지 교환이 필요함을 나타내며, 다음 IKE_AUTH 단계로 전환한다.

 

IKE_AUTH

1. IKE SA 초기화 (ike_sa->initiate() 호출): initiate_execute() 함수에서 IKE SA 객체를 생성하고 ike_sa->initiate()를 호출하여 초기화가 시작

 

2. Task 큐 설정 (queue_ike() 호출): task_manager가 queue_ike() 메서드를 호출하여 TASK 목록을 IKE SA 초기화에 필요한 순서로 큐에 삽입합니다. 초기 큐에 추가되는 TASK로는 TASK_IKE_INIT, TASK_IKE_AUTH, TASK_IKE_CONFIG 등이 있습니다.

 

3. IKE AUTH 메시지 생성 및 암호화: generate_message() 함수에서 IKE_AUTH 데이터를 생성하며, 이 과정에서 encrypt 플래그가 활성화됩니다. 다음 코드에서는 암호화된 페이로드 타입을 적절히 설정하고 이를 다음 타입으로 설정합니다.

 

4. 메시지 전송 (send_packets()): 생성된 IKE_AUTH 패킷은 charon->sender->send() 메서드를 통해 전송됩니다. 이 메서드는 송신 큐에 패킷을 삽입하고, 각 송신 패킷을 charon->socket->send()를 통해 실제로 네트워크로 전송하게 됩니다.

 

5. IKE_AUTH 응답 수신 및 처리: 상대방으로부터 IKE_AUTH 응답을 수신하면 receive_packets()가 이 패킷을 수신하고 process_message()에서 패킷을 해독한 후 process_response()를 호출하여 응답을 처리합니다. IKE_AUTH 응답이 성공하면 IKE SA 상태는 IKE_ESTABLISHED로 변경됩니다.

 

6. Virtual IP 설정: TASK_IKE_CONFIG 처리 시 IKE_AUTH 후에 가상 IP 설정이 완료됩니다. 이 과정은 kernel과 통신하여 IPSEC 터널을 설정하는 작업이 수행됩니다.

 

7. CHILD SA 준비: IKE SA가 IKE_ESTABLISHED 상태로 설정된 후 다음 단계로 TASK_CHILD_CREATE 작업이 활성화되어 CHILD SA를 설정합니다.

 

8. IKE_AUTH 처리

  • TASK_IKE_AUTH 작업이 성공적으로 처리되면, active_tasks에서 해당 작업을 제거
  • 이어서 TASK_IKE_CONFIG 작업을 처리하며, 이 과정에서 가상 IP(Virtual IP)를 설정하고, 커널에 IP를 구성
  •  

 

CHILD SA 생성

 

 

1. 스레드 생성

  • thread_create((thread_main_t)process_jobs, worker)로 새로운 스레드를 생성하여 process_jobs 함수가 실행되도록 설정
INIT(worker, .processor = this, );
worker->thread = thread_create((thread_main_t)process_jobs, worker);
if (worker->thread) {
    this->threads->insert_last(this->threads, worker);
    this->total_threads++;
}

 

  • INIT(worker, .processor = this, ): worker 객체를 초기화하고 processor에 현재 객체(this)를 할당
  • worker->thread = thread_create((thread_main_t)process_jobs, worker);: process_jobs 함수를 실행하는 스레드를 생성하여 worker->thread에 할당
  • insert_last: 생성한 스레드를 this->threads 리스트에 추가
  • 이 스레드는 lib->processor->set_threads()에서 DEFAULT_THREADS (기본값 16)개가 생성된다.

2. 송수신 객체 생성

  • sender_receiver_cb()에서 receiver_create() 및 sender_create()를 통해 패킷 송수신을 담당하는 객체들이 생성된다.
receiver_create();
sender_create();
  • receive_packets()와 send_packets() 콜백 함수가 각각 송수신 작업을 담당하도록 설정된다.

 

3. 작업(job) 큐

  • queue_job() 함수를 통해 작업을 lib->processor의 작업 큐에 넣는다.
lib->processor->queue_job(
    lib->processor,
    (job_t*)callback_job_create_with_prio((callback_job_cb_t)send_packets, this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL)
);
  • queue_job: send_packets 작업을 작업 큐에 추가
  • callback_job_create_with_prio: 우선순위 JOB_PRIO_CRITICAL로 작업을 생성
  • 예를 들어 receive_packets()와 send_packets() 작업들이 PROVIDE 'libcharon-receive'에 의해 큐에 추가됨
  • 수신 측도 유사하게 설정
lib->processor->queue_job(
    lib->processor,
    (job_t*)callback_job_create_with_prio((callback_job_cb_t)receive_packets, this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL)
);

 

4. process_job

  • 스레드는 process_jobs() 함수에서 작업 큐에서 작업을 꺼내어 실행
void process_jobs(void *arg) {
    job_t *job;
    while ((job = this->get_job(this)) != NULL) {
        job->execute(job);
    }
}
  • this->get_job(this): 작업 큐에서 작업을 가져옴
  • job->execute(job);: 가져온 작업을 실행한다. 이때 receive_packets와 send_packets 콜백 함수가 호출된다.

 

5. IKE SA 및 CHILD SA 관리자 생성

 

SA Manager는 sa_managers_cb() 콜백 함수에서 ike_sa_manager와 child_sa_manager를 생성한다.

  • sa_managers_cb() 콜백을 통해 ike_sa_manager와 child_sa_manager 객체들이 생성
  • 이들은 IKE 및 CHILD 보안 연결의 생성 및 관리를 담당한다.
  • ike_sa_manager_create(): private_ike_sa_manager_t 객체를 생성하여 IKE SA 관리를 담당
  • child_sa_manager_create(): private_child_sa_manager_t 객체를 생성하여 CHILD SA 관리를 담당

 

이러한 구조를 통해 libcharon은 여러 스레드로 패킷 송수신과 IKE/CHILD SA 관리를 병렬로 수행하며, IPsec 연결을 설정하고 유지하는 역할을 한다.