pow( ) - 거듭제곱 함수 구현하기(정수 기반)

by Blogger 하얀쿠아
2017. 12. 21. 13:57 소프트웨어 Note/C

개요

C언어의 수학 함수 중, 거듭제곱을 구하는 pow( )라는 함수가 있다.

예를들어, 2의 10승을 계산하고 싶다면, pow(2.0, 10.0); 형태로 사용하는 함수이다.

이 함수는 math.h 헤더를 include하면 사용할 수 있는 함수이다.


#include <math.h>

double pow(double x, double y);

x : 거듭제곱의 밑수

y : 거듭제곱의 지수


그런데 이렇게 거듭제곱을 계산하는 함수를 직접 구현해야 하는 경우가 있었다.

매우 큰 수의 거듭제곱을 계산하면서 중간중간에 주어진 특정 값으로 mod 연산(나눗셈 후 나머지 값을 취하여 반환하는 연산)을 해야 하는 알고리즘 풀이 문제였다.


문제 조건 상, pow( )를 직접 사용하면 연산속도가 느려지는 상황이라 직접 구현을 시도해 보았다.



my_pow( ) 직접 구현하기


내가 필요한 것은 math.h 에서 제공하는 함수와는 달리, 밑과 지수가 큰 정수(unsigned long long 타입)이면 충분했다.

아래와 같이 구현했다.

편의상 위에서 언급한 mod 연산 부분은 제외하였다.

또한, 항상 exp >= 1 이고 base >= 1 이라고 가정하였다.


unsigned long long my_pow(unsigned long long base, int exp)
{
    unsigned long long res = 1;
    while (exp)
    {
        if (exp & 1)
            res *= base;
        exp >>= 1;
        base *= base;
    }

    return res;
}


설명

조금만 찾아보면 알겠지만, '제곱에 의한 지수법(?)' 표준 방법이라고 한다.

그러나 이것은 모든 지수 값에 대해 작동하는 일반적인 방법으로 할 수있는 최선의 방법 일 수는 있지만 특정 지수 값에 대해서는 더 적은 곱셈으로도 계산 가능한 더 빠른 해결방법이 존재할 수 있다. 즉, 항상 최적의 해결방법은 아니라는 것이다.


예를들면 x의 15승 (x ^ 15) 를 생각해보자.


x^15 = (x^7)*(x^7)*x 

x^7 = (x^3)*(x^3)*x 

x^3 = x*x*x


위의 구현방법으로는 총 6번의 곱셈을 해야 계산결과를 얻게된다.


그러나, 'Addition-Chain exponentiation[각주:1]' 방법을 사용하면 5번의 곱셈으로 결과를 얻을 수 있다.


n*n = n^2

n^2*n = n^3

n^3*n^3 = n^6

n^6*n^6 = n^12

n^12*n^3 = n^15




사용 예

아래와 같이 사용하면 된다.

// 2^3
pow(2,3); // 8

// 5^5
pow(5,5); // 3125


  1. https://en.wikipedia.org/wiki/Addition-chain_exponentiation [본문으로]
이 댓글을 비밀 댓글로

[네트워크/C reference] Special IPv6 주소 검사 매크로

by Blogger 하얀쿠아
2017. 12. 14. 02:25 소프트웨어 Note/C

개요


IPv6 주소 체계에서는 'Special IPv6 addresses' 라고 부르는, 미리 정의된 특별한 주소들이 있다.

미리 정의된 비트 패턴(bit pattern) 으로 각 주소의 역할을 구분한다.


최근들어 C언어로 네트워크 프로그래밍을 하면서 IPv6 주소를 다루게 되는 경우가 발생하곤 하는데, 이 주소가 특정 Special IPv6 address 인지 아닌지를 확인 해야 하는 경우가 있었다. 

그래서 조금 찾아보니, 이런 경우 유용하게 사용 할 수 있는 매크로 함수가 C의 표준 라이브러리인 glibc 의 netinet/in.h 헤더에 정의가 되어 있었다.


이번 포스팅에서는 이들 매크로 함수들을 간단히 소개한다.


특수 IPv6 주소(Special IPv6 Address) 검사 매크로 함수

IEEE Std 1003.1, 2004 Edition 에는, <netinet/in.h> 헤더는 아래와 같은 매크로들을 정의해야 한다고 권고하고 있다.

이 각각의 매크로들은 인자로 하나의 const struct in6_addr * type을 받으며, 인자의 IPv6 주소가 special IPv6 addresses 인지 아닌지를 검사하고 int type으로 참인 경우 1 / 거짓인 경우 0을 반환하는 기능을 한다.


아래 내용중, 실제 정의 내용은 glibc/inet/netinet/in.h 에서 발췌했다. 정확한 버전은 기억나지 않는다. 

변경될 만한 성격의 것이 아니라 버전마다 다를 가능성은 적지만, 단언하긴 어려우며 확인이 필요하다.



IN6_IS_ADDR_UNSPECIFIED(addr)

정의되지 않은 주소(Unspecified address) 여부를 검사한다.

정의되지 않은 주소이면 1을 반환한다.

정의되지 않은 주소가 아니면 0을 반환한다.


참고로 IPv6에서 정의되지 않은 주소는 ::/128 이다.


헤더파일에는 아래와 같이 정의되어 있다.


#ifdef __GNUC__
# define IN6_IS_ADDR_UNSPECIFIED(a) \
  (__extension__                                                              \
   ({ const struct in6_addr *__a = (const struct in6_addr *) (a);              \
      __a->__in6_u.__u6_addr32[0] == 0                                              \
      && __a->__in6_u.__u6_addr32[1] == 0                                      \
      && __a->__in6_u.__u6_addr32[2] == 0                                      \
      && __a->__in6_u.__u6_addr32[3] == 0; }))
#else
# define IN6_IS_ADDR_UNSPECIFIED(a) \
        (((const uint32_t *) (a))[0] == 0                                      \
         && ((const uint32_t *) (a))[1] == 0                                      \
         && ((const uint32_t *) (a))[2] == 0                                      \
         && ((const uint32_t *) (a))[3] == 0)

#endif





IN6_IS_ADDR_LOOPBACK(addr)

local host로의 루프백 주소(Loopback address) 여부를 검사한다.

루프백 주소이면 1을 반환한다.

루프백 주소가 아니면 0을 반환한다.


참고로 IPv6에서 루프백 주소는 ::1/128 이다. IPv4의 127.0.0.1과 같은 역할을 한다.


헤더파일에는 아래와 같이 정의되어 있다.


#ifdef __GNUC__
# define IN6_IS_ADDR_LOOPBACK(a) \
  (__extension__                                                              \
   ({ const struct in6_addr *__a = (const struct in6_addr *) (a);              \
      __a->__in6_u.__u6_addr32[0] == 0                                              \
      && __a->__in6_u.__u6_addr32[1] == 0                                      \
      && __a->__in6_u.__u6_addr32[2] == 0                                      \
      && __a->__in6_u.__u6_addr32[3] == htonl (1); }))
#else
# define IN6_IS_ADDR_LOOPBACK(a) \
        (((const uint32_t *) (a))[0] == 0                                      \
         && ((const uint32_t *) (a))[1] == 0                                      \
         && ((const uint32_t *) (a))[2] == 0                                      \
         && ((const uint32_t *) (a))[3] == htonl (1))
#endif



IN6_IS_ADDR_MULTICAST(addr)

멀티캐스트 주소 (Multicast address) 여부를 검사한다.

멀티캐스트 주소이면 1을 반환한다.

멀티캐스트 주소가 아니면 0을 반환한다.


IPv6 에서 멀티캐스트 주소라고 하면, ff0X:: 로 시작하는 주소이다. 여기서 X는 16 진수 값으로 예약되어 있으므로 멀티캐스트 그룹에 할당하면 안된다. 이 예약에 대해서는 IANA (Internet Assigned Numbers Authority)가 관리한다. 현재까지는, 이 X의 값으로 scope를 구분한다.

현재는 0, 1, 2, 3, 5, 6, 7, 8, e, f 가 사용되고 있는데, 이중.. 1 (interface-local), 2 (link-local), 5 (site-local)를 의미한다. 다른 값들도 특정한 의미가 있다.


간단히 예를 들어보면, ff02::2 는 link-local의 모든 라우터를 지칭하는 주소이고, ff01::2 는 interface-local의 모든 라우터를 지칭하는 주소이며, ff05::2는 site-local의 모든 라우터를 지칭하는 주소인 식이다.

이에 대해서는 별도의 포스팅으로 한번 더 다뤄보겠다.


헤더파일에는 이 매크로를 아래와 같이 정의하고 있다.


#define IN6_IS_ADDR_MULTICAST(a) (((const uint8_t *) (a))[0] == 0xff)





IN6_IS_ADDR_LINKLOCAL(addr)

유니캐스트 링크로컬 주소(Unicast link-local address) 여부를 검사한다.

유니캐스트 링크로컬 주소이면  1을 반환한다.

유니캐스트 링크로컬 주소가 아니면 0을 반환한다.


헤더파일에는 아래와 같이 정의하고 있다.


#ifdef __GNUC__
# define IN6_IS_ADDR_LINKLOCAL(a) \
  (__extension__                                                              \
   ({ const struct in6_addr *__a = (const struct in6_addr *) (a);              \
      (__a->__in6_u.__u6_addr32[0] & htonl (0xffc00000)) == htonl (0xfe800000); }))
#else
# define IN6_IS_ADDR_LINKLOCAL(a) \
        ((((const uint32_t *) (a))[0] & htonl (0xffc00000))                      \
         == htonl (0xfe800000))
#endif




IN6_IS_ADDR_SITELOCAL(addr)

유니캐스트 사이트 로컬 주소(Unicast site-local address) 여부를 검사한다.

유니캐스트 사이트 로컬 주소이면  1을 반환한다.

유니캐스트 사이트 로컬 주소가 아니면 0을 반환한다.


헤더파일에는 아래와 같이 정의되어 있다.


#ifdef __GNUC__
# define IN6_IS_ADDR_SITELOCAL(a) \
  (__extension__                                                              \
   ({ const struct in6_addr *__a = (const struct in6_addr *) (a);              \
      (__a->__in6_u.__u6_addr32[0] & htonl (0xffc00000)) == htonl (0xfec00000); }))
#else
# define IN6_IS_ADDR_SITELOCAL(a) \
        ((((const uint32_t *) (a))[0] & htonl (0xffc00000))                      \
         == htonl (0xfec00000))
#endif




IN6_IS_ADDR_V4MAPPED(addr)

IPv4 매핑된 주소(IPv4 mapped address) 여부를 검사한다.

참고로 IPv4 mapped address는 prefix가 ::ffff:0:0/96 이고, 범위는 ::ffff:0.0.0.0부터 ::ffff:255.255.255.255 까지 이다.

이 매크로는, 인자 addr이 이에 해당하는 지를 검사하여 결과를 반환한다.


헤더파일에는 아래와 같이 정의되어 있다.


#ifdef __GNUC__
# define IN6_IS_ADDR_V4MAPPED(a) \
  (__extension__                                                              \
   ({ const struct in6_addr *__a = (const struct in6_addr *) (a);              \
      __a->__in6_u.__u6_addr32[0] == 0                                              \
      && __a->__in6_u.__u6_addr32[1] == 0                                      \
      && __a->__in6_u.__u6_addr32[2] == htonl (0xffff); }))
#else
# define IN6_IS_ADDR_V4MAPPED(a) \
        ((((const uint32_t *) (a))[0] == 0)                                      \
         && (((const uint32_t *) (a))[1] == 0)                                      \
         && (((const uint32_t *) (a))[2] == htonl (0xffff)))
#endif


IN6_IS_ADDR_V4COMPAT(addr)

IPv4 호환 주소(IPv4-compatible address) 여부를 검사한다.

참고로 IPv4-compatible address의 prefix는 ::/96 이다. 그러나 이 주소 prefix는 deprecated 되었음을 기억하자.


이 매크로는 헤더파일에 아래와 같이 정의되어 있다.


#ifdef __GNUC__
# define IN6_IS_ADDR_V4COMPAT(a) \
  (__extension__                                                              \
   ({ const struct in6_addr *__a = (const struct in6_addr *) (a);              \
      __a->__in6_u.__u6_addr32[0] == 0                                              \
      && __a->__in6_u.__u6_addr32[1] == 0                                      \
      && __a->__in6_u.__u6_addr32[2] == 0                                      \
      && ntohl (__a->__in6_u.__u6_addr32[3]) > 1; }))
#else
# define IN6_IS_ADDR_V4COMPAT(a) \
        ((((const uint32_t *) (a))[0] == 0)                                      \
         && (((const uint32_t *) (a))[1] == 0)                                      \
         && (((const uint32_t *) (a))[2] == 0)                                      \
         && (ntohl (((const uint32_t *) (a))[3]) > 1))
#endif



IN6_IS_ADDR_MC_NODELOCAL

멀티캐스트 노드 로컬 주소 (Multicast node-local address) 여부를 검사한다.


이 매크로는 헤더파일에 아래와 같이 정의되어 있다.


#define IN6_IS_ADDR_MC_NODELOCAL(a) \
        (IN6_IS_ADDR_MULTICAST(a)                                              \
         && ((((const uint8_t *) (a))[1] & 0xf) == 0x1))




IN6_IS_ADDR_MC_LINKLOCAL


멀티캐스트 링크 로컬 주소 (Multicast link-local address) 여부를 검사한다.


이 매크로는 헤더파일에 아래와 같이 정의되어 있다.


#define IN6_IS_ADDR_MC_LINKLOCAL(a) \
        (IN6_IS_ADDR_MULTICAST(a)                                              \
         && ((((const uint8_t *) (a))[1] & 0xf) == 0x2))




IN6_IS_ADDR_MC_SITELOCAL

멀티캐스트 사이트-로컬 주소(Multicast site-local address) 여부를 검사한다.


멀티캐스트 사이트-로컬 주소이면 1을 반환한다.

멀티캐스트 사이트-로컬 주소가 아니면 0을 반환한다.


헤더파일에는 아래와 같이 정의되어 있다.


#define IN6_IS_ADDR_MC_SITELOCAL(a) \
        (IN6_IS_ADDR_MULTICAST(a)                                              \
         && ((((const uint8_t *) (a))[1] & 0xf) == 0x5))



IN6_IS_ADDR_MC_ORGLOCAL

멀티캐스트 organization-local 주소(Multicast organization-local address) 여부를 검사한다.


참고로 organization-local 주소의 prefix는 ffx8::/16 이고, IPv4의 239.192.0.0/14 와 동일하다.

헤더파일에는 아래와 같이 정의되어 있다.


#define IN6_IS_ADDR_MC_ORGLOCAL(a) \
        (IN6_IS_ADDR_MULTICAST(a)                                              \
         && ((((const uint8_t *) (a))[1] & 0xf) == 0x8))



IN6_IS_ADDR_MC_GLOBAL

멀티캐스트 글로벌 주소(Multicast global address) 여부를 검사한다. 

멀티캐스트 글로벌 주소이면 1을 반환한다.

멀티캐스트 글로벌 주소가 아니면 0을 반환한다.


헤더파일에는 아래와 같이 정의되어 있다.



#define IN6_IS_ADDR_MC_GLOBAL(a) \
        (IN6_IS_ADDR_MULTICAST(a)                                              \
         && ((((const uint8_t *) (a))[1] & 0xf) == 0xe))



참조 - 예약된 IPv6 주소(Reserved IPv6 address)

Special IPv6 address와 같이 볼 것이 있는데, Reserved IPv6 address 이다. IPv6 주소 체계에서 특수 목적으로 예약된 몇몇 주소들인데, 아래 wiki의 표와 같다.


https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv6

이 댓글을 비밀 댓글로

[네트워크/C reference] inet_pton 함수

by Blogger 하얀쿠아
2017. 11. 12. 01:07 소프트웨어 Note/C

기능 요약 (Summary)

inet_pton( ) 함수는 사람이 알아보기 쉬운 텍스트(human-readable text)형태의 IPv4 와 IPv6 주소를 binary 형태로 변환 하는 기능을 한다.



헤더 (Header files)

이 함수를 사용하려면 다음 헤더파일을 포함시킨다.


#include <arpa/inet.h>


API 원형 (API Prototype)

3개의 파라미터를 가지는 함수이다.

int inet_pton(int af, const char *src, void *dst);

af [input] -  address family를 지정한다. src의 문자열이 IPv4 주소를 나타내는지, IPv6 주소를 나타내는지를 함수에 알린다.

src [input] - 문자열 형태의 IP주소를 넣는다.

dst : [output] - src를 binary형태로 변환 후 복사된 메모리의 포인터이다.


설명 (Description)

이 함수는 af의 address family가 가리키는 네트워크 주소 구조체에 src의 문자열 값을 변환 한 후, 이 네트워크 주소 구조체를 dst에 복사한다.

af는 반드시 AF_INET 혹은 AF_INET6 둘 중 하나여야 한다.

dst는 network byte order로 작성된다.


언급했듯이 현재는 IPv4 및 IPv6를 의미하는 아래 2개의 address family들만 지원된다.


AF_INET

src이 가리키는 문자열은 IPv4 네트워크 주소이고, 점으로 자리를 구분하는 10진수 주소 형태이다.

"ddd.ddd.ddd.ddd" 와 같은 형태이고, ddd 자리에 0~255 범위의 10진수가 온다.

그 주소는 in_addr 구조체로 변환 된 후, 4byte크기를 가지는 dst로 복사된다. 

참고로 in_addr 구조체는 크기가 4byte 이다.



AF_INET6

src이 가리키는 문자열은 IPv6 네트워크 주소이다.

그 주소는 in6_addr 구조체로 변환된 후 16byte크기를 가지는 dst로 복사된다.

src에 넣을 수 있는 IPv6 주소 표현방식은 IPv4 주소 표현 방식에 비해 다양한데, 아래와 같은 규칙을 따른다.


IPv6 주소 표현 규칙

1. 우선시 되는 형태는 x:x:x:x:x:x:x:x 이다. 

각각의 x 는 16진수 문자이며 16bit 값을 표현하므로, 최대 4자리가 될 수 있다.

또한 x와 x 는 콜론으로 구분되며, 전체 8개로 구성된다.


2. 연속되는 0 값들은 :: 로 축약해서 표현하는 방법이 사용된다. 

단, 하나의 주소표현에서 :: 는 단 한번만 등장할 수 있다.

예를 들면, IPv6의 loopback 주소는 0:0:0:0:0:0:0:1 인데, 이걸 간단히 ::1 로 축약해서 표현 할 수 있다.

또한, wildcard 주소인 모든 값이 0으로 구성된 주소는, :: 라고 쓸 수 있다.


3. 대체 유형은  IPv4-mapped IPv6주소를 표현하는데 유용하다. 이 형태는 x:x:x:x:x:x:d.d.d.d 와 같이 쓰인다.

처음 앞의 6개 x 들은 주소의 최상위 비트 6개를 정의한다. (96 bit)

그리고 나머지 d들은 최하위 32bit에 점으로 구분하는 10진수 표기법을 정의한다.

예를 들면 ::FFFF:204.152.189.116 이러한 주소다.



더 자세한 IPv6 주소 표현방법을 알고 싶다면, RFC 2373 을 참고하기 바란다.


반환값 (Return value)

inet_pton( )은 성공시 1을 반환한다.

즉, 1이 return되면 네트워크 주소가 성공적으로 변환되었음을 의미한다.


0 이 반환되는 경우는 src의 문자열이 나타내는 네트워크 주소가, af에 명시된 address family의 유효한 값이 아닌 경우이다.

-1 이 반환되는 경우는 af가 적절한 address family값이 아닌 경우이다. 이때는 errno이 EAFNOSUPPORT 값으로 설정된다.


준수사항

POSIX.1-2001, POSIX.1-2008.  



알아둘 내용

inet_aton( ) 및 inet_addr( )와는 달리, inet_pton( )은 IPv6주소를 지원한다. 


반면에, inet_pton( )은 점 3개와 10진수 표현법의 (dotted-decimal notation) IPv4주소만 받아들인다. 

inet_aton( )및 inet_addr( )은 좀더 일반적인 표현법인 'numbers-and-dots notation' 에 대해서도 받아들인다.


'numbers-and-dots notation'는 이와 같다.

hexadecimal and octal number formats, and formats that don't require all four bytes to be explicitly written.  


알려진 버그

AF_INET6은 IPv4주소를 인식하지 못한다. 

대신에 명시적으로 IPv4-mapped IPv6 주소가 반드시 src에 넣어져야 한다.


같이 볼 함수들

getnameinfo( )

getaddrinfo( )

inet_ntop( )


이 댓글을 비밀 댓글로

[네트워크/C reference] inet_ntop 함수

by Blogger 하얀쿠아
2017. 11. 2. 02:16 소프트웨어 Note/C

기능 요약 (Summary)

inet_ntop - IPv4 와 IPv6 주소를 binary 형태에서 사람이 알아보기 쉬운 텍스트(human-readable text)형태로 전환해준다.



헤더 (Header files)

  #include <arpa/inet.h>


API 원형 (API Prototype)

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);



설명 (Description)

이 함수는 네트워크 주소 구조체인 src를 캐릭터 문자열 dst로 변환해주는 함수이다. 네트워크 주소는 IPv4 혹은 IPv6 가 될 수 있으며, 'address family'를 의미하는 af 를 참고하여 src를 해석하게 된다. 결과가 저장되는 dst는 반드시 NULL 포인터가 아니어야 한다. 즉, 메모리 공간이 할당된 포인터 이어야 한다는 뜻.


이 API를 호출하는 쪽에서는 dst 버퍼의 크기를 size 를 통해 지정한다.


inet_ntop( )는 다양한 address family를 지원하기 위해 inet_ntoa( ) 함수를 확장한다.

그래서 inet_ntoa( )는 현재 deprecated 되도록 고려되고 있다.


af 는 현재 아래의 address family들을 사용 할 수 있도록 지원된다.


AF_INET

src 는 struct in_addr (network byte order임) 구조체를 가리킨다. 

이것은 IPv4 네트워크 주소로 변환 될 것이고, 'dotted-decimal format' 이라고 불리는 형태의 주소, 즉, "ddd.ddd.ddd.ddd" 과 같은 형태가 될 것이다.

버퍼 dst 는 반드시 메모리가 할당되어야 하며, 그 크기는 최소한 INET_ADDRSTRLEN 크기만큼의 바이트 수는 되어야 한다.


AF_INET6

src 는 struct in6_addr (network byte order임) 구조체를 가리킨다.

이것은 IPv6 네트워크 주소로 변환될 것이다. 변환된 문자열의 형태는 해당 IPv6 주소를 나타내기에 가장 적절한 형태가 될 것이다. 

즉, 생략가능한 부분은 연속으로 콜론 2개를 찍은 형태의 단축형 결과를 얻게 된다는 것이다. 

버퍼 dst 는 반드시 메모리가 할당되어야 하며, 그 크기는 최소한 INET6_ADDRSTRLEN 크기만큼의 바이트 수는 되어야 한다.



반환값 (Return value)

성공하면, inet_ntop( ) 는 dst 를 가리키는 포인터를 반환한다.

만약 에러가 있었다면, NULL이 반환되고, errno 에 에러와 관련된 값이 설정된다.




오류 값 (Errors)

EAFNOSUPPORT

af 가 유효한 address family가 아님.


ENOSPC 

변환된 주소 문자열이 size에 의해 주어진 크기를 초과하게 됨.



준수사항

POSIX.1-2001, POSIX.1-2008.  

RFC 2553 에서는 이 함수 원형의 마지막 인자인 size를 size_t로 정의하고 있음을 알아둘 것.

많은 시스템이 RFC2553를 따른다. Glibc 2.0와 2.1 버전은 size_t 이지만, 2.2 및 이후 버전은 socklen_t 이다.



같이 볼 함수들

getnameinfo( )

getaddrinfo( )

inet_pton( )



이 댓글을 비밀 댓글로

[C][CURL] CURLOPT_FOLLOWLOCATION 의 버전별 동작 차이

by Blogger 하얀쿠아
2017. 8. 31. 01:49 소프트웨어 Note/C


C언어로 프로그램을 작성하다보면, HTTP 통신을 해야 할 때가 종종있는데, 이때 개인적으로 선호하는 라이브러리가 있다.

바로 CURL[각주:1] 이다. 라이브러리만을 따로 부를땐 libcurl 이라는 이름으로도 부른다.



이 libcurl을 사용해서 HTTP 처리를 하다보면, 서버가 주는 3xx 응답을 만날때가 있다. 이때 클라이언트는 이에 대한 추가적인 처리 - 변경 혹은 이동된 URL을 따라간다던가 하는 - 를 해야 할 경우가 있다.  libcurl은 이때 간단히 사용할 수 있는 옵션을 제공하는데 바로 'CURLOPT_FOLLOWLOCATION' 옵션이다.


우선 HTTP 3xx status에 대해 간단히 살펴보겠다.


300(여러 선택항목): 서버가 요청에 따라 여러 조치를 선택할 수 있다. 서버가 사용자 에이전트에 따라 수행할 작업을 선택하거나, 요청자가 선택할 수 있는 작업 목록을 제공한다.


301(영구 이동): 요청한 페이지를 새 위치로 영구적으로 이동했다. GET 또는 HEAD 요청에 대한 응답으로 이 응답을 표시하면 요청자가 자동으로 새 위치로 전달된다.


302(임시 이동): 현재 서버가 다른 위치의 페이지로 요청에 응답하고 있지만 요청자는 향후 요청 시 원래 위치를 계속 사용해야 한다.


303(기타 위치 보기): 요청자가 다른 위치에 별도의 GET 요청을 하여 응답을 검색할 경우 서버는 이 코드를 표시한다. HEAD 요청 이외의 모든 요청을 다른 위치로 자동으로 전달한다.


304(수정되지 않음): 마지막 요청 이후 요청한 페이지는 수정되지 않았다. 서버가 이 응답을 표시하면 페이지의 콘텐츠를 표시하지 않는다. 요청자가 마지막으로 페이지를 요청한 후 페이지가 변경되지 않으면 이 응답(If-Modified-Since HTTP 헤더라고 함)을 표시하도록 서버를 구성해야 한다.


305(프록시 사용): 요청자는 프록시를 사용하여 요청한 페이지만 액세스할 수 있다. 서버가 이 응답을 표시하면 요청자가 사용할 프록시를 가리키는 것이기도 하다.


307(임시 리다이렉션): 현재 서버가 다른 위치의 페이지로 요청에 응답하고 있지만 요청자는 향후 요청 시 원래 위치를 계속 사용해야 한다.


308(영구 리다이렉션, RFC에서 실험적으로 승인됨)


HTTP의 3xx 상태값은 총 9가지 상태가 IETF RFC 2616에 정의되어 있다.


예를들어 302(임시 이동 - temporarily moved) 를 서버로 부터 응답받았다고 하자. 서버는 보통 HTTP header에 이동된 URL을 함께 전달해야 한다.


HTTP 302 status header




위 사진들처럼 'Location:" 필드에 이동된 URL을 전달한다. 유의해야 할 점은, 서버에 따라  절대URL로 알려주기도 하고, 상대URL로 알려주기도 한다는 점이다.

이것을 자세히 알아보고자 하는건 아니고, libcurl을 사용할 때 이를 어떻게 처리할 지를 알아보고자 한다.



API header 및 prototype

#include <curl/curl.h>


CURLcode curl_easy_setopt(CURL *handle, CURLOPT_FOLLOWLOCATION, long enable);


앞서 언급한 CURLOPT_FOLLOWLOCATION 옵션은 curl_easy_setopt( ) 의 2번째 매개변수로 사용한다.

3번째 매개변수로 '0L' 혹은 '1L'을 줄수 있다. 어떤 값을 주느냐에 따라 동작이 달라진다.


0L : 3xx에 대해 libcurl은 아무런 처리를 하지 않는다.

1L : 3xx에 대해 libcurl의 내장된 로직으로 처리를 한다. 내장된 로직은 HTTP헤더의 Location: 필드의 URL로 HTTP request 다시 시도하는 것이다.


자세한 설명

3번째 'long enable' 매개변수를 1로 설정하면 libcurl 라이브러리가 3xx 응답을 받았을 때, HTTP 헤더의 일부로 서버가 보내는 Location: 필드의 값을 따라가도록 설정하는 것이다. Location: 필드는 따라야 할 상대URL 또는 절대URL가 지정될 수 있다.


libcurl은 새로운 URL에 대한 또 다른 요청을하고 더 이상 그러한 헤더가 반환되지 않을 때까지 새로운 Location: 헤더를 따라간다. CURLOPT_MAXREDIRS를 통해서 libcurl이 따라가는 'redirection'의 회수를 제한할 수 있다.


libcurl은 자동으로 따르는 프로토콜을 제한한다. 허용 된 프로토콜은 CURLOPT_REDIR_PROTOCOLS로 설정된다. 기본적으로 libcurl은 보안상의 이유로 인해 비활성화 된 프로토콜을 제외한 모든 프로토콜을 redirection 할 수 있다. => 7.19.4 버전부터 FILE 및 SCP 프로토콜이 비활성화되며, 7.40.0 버전부터는 SMB 및 SMBS도 비활성화된다.


Location을 따라갈 때, 3xx 응답 코드는 다음 요청에서 사용할 요청 방법을 지시한다. 301, 302 및 303 응답의 경우 CURLOPT_POSTREDIR이 libcurl을 지정하지 않는 한 libcurl은 메소드를 GET으로 전환합니다. 다른 모든 3xx 코드는 libcurl이 동일한 메소드를 다시 전송하도록 한다.


기존 위치 추적이 너무 단순하거나 기능이 부족하다고 생각하는 사용자라면, CURLOPT_FOLLOWLOCATION 대신 curl_easy_getinfo의 CURLINFO_REDIRECT_URL 옵션을 사용하여 redirection 추적 로직을 구현하는 것이 차라리 매우 싸게먹힌다.



겪은 이슈

libcurl 7.40 버전과 libcurl 7.50 버전에서 모든 세팅을 동일하게 했으나 서로 다른 동작을 하는 경우가 있다. 예상치 못한 문제였다.


조건

1. CURLOPT_PROXY로 proxy server를 사용하도록 libcurl을 설정할 것

2. CURLOPT_FOLLOWLOCATION를 사용하도록 libcurl을 설정할 것

3. 위 1에서 설정한 proxy server가 꽤나 저성능(기능도 적고, 단순하고, 스마트하지 못한)일 것


위 조건을 만족한 상태에서, 예를들어, http://x-sub.abcd.com/first.html 을 요청(HTTP GET)했다고 하자. 

그리고 x-sub.abcd.com 서버가 302 상태값과 함께 Location: http://subdomain.abcd.com/second.html 을 주었다고 가정하자. 

main domain은 abcd.com으로 동일하지만 sub domain이 다르며, 각각의 sub domain을 호스팅하는 서버가 물리적으로 다를 수 있다.


libcurl 7.40 동작

프록시 서버와 연결 -> HTTP GET 으로 http://x-sub.abcd.com/first.html 를 요청함.

-> 302  Location: http://subdomain.abcd.com/second.html 를 받음 -> 프록시 서버와 새로운 포트로 새로운 연결 -> http://subdomain.abcd.com/second.html 를 요청함 -> 200 OK 받음 -> 프록시 서버와 연결 끊음 -> 프록시 서버와 새로운 포트로 새롭게 열었던 연결 끊음


libcurl 7.50 동작

프록시 서버와 연결 -> HTTP GET 으로 http://x-sub.abcd.com/first.html 를 요청함.

-> 302  Location: http://subdomain.abcd.com/second.html 를 받음 ->  http://subdomain.abcd.com/second.html 를 요청함 -> 404 Not found 받음 -> 프록시 서버와 연결 끊음


libcurl 7.50에서는 프록시 서버와 새로운 포트로 새롭게 연결하는 동작이 없었다. 이 점은 개선된 점이다. 프록시 서버와 매번 연결하는 부하를 줄였다. 그런데 redirection URL의 서버가 물리적으로 다른 경우, 프록시 서버에서 새로운 서버로 연결을 진행해야 할 것 같은데, 그렇지 않은 경우가 있었다. 첫번째 요청했던 서버한테 그대로 redirection URL의 resource를 HTTP GET 으로 요청하는 것처럼 보였다.


해결방법

이를 해결하기 위해서는 아래와 같이 했다.


1. CURLOPT_FOLLOWLOCATION 를 사용하지 않도록 0L 로 libcurl을 설정한다.

2. 요청하려는 URL로의 curl_easy_perform의 반환값이 CURLE_OK 인 경우

 2-1. curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &url) 

 2-2. url 이 존재하는 경우

  2-2-1. redirection url이므로 다시 curl_easy_perform을 수행한다.



기본값

0L (비활성화)


프로토콜

HTTP(S)





예제 코드

CURL *curl = curl_easy_init();
if(curl) {
  curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
 
  /* example.com is redirected, so we tell libcurl to follow redirection */
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
 
  curl_easy_perform(curl);
}


사용가능성

HTTP와 사용가능하다.


반환값

HTTP가 지원되면 CURLE_OK를 반환하고 그렇지 않으면 CURLE_UNKNOWN_OPTION을 반환한다.


같이 보기

CURLOPT_REDIR_PROTOCOLS

CURLOPT_PROTOCOLS

CURLOPT_POSTREDIR

CURLINFO_REDIRECT_URL

CURLINFO_REDIRECT_COUNT

  1. cURL(/kɝl/ 또는 /kə:l/)은 다양한 통신 프로토콜을 이용하여 데이터를 전송하기 위한 라이브러리와 명령 줄 도구를 제공하는 컴퓨터 소프트웨어 프로젝트이다. 이 cURL 프로젝트는 libcurl와 cURL이라는 2개의 제품을 만든다. [본문으로]
이 댓글을 비밀 댓글로

[네트워크/C] addrinfo 구조체

by Blogger 하얀쿠아
2017. 6. 16. 01:14 소프트웨어 Note/C

이 포스팅은 POSIX/리눅스에서 C 언어를 사용한 네트워크 소켓 프로그래밍과 관련된 글이다.


addrinfo 구조체는 네트워크 주소정보(인터넷 주소)와 호스트이름을 표현하는데 사용되며, 이 정보는 bind( ), connect( )호출 시 입력 파라미터에 사용될 수 있다.

또한 getaddrinfo( ) 함수 호출 시, hint 정보를 알리는 입력 파라미터로 사용할 수 있으며, getaddrinfo( ) 함수의 결과값을 전달하는 출력 파라미터로도 사용된다.


관련 헤더

struct addrinfo를 코드에서 사용하기 위해서는 아래 헤더파일을 포함시키면 된다.
#include <netdb.h>



구조체 원형

struct addrinfo {
   int          ai_flags;           /* 추가적인 옵션을 정의 할 때 사용함. 여러 flag를 bitwise OR-ing 하여 넣는다 */
   int          ai_family;          /* address family를 나타냄. AF_INET, AF_INET6, AF_UNSPEC */
   int          ai_socktype;        /* socket type을 나타냄. SOCK_SREAM, SOCK_DGRAM */
   int          ai_protocol;        /* IPv4와 IPv6에 대한 IPPROTO_xxx와 같은 값을 가짐. */
   socklen_t    ai_addrlen;         /* socket 주소인 ai_addr의 길이를 나타냄 */
   char        *ai_canonname;       /* 호스트의 canonical name을 나타냄 */
   struct sockaddr    *ai_addr;     /* socket 주소를 나타내는 구조체 포인터 */
   struct addrinfo    *ai_next;     /* 주소정보 구조체 addrinfo는 linked list이다. 다음 데이터의 포인터 */
};


ai_flags 값 목록

ai_flags에 가능한 플래그 값은 아래와 같다.


AI_PASSIVE

The caller will use the socket for a passive open.


AI_CANONNAME

Tells the function to return the canonical name of the host.


AI_NUMERICHOST

Prevents any kind of name-to-address mapping; the hostname argument must be an address string.


AI_NUMERICSERV

Prevents any kind of name-to-service mapping; the service argument must be a decimal port number string.


AI_V4MAPPED

If specified along with an ai_family of AF_INET6, then returns IPv4-mapped IPv6 addresses corresponding to A records if there are no available AAAA records.


AI_ALL

If specified along with AI_V4MAPPED, then returns IPv4-mapped IPv6 addresses in addition to any AAAA records belonging to the name.


AI_ADDRCONFIG

Only looks up addresses for a given IP version if there is one or more interface that is not a loopback interface configured with an IP address of that version.



ai_addrlen의 type관련 주의사항

어떤 운영체제에서는 socklen_t가 아닌 size_t를 ai_addrlen으로 사용하기도 한다.


대부분의 소켓함수에서 두 type은 호환되지만, 호환불가능한 경우 런타임 오류룰 발생시키는 경우가 있다. 

예를 들면, big-endian을 사용하는 64-bit Solaris 9 시스템은 size_t는 8바이트, socketlen_t는 4바이트 이므로 두 type간 호환되지 않고 런타임 오류 발생할 것이다.

따라서 본인이 작성한 코드가 어떤 시스템 위에서 동작할 지를 충분히 고려해야 한다.

같이보기

getaddrinfo 함수 - 2017/06/16 - [Software Dev Note/Embedded Linux] - [네트워크/C] getaddrinfo 함수



이 댓글을 비밀 댓글로

[네트워크/C] getaddrinfo 함수

by Blogger 하얀쿠아
2017. 6. 16. 00:56 소프트웨어 Note/C

개요


getaddrinfo는 domain address를 받아서 네트워크 주소 정보(IP address)를 가져오는 함수이다.


예를들면, http://www.google.co.kr라는 domain address가 있는데, 이 주소는 사람이 알아보기 쉬운 주고이긴 하지만, 컴퓨터는 이 주소를 가지고 해당되는 구글의 서버를 찾아가지 못한다. 그래서 이 domain address와 대응되는 IP주소가 무엇인지를 알아 낸 뒤에 그 IP주소로 연결을 해야한다. 


즉, Domain address -> IP address 변환을 하고 싶을 때 사용하는 함수라는 뜻이다.

이걸 전문용어로 DNS (Domain Name System/Service) resolving 이라고 한다.


getaddrinfo 함수는 총 4개의 매개변수를 가진다.

그 중, 1~3번째는 입력 매개변수이고, 4번째 매개변수는 결과를 사용자에게 돌려주는 출력 매개변수이다.

결과는  addrinfo 구조체 (strcut addrinfo) 의 linked list로 돌려준다.


이 결과는 사용을 끝낸 뒤엔 freeaddrinfo 함수로 메모리 해제를 해주어야 한다. 그렇지 않으면 메모리 누수가 발생한다.


관련헤더

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>


함수원형

int getaddrinfo(const char *hostname,
                const char *service,
                const struct addrinfo *hints,
                struct addrinfo **result);

void freeaddrinfo(struct addrinfo *res);


getaddrinfo 함수의 매개변수는 총 4개이다.


hostname [in] : 호스트 이름 혹은 주소 문자열(주소 문자열은 => IPv4의 점으로 구분하는 10진 주소 문자열이거나 IPv6의 16진 문자열)


service [in] : 서비스 이름 혹은 10진수로 표현한 포트 번호 문자열


hints [in] : getaddrinfo 함수에게 말그대로 힌트를 준다. 희망하는 유형을 알려주는 힌트를 제공한다. addrinfo 구조체에 hint로 줄 정보를 채운 뒤, 그것의 주소값을 넘기면 된다. 이 힌트는 반환받을 result를 제한하는 기준을 지정하는 것이다. 예를들면, IPv4주소만 받고 싶거나, IPv6주소만 받고 싶을 수도 있고, 둘다 받고 싶을수도 있다. 이럴땐hints의 ai_family의 값을 조작하면 된다. 별도의 hint를 제공하지 않을 경우, NULL을 넣는다.


result [out] : DNS서버로부터 받은 네트워크 주소 정보(IP 주소)를 돌려주는 output 매개변수이다. addrinfo 구조체를 사용하며, 링크드 리스트이다. 이 addrinfo 구조체에 대해서는 아래 그림을 참고하면 이해하기 쉽다. 이 result의 내용중 필요한것들은 적절히 copy하여 사용자의 변수로 옮겨두어야 하며, result는 사용이 끝나는 즉시 freeaddrinfo 함수로 메모리 해제를 해주어야 한다.


getaddrinfo가 반환한 주소정보(addrinfo)의 예



반환 값

이 함수는 성공하면 0을 반환하다.

실패하면 0이 아닌 값을 반환하는데, 아래 에러 값들중 하나를 반환하게 된다.


EAI_ADDRFAMILY

지정된 네트워크 호스트에 요청했던 address family 주소가 없는 경우이다. 예를들어 IPv4주소만 갖는 네트워크 호스트에게 'IPv6주소를 내놔라' 하고 요청한다면, 이런 에러가 반환된다.


EAI_AGAIN

nameserver가 일시적인 오류 표시를 반환했다. 일정 시간 이후에 다시 시도하라는 의미이다.


EAI_BADFLAGS

hints.ai_flags에 잘못된 플래그가 포함되어 있다. 또는 hints.ai_flags에는 AI_CANONNAME이 포함되었고 이름이 NULL인 경우이다.


EAI_FAIL

nameserver가 지속되는 오류 표시를 반환했다. EAI_AGAIN과는 달리 다시 시도해도 실패할 것이라는 의미다.


EAI_FAMILY

요청한 address family는 지원되지 않는다.


EAI_MEMORY

getaddrinfo를 수행하기에 메모리가 부족한 경우다. 거의 막장인 상황이다. kernel의 OOM killer가 제대로 작동하길 기대하면서 기다렸다가 일정 시간 지난 후 재시도 해보고 그래도 같은 에러를 받는다면 속편하게 재부팅 하라.


EAI_NODATA

지정한 네트워크 호스트가 있긴 한데... 네트워크 주소가 정의되어 있지 않은 경우다.


EAI_NONAME

노드 또는 서비스를 알 수 없다.

또는 노드와 서비스 모두가 NULL이다.

또는 AI_NUMERICSERV가 hints.ai_flags에 지정되었는데, 정작 service는 포트 번호를 나타내는 숫자형태의 문자열이 아니었다.

어떤 경우이건, 입력 매개변수를 잘못 넣었다는 의미다. 자신이 매개변수를 잘 넣었는지? 스스로의 코드를 다시 점검 해볼 것.


EAI_SERVICE

요청한 서비스를 요청한 소켓 유형에 사용할 수 없다. 다른 소켓 유형을 통해 사용할 수 있다. 예를 들어 서비스가 "쉘"(스트림 소켓에서만 사용할 수있는 서비스)이고 hints.ai_protocol이 IPPROTO_UDP이거나 hints.ai_socktype이 SOCK_DGRAM 인 경우 이 오류가 발생할 수 있다.

또는 서비스가 NULL이 아니고 hints.ai_socktype이 SOCK_RAW (서비스 개념을 지원하지 않는 소켓 유형) 인 경우 오류가 발생할 수 있습니다.


EAI_SOCKTYPE

요청한 소켓 유형이 지원되지 않는다. 

예를 들어, hints.ai_socktype 및 hints.ai_protocol이 일치하지 않는 경우다. ai_socktype에는 SOCK_DGRAM 를 넣고, ai_protocol에는 IPPROTO_TCP를 넣은 hint를 입력 매개변수로 넘기면 발생할 수 있는 에러이다.


EAI_SYSTEM

위 모든 경우를 제외한 뭔가 다른 시스템 오류다, 골치아픈 경우다. 자세한 내용은 errno를 확인해야 하는 경우다.



반환값의 해석

const char *gai_strerror(int errcode);


gai_strerror( ) 함수를 이용한다.

이 함수는 getaddrinfo( )가 return한 오류 코드를 사람이 읽을 수 있는 문자열로 변환해준다.

디버깅, 로그 출력 시 도움이 되니 참고할 것.



예제

아래는 www.google.com 80포트의 Network Address 정보를 얻는 예제이다.

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int main(int argc,char *argv[])
{
	int status;
	struct addrinfo hints;
	struct addrinfo *servinfo;  // 결과를 저장할 변수

	memset(&hints, 0, sizeof(hints)); // hints 구조체의 모든 값을 0으로 초기화
	hints.ai_family = AF_UNSPEC;     // IPv4와 IPv6 상관하지 않고 결과를 모두 받겠다
	hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

	status = getaddrinfo("www.google.com", "80", &hints, &servinfo);
}


공식 근거문서

POSIX.1-2001. getaddrinfo( ) 함수는 RFC 2553에 문서화 되어 있다.



중요 알림사항

getaddrinfo( ) 는 IPv6 scope-ID를 지정하기 위한 address%scope-id 표기법을 지원한다.


AI_ADDRCONFIG, AI_ALL 및 AI_V4MAPPED는 glibc 2.3.3부터 사용할 수 있다.


AI_NUMERICSERV는 glibc 2.3.4부터 사용 가능하다.


POSIX.1-2001에 따르면, hint를 NULL로 지정하면 ai_flags가 0으로 간주된다. 그러나 GNU C 라이브러리는 이 값이 향상을 위한 것으로 간주하기 때문에, 이렇게 hint가 NULL인 경우에는 ai_flags를 0 대신 (AI_V4MAPPED | AI_ADDRCONFIG) 값을 사용합니다.


같이보기

inet_ntop( ) 함수

inet_pton( ) 함수

addrinfo 구조체



이 댓글을 비밀 댓글로

[네트워크/C] sockaddr, sockaddr_in, sockaddr_un 구조체 - 소켓 주소 정보를 나타낸다

by Blogger 하얀쿠아
2017. 6. 14. 01:56 소프트웨어 Note/C

기본 지식

'Socket Address Structures' 에 관해서 설명하는 글이다.

리눅스/유닉스 시스템에서는 소켓(socket)의 통신 대상을 지정하기 위해 '주소(address)'를 사용한다.
이 '주소' 라는 것을 저장하거나 표현하는데 사용하는 구조체가 바로, 본 포스팅에서 설명하고자 하는 'struct sockaddr' 이다.
bind( ), connect( ) 와 같은 함수들이 2번째 매개변수로써 바로 이 'struct sockaddr' 을 받는다.

이 struct sockaddr은 기본 형태이고, 주소체계(Address family)값에 따라서 구조체를 형변환 해서 사용하면 편리하다.
즉, 일반적으로 개발을 하다보면 struct sockaddr_in, struct sockaddr_un, struct sockaddr_in6 등의 구조체와 상호 형변환을 해서 사용하게 된다.

주소체계 유형은 아래와 같은 것들이 있다

AF_INET, AF_INET6, AF_UNIX, AF_LOCAL, AF_LINK, AF_PACKET


관련 헤더

다음 세가지 헤더파일이 관련있다.

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */



sockaddr 구조체

sockaddr 구조체는 소켓의 주소를 담는 기본 구조체 역할을 한다.

속된말로, '소켓 주소의 와꾸[각주:1]를 잡는 녀석' 쯤으로 이해하면 쉽다.

아래와 같이 정의되어 있다.

struct sockaddr {
	u_short    sa_family;     // address family, 2 bytes
	char    sa_data[14];     // IP address + Port number, 14 bytes
};


구성은 정말 간단하다. 2개의 멤버변수를 가진 구조체이다.

 sa_family : 주소체계를 구분하기 위한 변수이며, 2 bytes 이다. 참고로, u_short는 unsigned short를 말한다.

 sa_data : 실제 주소를 저장하기 위한 변수다. 14 bytes 이다.

즉, 이 구조체는 16 bytes 의 와꾸(틀, 크기)를 잡아주는 녀석이다.


sockaddr_in 구조체 : AF_INET인 경우

위에서 정의된 sockaddr 구조체에서 sa_family가 AF_INET인 경우에 사용하는 구조체이다.

sockaddr을 그대로 사용할 경우, sa_data에 IP주소와 Port번호가 조합되어 있어 쓰거나 읽기 불편하다.

그래서 sockaddr_in을 사용한다. 이 구조체에서 사용하는 IP주소는 IPv4 주소체계를 사용한다.


struct sockaddr_in {
	short    sin_family;          // 주소 체계: AF_INET
	u_short  sin_port;            // 16 비트 포트 번호, network byte order
	struct   in_addr  sin_addr;   // 32 비트 IP 주소
	char     sin_zero[8];         // 전체 크기를 16 비트로 맞추기 위한 dummy
};

struct  in_addr {
	u_long  s_addr;     // 32비트 IP 주소를 저장 할 구조체, network byte order
};


sin_family : 항상 AF_INET을 설정한다. 이것은 필수다.

sin_port : 포트번호를 가진다. 2bytes 이다. 즉, 포트번호는 0~65535 의 범위를 갖는 숫자 값이다. 이 변수에 저장되는 값은 network byte order이어야 한다. 

참고로, 1024이하의 포트 번호는 'privileged port' 혹은 'reserved port' 라고 불리우는 예약된 포트이다. 따라서, 권한을 가진 프로세스만이 1024 이하의 소켓주소에 대해서 바인딩 (bind) 할 수 있다. 여기서 '권한을 가진 프로세스' 라 함은, 리눅스에서는 'CAP_NET_BIND_SERVICE' 라는 capability를 가지고 있는 사용자 영역(user space)의 프로세스를 지칭한다.

두번째 참고로, raw socket (socket type을 'SOCK_RAW' 로 생성한 소켓)은 포트 번호 라는 개념을 가지지 않는다. 포트번호는 TCP 혹은 UDP에서만 구현하는 개념이다.

sin_addr : 호스트 IP주소이다.
이 변수에는 INADDR_ 로 시작하는 값, 예를 들면 'INADDR_ANY' 와 같은 것이 저장되어야 한다. 혹은 inet_aton( ), inet_addr( ), inet_makeaddr( ) 과 같은 라이브러리가 제공하는 함수의 반환값이 저장되어야 한다. 혹은 name resolver를 통해 직접 설정도 가능하다. 이 방법은 gethostbyname( )을 알아보면 된다.

sin_zero : 8 bytes dummy data이다. 반드시 모두 0으로 채워져 있어야 한다. sockaddr_in가 sin_zero를 제외한 크기(sin_family + sin_port + sin_addr)가 8 bytes이므로, 총합이 16 bytes이다. struct sockaddr구조체와 크기를 일치시키려는 목적인 걸 눈치 챌 수 있을 것이다.

여백을 채워서 16 bytes로 만든다는 뉘앙스로 padding bytes 혹은 padding data 라고도 한다.


#struct sockaddr_in 사용 예제

아래는 소켓을 열고 원격지의 호스트 (주소 127.0.0.1, 포트 80)에 연결하는 방법을 보여준다.

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <inttypes.h>
#include <strings.h>

int main(void)
{
	int sock;
	struct sockaddr_in in;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return (1);
	}

	bzero(&in, sizeof (struct sockaddr_in));
	in.sin_family = AF_INET;
	in.sin_port = htons(80);
	if (inet_pton(AF_INET, "127.0.0.1", &in.sin_addr) != 1) {
		perror("inet_pton");
		return (1);
	}

	if (connect(sock, (struct sockaddr *)&in,
				sizeof (struct sockaddr_in)) != 0) {
		perror("connect");
		return (1);
	}

	/* use socket */

	return (0);
}



sockaddr_in6 구조체 : AF_INET6인 경우

IPv6 주소체계의 소켓주소를 사용하는 구조체이다.

IPv4에 사용되는 struct sockaddr_in과는 달리, struct sockaddr_in6는 bzero( ) 또는 memset( )과 같은 함수를 통해 0으로 초기화 해주어야 하는 몇가지 멤버변수들이 추가되어 있다. 만약 strcut sockaddr_in6을 사용하기 전에 모두 0으로 초기화 하지 않으면, 프로그램은 정의되지 않은 동작을 야기할 수 있으므로 주의 해야 한다.

아래와 같은 멤버변수를 가지고 있다.

#include <netinet/in.h>

struct sockaddr_in6 {
	sa_family_t     sin6_family;   /* Address family, AF_INET6 */
	in_port_t       sin6_port;     /* port number */
	uint32_t        sin6_flowinfo; /* IPv6 flow information and traffic class */
	struct in6_addr sin6_addr;     /* IPv6 address */
	uint32_t        sin6_scope_id; /* Interface Scope ID (new in 2.4) */
};

struct in6_addr {
	unsigned char   s6_addr[16];   /* IPv6 address */
}; 

sin6_family : 항상 AF_INET6 이어야만 한다.

sin6_port : IPv6 포트를 저장하는 변수이다.
이 값은 직접 조작하기보다는, ntohs( ) htons( ) 로 조작하는 같이 좋다. 혹은 앞의 2개 함수와 동등한 수준으로 직접 조작하거나 말이다.

sin6_flowinfo : IPv6 헤더와 연관된 트래픽 클래스와 플로우 레이블을 포함한다. 

sin6_addr : 16bytes (128bits)의 IPv6주소를 저장하는 변수이다.
inet_pton( ) 함수 외에도, netinet/in.h 헤더파일에는 IPv6 주소를 조작하고 테스트하기 위한 많은 매크로를 정의하고 있다. 이들에 대한 설명은, 추후 IPv6에 대해 설명하는 포스팅에서 따로 자세히 다뤄보도록 하겠다.

sin6_scope_id : sin6_addr의 주소범위에 따라 달라지는 식별자를 포함할 수 있다. 프로그램은 sin6_scope_id를 초기화 할 필요는 없다. 다양한 라이브러리 함수 호출의 결과로, 운영체제에 의해 채워지는 값이다.


#struct sockaddr_in6 사용 예제

아래 예제는 소켓을 열고 포트 12345의 로컬 IPv6 주소 :: 1 포트에 바인드하도록 준비하는 방법을 보여준다.
예제는 libsocket과 libnsl 을 링크해야 한다. (컴파일시 링크옵션 추가 필요함 : -lsocket -lnsl)

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <inttypes.h>
#include <strings.h>

int
main(void)
{
	int sock6;
	struct sockaddr_in6 in6;

	if ((sock6 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return (1);
	}

	bzero(&in6, sizeof (struct sockaddr_in6));
	in6.sin6_family = AF_INET6;
	in6.sin6_port = htons(12345);
	if (inet_pton(AF_INET6, "::1", &in6.sin6_addr) != 1) {
		perror("inet_pton");
		return (1);
	}

	if (bind(sock6, (struct sockaddr *)&in6,
				sizeof (struct sockaddr_in6)) != 0) {
		perror("bind");
		return (1);
	}

	/* use server socket */

	return (0);
}




sockaddr_un 구조체 : AF_UNIX 또는 AF_LOCAL인 경우

struct sockaddr_un은 하나의 시스템에서 서로다른 프로세스 사이의 통신에 사용되는 소켓의 주소를 지정하는데 사용하는 구조체이다.

'Unix Domain Socket' 이라고도 한다. 이 Unix domain 소켓은 파일 시스템의 경로에 의해 식별된다.

struct sockaddr_un은 아래와 같은 멤버를 가지고 있다.


#include <sys/un.h>

#define UNIX_PATH_MAX    108

struct sockaddr_un {
    sa_family_t sun_family;               /* AF_UNIX */
    char        sun_path[UNIX_PATH_MAX];  /* pathname */
};

sun_family : 항상 AF_UNIX 값을 가져야 한다.

sun_path : 파일 시스템 경로를 지정한다. NUL 로 끝나는 문자열(C string)이어야 한다. 경로의 최대 길이는 NUL terminator를 포함해서 108 bytes이다.


주의 

응용프로그램이 자신의 local address를 표현할 때도 사용하며, 원격지의 상대방 주소를 표현할 때도 소켓 주소(socket address)로 표현한다.


참고 자료

https://illumos.org/man/3socket/sockaddr_in

  1. 일본어로 테두리 또는 범위라는 뜻이다. 자세한 내용은 다음 주소를 찾아가보자. https://namu.wiki/w/%EC%99%80%EA%BE%B8 [본문으로]
이 댓글을 비밀 댓글로
    • 2021.01.30 00:49
    비밀댓글입니다