strongswan #3 charon 초기화, plugin loading
Charon 프로세스 시작
1. starter_start_charon()을 호출하여 fork()를 통해 Charon 프로세스를 시작한다.
2. starter_stroke_add_conn()을 통해 stroke_msg 객체(type을 STR_ADD_CONN으로 설정)를 생성하여 연결 설정 메시지를 준비한다.
- URI를 /etc/ipsec.d/run/charon.ctl로 설정하고 스트림을 생성
- stream_service_create_unix 호출하여 Unix 도메인 소켓을 생성하고 연결
Charon 초기화
INIT(this,
.public = {
.initialize = _initialize,
.start = _start,
.load_loggers = _load_loggers,
.set_default_loggers = _set_default_loggers,
.set_level = _set_level,
.bus = bus_create(),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
}
);
- initialize, start, load_loggers, set_default_loggers, set_level과 같은 초기화 함수들이 설정
- kernel, attributes, controller, backend 등의 매니저를 생성하고, 이를 public 구조체에 할당하여 Charon의 각 기능을 초기화
플러그인 로드
Strongswan에는 구성 단계(strongswan.conf 실행) 중에 활성화하거나 비활성화할 수 있는 많은 플러그인이 있다. 컴파일 후 플러그인은 동적으로 로드된다.
stroke 플러그인을 예로 들어보자.
charon 프로세스는 시작 시 charon->initialize() 함수에서 strongswan.conf 파일을 읽어 플러그인 목록을 파싱한다.
/* initialize daemon */
if (!charon->initialize(charon,
lib->settings->get_str(lib->settings, "charon.load", PLUGINS)))
{
DBG1(DBG_DMN, "initialization failed - aborting charon");
goto deinit;
}
charon이 strongswan.conf 파일의 load 항목을 확인하여 나열된 플러그인을 동적으로 로드하게된다.
Charon
초기화
1. library_init() 호출
2. libcharon_init() 호출
3. command line argument 파싱
4. charon->initialize() 호출
- charon->initialize() -> lib->plugins->load()
- initialize() 함수가 호출되면 load_plugins() 함수를 통해 모든 플러그인을 로드한다.
// charon/charon.c main
charon->initialize(charon, lib->settings->get_str(lib->settings, "charon.load", PLUGINS))
// libcharon/daemon.c initialize
if (!lib->plugins->load(lib->plugins, plugins))
{
return FALSE;
}
Plugin Load
- load_plugins() -> load_features() -> load_provided() -> load_feature() -> plugin_feature_load() -> reg->arg.cb.f()의 호출 흐름에서 sender_receiver_cb()와 sa_managers_cb() 콜백 함수가 호출된다.
- load_plugins ()
- 각 플러그인을 검색하여 find_plugin() 함수를 통해 찾고, 플러그인 라이브러리 파일 libstrongswan-%s.so를 로드한다.
- %s는 플러그인의 이름을 나타냄, stroke 플러그인이라면 파일 이름은 libstrongswan-stroke.so
- load_plugin()
- load_plugin()은 dlopen()을 호출하여 플러그인 라이브러리 파일을 열고, %s-plugin-create()라는 생성자 함수를 호출하여 플러그인 객체를 만든다.
- register_features()
- 각 플러그인은 get_features() 함수를 통해 해당 플러그인이 제공하는 기능 정보를 가져온다.
- 이를 통해 각 플러그인의 특징(feature) 정보를 등록한다.
count = entry->plugin->get_features(entry->plugin, &feature);
for (i = 0; i < count; i++)
{
switch (feature->kind)
{
case FEATURE_PROVIDE:
lookup.feature = feature;
registered = this->features->get(this->features, &lookup);
if (!registered)
{
INIT(registered,
.feature = feature,
.plugins = linked_list_create(),
);
this->features->put(this->features, registered, registered);
}
INIT(provided,
.entry = entry,
.feature = feature,
.reg = reg,
.dependencies = count - i,
);
registered->plugins->insert_last(registered->plugins,
provided);
entry->features->insert_last(entry->features, provided);
break;
case FEATURE_REGISTER:
case FEATURE_CALLBACK:
reg = feature;
break;
default:
break;
}
feature++;
}
- load_features()
- 각 플러그인이 제공하는 기능을 로드한다.
- load_provided()와 load_feature()를 거쳐 plugin_feature_load() 함수가 호출된다.
- plugin_feature_load()는 플러그인 등록 정보에서 콜백 함수(reg->arg.cb.f())를 호출한다.
예를 들어, stroke 플러그인의 경우 register_stroke() 콜백 함수가 호출된다.
register_stroke()는 stroke_socket_create() 함수를 통해 starter와 통신하기 위한 Unix 소켓을 생성한다.
이후 this->service = lib->streams->create_service를 호출하여 stream_service 객체를 생성하고, this->service->on_accept()을 호출하여 on_accept() 함수를 콜백으로 설정한다.
on_accept() 함수는 lib->watcher를 사용해 processor->job[] 대기열에 작업을 추가하고, 스레드에서 실행되도록 대기한다.
sender_receiver_cb() 함수는 receiver와 sender를 생성한다.
- receiver_create()
- 수신 전용 객체인 private_callback_job_t를 생성
- callback job으로 receive_packets() 함수가 설정
private_callback_job_t *receiver = receiver_create();
receiver->job = (callback_job_cb_t)receive_packets;
- sender_create()
- 송신 전용 객체인 private_sender_t를 생성
- callback job으로 send_packets() 함수가 설정
private_sender_t *sender = sender_create();
sender->job = (callback_job_cb_t)send_packets;
이렇게 생성된 receiver와 sender의 콜백 함수는 process->set_threads() 호출 시 스레드에 할당된다.
이후 생성된 스레드가 receive_packets()와 send_packets()를 실행한다.