Prototype Ghost
귀신일지
Prototype Ghost
전체 방문자
오늘
어제
  • 분류 전체보기 (29)
    • Hacking (1)
      • CTF (0)
      • Wargame (0)
      • Review (1)
    • C (12)
      • Crypto (12)
    • C++ (4)
    • Python (1)
    • Swift (1)
    • Blockchain (9)
      • Solidity (9)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 정보보안
  • 리눅스
  • 암호화폐
  • solidity
  • 암호화
  • 솔리디티
  • 비트코인
  • Bitcoin
  • c언어
  • Blockchain
  • 보안
  • C
  • 이더리움
  • 복호화
  • ethereum
  • 프로그래밍
  • 접근제어
  • openssl
  • C++
  • 블록체인

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Prototype Ghost

귀신일지

Crypto Project : 2-2. 소켓에서 select() 함수를 이용한 recv_all() 함수
C/Crypto

Crypto Project : 2-2. 소켓에서 select() 함수를 이용한 recv_all() 함수

2023. 4. 26. 21:36

시작하며


  저번 시간에 멀티쓰레드 기반의 소켓 프로그램의 구현 일부분을 살펴보았다. 데이터를 전송할 때 중요한 점은 데이터의 크기를 먼저 보내야 한다는 것이다. 네트워크의 지연, 혼잡 등 여러가지 이유로 만약 송신자가 16바이트의 데이터를 보내도 수신자에게 11바이트, 3바이트, 2바이트 이렇게 끊어져서 데이터가 올 수도 있다. 이런 상황이 벌어졌을 때, 데이터의 크기를 수신자가 알지 못한다면 수신자는 처음 온 11바이트가 전송받은 데이터의 전부인 줄 알고 처리할 것이다. 이는 여러 문제점을 야기할 수 있는데, 이 프로그램에서 가장 큰 문제는 "복호화"이다. 이 프로그램은 16바이트의 배수로 암호화를 진행한다. 부족한 부분은 패딩을 채운다. 만약 상대가 11바이트만 먼저 받았다면, 복호화를 애초에 진행할 수 없고, 만약 32바이트를 보냈는데 상대에게 16바이트가 먼저 갔다면 이 16바이트의 복호화는 진행되더라도 패딩 검증에 실패하게 될 것이다. 즉 상대에게 내가 보낼 데이터는 32바이트야 라고 먼저 알려준다면, 상대는 처음 온 16바이트 말고도 더 데이터가 올 것을 알 수 있고, 해당 크기의 데이터를 다 수신한 후에야 그에 따른 처리를 할 것이다. 즉 데이터를 보낼 때 사이즈를 먼저 보내주는게 정석이며, 기존에 recv 함수를 래핑하여 데이터를 데이터의 크기만큼 다 받을 때까지 수신하는 recv_all함수를 만들게 되었다. 이 때 필요한 함수 중 하나가 바로 select() 함수이다. 이 select함수가 왜 필요한지, 또 이를 이용해 recv_all 함수는 어떻게 구현하였는지 설명하겠다.

 

1. 기존 recv 함수의 문제점


  한가지 생각해 볼 상황이 있다. 만약 상대방이 보낼 데이터 사이즈 32바이트를 보내고, 16바이트가 먼저 수신자에게 도달하고 나머지 16바이트를 받을 때까지 대기하고 있다고 가정해보자. 이 때 네트워크나, 송신자에 문제가 발생하여 연결이 끊어졌다고 가정해보자. recv 함수는 blocking 함수라 함수를 호출하면 데이터가 전달될때까지 계속 대기하고 있는다. 즉 예를 들어 5초 이상 데이터를 받지 못한다면 무언가 잘못되었다 판단하고 연결을 끊든 어떤 처리를 해야하는데 recv는 그냥 계속 대기하고 있어 다음 코드가 수행이 안된다. 필자는 blocking의 개념을 몰라서 단순히 5초 동안 데이터를 받지 않으면 실행을 종료하도록 설계하였는데 예를 들어

 

1. recv()

2. 5초가 지나면

3. return

 

간단하게 표현하면 이런식이었다. 그런데 recv()가 blocking이기 때문에, 데이터를 받지 않으면 10초가 지나도 20초가 지나도 다음 흐름으로 넘어가지 않기 때문에, 2, 3번 라인으로 실행이 진행되지 않는다. 이러한 문제를 해결하기 위해 사용하는 함수가 select()함수이다. 아래에서 select 함수를 이용한 recv_all() 함수의 구현을 살펴보자.

 

2. select() 함수를 이용한 recv_all() 함수


 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

위는 select()함수의 헤더이다.

  우선 select함수가 뭔지 살펴보자. 위는 chatgpt가 설명한 select 함수이다. 우리는 recv에 대한 타임아웃을 하므로 두번째 인자를 세팅해주면, 파일 디스트립터의 변화를 감지하면서 timeout동안 변화가 없다면 이에 따른 처리를 할 수 있게 해당하는 0값을 리턴해준다. select는 에러 발생 시 -1, timeout 발생 시 0, 정상작동했을 경우 양의 정수를 리턴한다. 즉 recv가 일정 시간 이상 blocking상태가 되는 것을 방지해준다. 다음은 이를 이용한 recv_all 함수이다.

 

//전송 받을 메시지의 크기를 먼저 받았을 때 그 크기만큼 데이터를 읽는 함수(recv함수가 한번에 모든 데이터를 다 안읽어왔을수도 있기 때문)
int recv_all(int msg_length, int sock, uint8_t* buf){
    int ret = 0;

    // fd_set 구조체 초기화
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(sock, &read_fds);

    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    while (ret != msg_length) {
        // select 함수 호출
        int ready = select(sock + 1, &read_fds, NULL, NULL, &timeout);
        if (ready < 0) {
            return -1;
        } else if (ready == 0) {
            // 타임아웃 발생
            return -1;
        } else {
            ret += recv(sock, buf+ret, msg_length-ret, 0);
        }
    }
    return ret;
}

  파라미터로 msg_length로 받아야할 데이터 크기를 넘겨받는다. 그리고 fd_set을 초기화한다. 타임아웃을 5초로 잡고 ret이 msg_length가 될 때까지 while문을 돈다. 타임아웃이나 에러가 발생하면 -1, 파일 디스크립터의 변화가 발생했으면 수신할 데이터가 왔다는 뜻이므로 else문에 recv함수가 실행된다. 파일 디스크립터가 변했다는 것은 데이터가 왔다는 뜻이므로  recv함수로 반드시 데이터를 받을 수 있음을 뜻한다. 만약 5초가 지난다면 else if 문에서 return -1이 실행되기 때문에 select 함수를 이용하면 recv의 블로킹 상태를 방지할 수 있게 되는 것이다. recv는 수신한 데이터 크기를 반환하므로 ret에 계속해서 recv가 리턴하는 값을 누적하여 msg_length만큼 데이터를 받았는지 검사하고, 데이터를 모두 받았다면 ret을 반환한다. 

아래는 select 함수에대해 보다 자세히 설명되어있는 자료이다.

https://yms2047.tistory.com/entry/select-함수-사용법

 

select 함수

파일 디스크립터의 변화를 확인하는 함수 기본적으로 blocking 함수(확인할 파일 디스크립터에 변화가 생길 때까지 무한 대기) 멀티플렉싱 서버를 구현하기 위한 방법으로 select 함수가 가장 많이

yms2047.tistory.com

 

마치며


  이번 글에서 데이터를 보낼 때, 메시지 크기를 먼저 보내는 이유에 대해 설명했고 recv_all함수의 필요성을 알아보았다. 이 때 기존 recv 함수가 blocking 함수이기 때문에 타임아웃 기능을 걸어야했고, 이를 가능하게 하는 것이 파일디스크립터의 변화를 감시하는 select 함수였다. 소켓 통신에서 굉장히 중요한 개념과 팁들이기 때문에 알아놓으면 반드시 도움이 될 것이다. 다음 글에선 최종적으로 이 프로그램이 어떻게 암호화된 통신을 하게 되는지 살펴보겠다.

 

github:https://github.com/0xGh-st/I_CRYPTO.git

 

GitHub - 0xGh-st/I_CRYPTO

Contribute to 0xGh-st/I_CRYPTO development by creating an account on GitHub.

github.com

 

728x90
반응형

'C > Crypto' 카테고리의 다른 글

Crypto Project : 3. OTP 프로그램  (1) 2023.05.06
Crypto Project : 2-3. TLS방식을 모방한 소켓프로그램  (0) 2023.05.05
Crypto Project : 2-1. 멀티쓰레드 기반 1대n 소켓 프로그램  (1) 2023.04.25
Crypto Project : 1-6. crypto library makefile과 테스트  (0) 2023.04.23
Crypto Project : 1-5. 암복호화 init, update, final 구현  (0) 2023.04.16
    'C/Crypto' 카테고리의 다른 글
    • Crypto Project : 3. OTP 프로그램
    • Crypto Project : 2-3. TLS방식을 모방한 소켓프로그램
    • Crypto Project : 2-1. 멀티쓰레드 기반 1대n 소켓 프로그램
    • Crypto Project : 1-6. crypto library makefile과 테스트
    Prototype Ghost
    Prototype Ghost

    티스토리툴바