strongswan #5 IKE SA INIT, IKE_AUTH, CHILD_SA 설정
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));
- lib->processor->queue_job(lib->processor,
- 패킷을 처리한 후 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))
- receiver는 수신된 IKE 메시지를 소켓을 통해 듣고, 새로운 메시지를 받을 때마다 이를 처리할 "process_message_job"을 생성하여 processor의 Jobqueue에 추가한다.
- 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)
- process_message.c의 execute()에서 IKE_SA를 찾아 ike_sa->process_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
- task_manager는 process_message(), process_response, 무조건 둘중의 하나만 호출하게 된다.
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 연결을 설정하고 유지하는 역할을 한다.