[질문] 포트 리다이렉션에 대해

낙엽의 이미지

서버를 하나 작성을 했습니다.

그런데 이 서버와 통신하는 외부 모듈이 포트를 수정할 수가 없는 상태에서

그 서버와 통신을 해야할 내부 모듈이 두개 이상 생겼습니다.

내부 서버 모듈 #1, 2... <---> fixed 포트만을 사용하는 외부 통신 인터페이스

포트를 공유해서 서버를 만들수는 없어서 고민을 하다가 device IP를 두개이상을 주었습니다. fixed port를 사용하는 외부에서 내부로 커넥트 시에 두개 이상의 서로 다른 IP를 할당을 했지요. 물론 이 IP는 가상이 아니고 실제 할당된 IP구요.

그래서 간단한 프록시를 작성을 했는데요..

근데 여기서 문제가 생겼습니다.

우선 외부 모듈과 통신을 할 포트를 열어두고요(예를들어 4000, 4001, 4002 ~ 4008번이라고 하고) 그리고 내부 서버에서는 리슨한 포트에 대해 루프를 돌면서 계속 accept합니다. 그리고 getsockname으로 연결요청한 IP를 보고 판단해서 내부적으로는 다른 포트로 리다이렉션을 하지요.

테스트 할때 한 포트에 대해서만 우선 테스트를 했었는데요, 4000을 리슨하고 있다가 3000과 5000으로 각기 다른 IP를 가지고 온 클라이언트를 다른 포트로 리다이렉션을 하는데는 별 무리가 없었는데요.. 여러 포트를 리슨하고 그것을 루프돌면서 accept를 하니까.. 클라이언트에서는 broken 현상이 나타나게 된 것입니다.

서버의 구성은 다음과 같습니다.

우선 main에서 특정 포트에 대해 listen을 하고 listenfd를 반환합니다.
물론 이 서버는 물리적인 IP를 두개이상 가지고 있기 때문에 INADDR_ANY로 다 받아들이지요.

    srvr_addr.sin_family = AF_INET;
    srvr_addr.sin_port = htons(port);

    if(((long)(ptr_pcol = getprotobyname("tcp"))) == 0) {
        trace(LOG_CRT, "cannot map \"tcp\" to protocol number");
        exit(0);
    }

    do {
        srvr_sd = socket(PF_INET, SOCK_STREAM, ptr_pcol->p_proto);
        if(srvr_sd < 0) {
            trace(LOG_CRT, "socket create failed");
            rc = srvr_sd;
            continue;
        }
        setsockopt(srvr_sd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
        srvr_addr.sin_addr.s_addr = htons(INADDR_ANY);
        if((rc = bind(srvr_sd, (struct sockaddr *)&srvr_addr, sizeof(struct sockaddr_in))) < 0) {
            trace(LOG_INF, "bind failed\n");
            close(srvr_sd);
            exit(0);
        }
    } while(rc < 0);

    if(listen(srvr_sd, LISTENQ) != 0) {
        trace(LOG_CRT, "listen error");
    }

    return srvr_sd;

여기서 리턴받은 listenfd를 루프를 돌면서 accept 하지요.

for(;;) {
   connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addr_len);
   getsockname(connfd, (struct sockaddr *)&client_addr, &client_addr_len);
   if(!(memcmp(inet_ntoa(client_addr.sin_addr), SERVICE_ADDR, 12))) {
      if(pthread_create(&ptConnection[0], NULL, proxy, (void *)ptr) != 0) {

      }
   } else if(!(memcmp(inet_ntoa(client_addr.sin_addr), OTHER_ADDR, 12))) {
      if(pthread_create(&ptConnection[1], NULL, proxy, (void *)ptr) != 0) {

      }
   } else {
      fprintf(stderr, "Unknown ip address");
   }
}

이런식으로 서버를 분리해서 리다이렉션을 합니다.
그리고 그 처리는 thread에서 하게되죠. 포트 하나를 열고 테스트할때는 전혀 이상없는 코드였는데 다중의 포트를 listen하도록 하고 그 listenfd를 가지고 accept를 하니 이상이 발생합니다.(socket broken)

그리고 thread 내부에서 하는 일은 간단합니다.
내부 모듈로 Connect를 하고 서로 전송받은 내용을 read하고 write해주는 것 뿐이죠.

    for(;;) {
        FD_SET(sd, &rset);
        FD_SET(sdOut, &rset);
        selectval = select(iMaxSocket, &rset, NULL, NULL, NULL);
        if(selectval == -1) {
            perror("select(): ");
            break;
        }

        memset(buf, 0, TRANSFER_BUFFER_SIZE);
        if(FD_ISSET(sd, &rset)) {
            nbytes = read(sd, &buf, TRANSFER_BUFFER_SIZE);
            if(nbytes <= 0) {
                Close(sdOut);
                Close(sd);
                break;
            }

            if(write(sdOut, &buf, nbytes) != nbytes) {
                trace(LOG_CRT, "write() failed");
            }

            nbytes -= nbytes;
        }

        if(FD_ISSET(sdOut, &rset)) {
            nbytes = read(sdOut, &buf, TRANSFER_BUFFER_SIZE);
            if(nbytes <= 0) {
                Close(sdOut);
                Close(sd);
                break;
            }

            if(write(sd, &buf, nbytes) != nbytes) {
                trace(LOG_CRT, "write() failed");
            }

            nbytes -= nbytes;
        }

    }

그래서 포트를 열어야 할 갯수대로 프로세스를 따로 만들어서 동작도 시켜봤는데 같은 현상이 일어납니다. 그리고 무엇보다 너무 느리구요..

아래 그림은 프로시 전체 밑그림입니다.
C1과 C2가 두개의 클라이언트고 S가 포트를 열고 대기하는 프록시 입니다.
그리고 그 프록시는 C1과 C2를 구분해서 M1과 M2 두군데와 서로 통신하도록 연결해주는 역할을 하구요..

[/code]

File attachments: 
첨부파일 크기
Image icon 프록시.JPG11.68 KB
leilei의 이미지

죄송하게도 질문을 다 이해하진 못했지만.. :oops:
NAT을 이용하면 되지 않을까 합니다...

낙엽의 이미지

leilei wrote:
죄송하게도 질문을 다 이해하진 못했지만.. :oops:
NAT을 이용하면 되지 않을까 합니다...

죄송하지만, NAT를 이용할수는 없는 상황입니다..

그래서 가장 기본적인 proxy를 구현을 한거구요.. cache나 뭐 그런기능은 전혀 없거든요. 단지 포트 리다이렉션만 있는겁니다..

낙엽의 이미지

으음.. 제가봐도 질문이 좀 난해하게 되어있네요

간단히 정리하자면,

1. Client의 외부 Interface는 수정할 수 없다.
2. Client는 n개일 수 있다.
3. Client에서 붙는 포트는 fix 되어있다.(1번과 동일한 얘기)
4. A service에 대한 Client와 B service에 대한 Client 프로그램 및 Interface는 동일하다.
5. A service에 대한 서버와 B service에 대한 서버가 동일하다.
6. A service client에서 여는 포트와 B service client에서 여는 포트가 같다.
7. 위 두가지 A, B service의 client가 붙는 서버시스템은 한대다.
8. 서버시스템은 한 포트를 두 서버에서 바인드할 수 없으므로, 서버시스템에 두장의 랜카드를 장착하여, A service에는 192.168.1.100의 IP와 Interface상에 fix되어 있는 포트를 할당했으며, B service에는 192.168.1.101의 IP와 Interface상에 fix되어 있는 포트를 할당했다.
9. 서버에서 A service가 192.168.1.100의 IP와 fixed port로 바인딩하고 대기중이다.
10. 서버에서 B service가 192.168.1.101의 IP와 fixed port로 바인딩하고 대기중이다.

문제점:

11. 통신중에 소켓이 close되지는 않지만, 한쪽이 통신될때 한쪽이 broken되는 현상이 발생한다.
12. 서버의 cpu점유율이 높아진다.

낙엽의 이미지

A client (A-IP, 1port) --> server
B client (B-IP, 1port) --> server

server에서는 IP를 다르게 포트는 같게 설정해서 리슨중.

A client <--> server(getsockname으로 A ip로 요청이 들어왔으므로 2port로 리다이렉션) <--> service daemon(in server, 2 port)

B client <--> server(getsockname으로 B ip로 요청이 들어왔으므로 3port로 리다이렉션) <--> service daemon(in server, 3 port)

위의 server는 같은 머쉰.

하아.. 어렵습니다.. 방법이 없을까요?

moonzoo의 이미지

일단 궁금한 점은 client에서

A service와 B service을 어떻게 선택할 수 있는건가요?

1. 오직 ip만으로 구분가능한건가요?

2. 아님 패킷의 내용을 통해서 구분가능한가여?

2번이 가능하다면 굳이 ip를 나눌필요 없이

서버의 accept을 하나의 프로세스에서 처리하고

accept 되는 순간에 A service 또는 B service를 exec하고

서버는 계속 accept를 하면 되지 않을까여?

낙엽의 이미지

우선 답변 감사합니다.

1. IP로만 구분가능합니다.
2. 패킷의 내용으로는 구분할 수 없습니다.

둘다 패킷안에는 ascii가 들어있으며, 같은 명령어가 올 수 있습니다. 그러나 그 결과는 전혀 다른것이 갈 수 있습니다.

제가 새로 질문을 했는데, 시스템 자원에 관한 질문입니다. 이 내용과 관련된것인데, 한 서버에 NIC 두장에서 같은 포트를 bind해서 리슨중일때 문제점에 관한 것 입니다...

낙엽의 이미지

해결했습니다. 어처구니 없는 실수로 계속 되어야할게 안되어서 헤매고 있었네요.

답변해주신 분들께 감사드립니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.