일상의 정리

CMake 플랫폼 체크하기

프로그래밍

1개의 소스로 서로 다른 운영 체제에서 컴파일 및 실행되는 소프트웨어를 생성하길 원한다면, 각각의 플랫폼(OS)간의 특별한 속성을 파악해야 한다.

서로 다른 플랫폼(운영 체제) 간에는 미묘한 차이가 있다.

예를 들어 FreeBSD에서는 malloc.h를 사용하지 않아야 하고, 반면에 Linux에서 사용할 수 있다.

이러한 차이는 통상 config.h 라고 불리는 각 플랫폼의 특성을 구분짓는 여러개의 define문을 포함하는 헤더파일을 통해 처리된다.

#define HAVE_MALLOC_H 1 /* #undef HAVE_SYS_MNTTAB_H 1 */ /* #undef HAVE_SYS_MNTENT_H 1 */ #define HAVE_SYS_MOUNT_H 1

이 헤더파일은 아래 예제 처럼 소스의 맨 처음에 include 되어 처리 된다.

foo.c: #include "config.h" #ifdef HAVE_MALLOC_H #include <malloc.h> #else #include <stdlib.h> #endif void do_something() { void *buf=malloc(1024); ... }

config.h의 내용은 소스가 컴파일 되는 플랫폼에 따라 달라야 하므로, 실제로 컴파일 되기 전에 헤더 파일이 생성되어야 한다.

Autotools 기반 소프트웨를 사용중이라면, make 전 단계에서 실행되는 ./configure 단계를 알고 있을 것이다.

./configure 스크립트는 시스템 내부 검사를 수행하고 수집 된 정보로부터 config.h 헤더 파일을 생성한다. 

여기서는 CMake서도 ./configure를 사용하는 것과 똑같이 할 수 있는 방법에 대해 설명할 것이다.

또한 내장 명령에 있어서도, CMake는 module이라고 불리는 스크립트 파일을 사용해서 더 많은 명령을 지원한다.

이 파일들은 cmake 모듈 디렉토리에 있으며, UNIX 시스템의 경우 디폴트로 /usr/local/share/CMake/Modules 디렉토리 이다.

이러한 모듈의 명령을 사용하려면 먼저 CMakeLists.txt 파일 안에 include 를 해 줘야 한다.

CMake에는 시스템을 체크하기 위한 몇 가지 모듈이 포함되어 있다.

예를 들면 CHECK_INCLUDE_FILES :

INCLUDE (CheckIncludeFiles) # usage: CHECK_INCLUDE_FILES (<header> <RESULT_VARIABLE> ) CHECK_INCLUDE_FILES (malloc.h HAVE_MALLOC_H) CHECK_INCLUDE_FILES ("sys/param.h;sys/mount.h" HAVE_SYS_MOUNT_H) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)

CMake 모듈 CheckIncludeFiles는 CHECK_INCLUDE_FILES() 명령을 사용할 수 있게 해 준다.

이 명령의 첫 번째 인수는 체크하려고 하는 헤더파일이다. 두 번째 인수는 결과를 돌려받을 변수이다.

주어진 헤더가 발견되면 두번째 인수의 변수가 1로 설정되고, 그렇지 않으면 빈 상태로 된다.

헤더를 사용하기 위해 다른 헤더가 필요한 경우 위의 예에서 보듯이 헤더들을 세미콜론으로 구분해서 나열해야 한다.

더 정확한 CHECK_INCLUDE_FILES()의 구현에 대해서는 /usr/local/share/CMake/Modules/CheckIncludeFiles.cmake 을 참고하면 된다.

지정된 헤더 파일에 대한 테스트 결과는 CMakeCache.txt 파일에 아래 예와 같이 저장되므로, 나중에 테스트가 성공했는지 여부를 확인하려는 경우

CMakeCache.txt 파일을 살펴보면 된다.

//Have include HAVE_MALLOC_H HAVE_MALLOC_H:INTERNAL=1

결과가 캐시에 있으면 테스트가 다시 실행되지 않으므로, 다시 실행하려면 CMakeCache.txt 파일을 삭제하거나 다시 테스트 하려는 변수 항목을 삭제해야만 테스트가 다시 실행될 수 있다.

테스트가 실패한 경우 이유를 찾고 싶다면 CMakeFiles/CMakeError.log 파일을 열어서 헤더 이름 (또는 함수 등)을 찾으면 거기에 실패한 코드가 표시되어 있다.

이제 우리는 예를 들어 malloc.h가 존재하는 지 테스트 하고 cmake 변수 HAVE_MALLOC_H에 결과를 얻었으므로, 이제 헤더 config.h를 만들어야 한다.

이를 위해 cmake 명령 CONFIGURE_FILE()을 사용해야 한다.

이것은 소스 파일을 대상 파일로 복사하면서 원하는 부분을 수정해 주는 역할을 한다.

먼저 config.h.in이라는 이름의 소스 파일을 작성해야 한다. (이름은 아무래도 좋지만 autotools에서는 통상 config.h.in으로 사용한다) :

#cmakedefine HAVE_MALLOC_H 1 #cmakedefine HAVE_SYS_MOUNT_H

cmake가 실행되면 위 구문 중 #cmakedefine이 대체된다. HAVE_MALLOC_H 및 HAVE_SYS_MOUNT_H가 true이면 아래와 같은 config.h가 생성된다.

#define HAVE_MALLOC_H 1 #define HAVE_SYS_MOUNT_H

만약 2개 변수 모두 false 이면 아래와 같은 파일이 생성된다.

/* #undef HAVE_MALLOC_H */ /* #undef HAVE_SYS_MOUNT_H */

이 헤더를 소스 파일에 포함 시키고 #ifdef를 사용해서 속성을 확인할 수 있다.

이러한 체크를 최상위 CMakeLists.txt 만이 아니라 프로젝트의 모든 CMakeLists.txt에 추가할 수 있다.

구성된 헤더가 여러 개인 경우에는 그것들을 모두 config.h라고 부르면 include에 문제가 생길 수 있다.

각 서브 디렉트리에 따라 config.h, foo/ 디렉토리는 config-foo.h, bar/ 디렉토리는 config-bar.h 와 같은 이름을 지정하는 편이 좋다

시스템 검사를 수행하는 cmake의 다른 명령들은 다음과 같다.

Module: INCLUDE (CheckIncludeFiles) Usage: CHECK_INCLUDE_FILES(headers variable) Example: CHECK_INCLUDE_FILES(strings.h HAVE_STRINGS_H)

지정된 헤더파일이 존재하는 지 여부를 판정.

Module: INCLUDE (CheckFunctionExists) Usage: CHECK_FUNCTION_EXISTS(function variable) Example: CHECK_FUNCTION_EXISTS(madvise HAVE_MADVISE)

지정된 함수가 존재하는 지 여부를 판정.

Module: INCLUDE (CheckSymbolExists) Usage: CHECK_SYMBOL_EXISTS(symbol headers variable) Example: CHECK_SYMBOL_EXISTS((LC_MESSAGES "locale.h" HAVE_LC_MESSAGES)

지정된 헤더가 존재하는 경우에, 지정된 기호가 존재하는 지 여부를 판정 "Symbol"은 Preprocessor 매크로 또는 주소를 사용할 수 있으며, Enumerate 형은 사용 될 수 없음

Module: INCLUDE (CheckLibraryExists) Usage: CHECK_LIBRARY_EXISTS(library function location variable) Example: CHECK_LIBRARY_EXISTS(volmgt volmgt_running "" HAVE_VOLMGT)

지정된 라이브러리가 존재하고 제공된 함수를 포함하는지 여부를 확인. 이것은 함수를 사용하는 작은 프로그램과 라이브러리에 대한 링크를 통해서 수행. 필요한 경우 추가 링크 디렉토리 위치 매개 변수(-Ldir)를 지정할 수 있다.

Module: INCLUDE (CheckTypeSize) Usage: SET(CMAKE_EXTRA_INCLUDE_FILES header) CHECK_TYPE_SIZE(type variable) SET(CMAKE_EXTRA_INCLUDE_FILES) Example: SET(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h) CHECK_TYPE_SIZE("struct ucred" STRUCT_UCRED) SET(CMAKE_EXTRA_INCLUDE_FILES)

지정된 타입이 존재하는 지 여부를 판정 해, 그 타입의 크기를 돌려줌. 변수 안에는 타입의 크기가 설정되며, 타입이 존재한다면 HAVE_STRUCT_UCRED는 true로 설정됨. 타입의 크기에는 관심이 없고, 단순히 타입이 존재하는 지 여부만 알고 싶다면 STRUCT_UCRED 를 직접 사용할 수 있다. 타입이 없을 경우 이 값은 FALSE로 평가된다.(HAVE_STRUCT_UCRED).

Module: INCLUDE (CheckPrototypeExists) Usage: CHECK_PROTOTYPE_EXISTS(function headers variable) Example: CHECK_PROTOTYPE_EXISTS(mkstemps "stdlib.h;unistd.h" HAVE_MKSTEMPS_PROTO)

헤더가 주어진 것에 대한 선언을 제공하는지 여부를 확인. 함수가 실제로 정의되었는 지 여부는 확인하지 않음.

Module: INCLUDE (CheckCXXSourceCompiles) INCLUDE (CheckCSourceCompiles) Usage: CHECK_CXX_SOURCE_COMPILES(source variable) CHECK_C_SOURCE_COMPILES(source variable)

소스 코드가 컴파일되고 링크되는지 여부를 확인. CMAKE_REQUIRED_LIBRARIES, CMAKE_REQUIRED_FLAGS 및 CMAKE_REQUIRE 를 설정할 수 있음



ASN.1 기초

프로그래밍/ASN.1

ASN.1은 추상적인 구문 표기법 1(Abstract Syntax Notation One)을 의미.

ASN.1은 특정 프로그래밍 언어와 독립적으로 복잡한 데이터 구조를 설명 할 수 있음. ASN.1 컴파일러는 이러한 추상적으로 지정된 구조에 대한 규격을 만족하는 각각의 대상 언어 (C, C ++, Java) 로 된 Native한 파일 세트를 생성하고 변환을 수행 할 코드를 생성해 줌 (구조체가 네트워크를 통해 전송되거나 외부 매체에 쓰여지는 경우 사용할 수 있음).

ASN.1 용으로 개발 된 여러 데이터 인코딩이 존재.

가장 널리 사용되는 것은 BER (Basic Encoding Rules), CER (Canonical Encoding Rules), DER (Distinguished Encoding Rules), PER (Packed Encoding Rules) 및 XER (XML Encoding Rules).

컴퓨터간에 특정 구조를 전송하려고 한다고 가정. 이 구조에는 문자열 태그, 두 개의 정수 값 (두번째는 선택 사항임) 및 부동소수점 값 집합이 포함되어 있음. 이 구조는 다음과 같이 ASN.1 표기법을 사용하여 표현 가능.

CertainStructure ::= SEQUENCE {
                tag     VisibleString,
                val1    INTEGER,
                val2    INTEGER   OPTIONAL,
                reals   SET OF REAL
}

위 표현은 asn1c 컴파일러로 처리 시, asn 컴파일러는 다음과 같은 헤더를 포함하는 C 언어 파일을 생성.

typedef struct CertainStructure {
                VisibleString_t  tag;
                long             val1;
                long            *val2;        /* OPTIONAL */
                A_SET_OF(double) reals;
} CertainStructure_t;

이러한 유형의 변수 내용은 직접 처리될 수 있음. 그러나 이 복잡한 구조를 일련의 바이트로 변환하는 직렬화 및 비 직렬화 프로세스가 중요.

네트워크에서 이러한 구조를 수신하여 편집 한 다음 다시 컴팩트 이진 인코딩으로 변환하는 방법은 다음과 같음

CertainStructure_t *cs = 0; ber_decode(0, &asn_DEF_CertainStructure, &cs, buffer, buffer_length); cs->val1 = 123; /* Modify the contents */ der_encode(&asn_DEF_CertainStructure, cs, write_handle, 0);

그리고 이 구조체의 내용을 XML 형식으로 출력하는 방법은 다음과 같음

xer_fprint(stdout, &asn_DEF_CertainStructure, cs);

다음은 xer_fprint()를 통해 생성한 예제임.

<CertainStructure>
       <tag>This is a random tag</tag>
       <val1>123</val1>
       <reals>
              <REAL>3.14159265</REAL>
              <REAL><MINUS-INFINITY/></REAL>
              <REAL>2.7182818284</REAL>
       </reals>
</CertainStructure>

ASN.1 표기법은 통신망 산업의 MMS (멀티미디어 서비스) 및 텔레콤 장비 CDR (Call Data Records), TAP3 (Transferred Account Procedure), 보안 프로토콜 및 데이터 형식 (PKIX/X.509) 및 기타 High-profile 애플리케이션을 포함하여 광범위하게 사용됨. SSL (HTTPS)을 사용하여 은행 또는 이메일 계정에 액세스하는 경우 ASN.1도 포함되어 있음.



IPtables를 이용한 DDoS 방어: 궁극의 가이드

리눅스/해킹


DDoS Protection With IPtables: The Ultimate Guide 글을 번역한 내용입니다.

원본 글: https://javapipe.com/ddos/blog/iptables-ddos-protection/


iptables에 대한 자체 DDoS 방지 규칙을 작성하는 여러 가지 방법이 있습니다.

우리는 이 포괄적 인 튜토리얼에서 가장 효과적인 iptables DDoS 보호 방법을 논의 할 것입니다.


이 가이드는 다음을 수행하는 방법을 알려줍니다.

DDoS 공격을 막기 위한 iptables 테이블과 체인을 선택
DDoS 공격의 영향을 줄이기 위한 커널 설정 조정
iptables를 사용하여 대부분의 TCP 기반 DDoS 공격 차단
iptables SYNPROXY를 사용하여 SYN 플러드를 차단하기


이 글은 리눅스 서버를 매일 다루는 전문가를 대상으로 작성되었습니다.

온라인 애플리케이션을 DDoS 공격으로부터 보호하려는 경우 원격 보호, DDoS 보호 기능이있는 VPS 또는 DDoS로 보호 된 베어 메탈 (Bare Metal) 서버를 사용할 수 있습니다.
iptables로 DDoS 공격을 차단할 수는 있지만 대규모 DDoS 플러드를 탐지하고 차단하기 위한 실제 하드웨어 방화벽 (최근에 DDoS 완화를 위해 하드웨어를 검토 한 방법)을 대체할 수는 없습니다.
그러나 iptables를 사용하여 회선 속도로 대부분의 불량 트래픽을 필터링하는 것은 불가능하지 않습니다!
우리는 TCP 기반 공격으로부터만 보호할 것입니다. 대부분의 UDP 기반 공격은 모든 공통 서버의 네트워크 인터페이스 카드를 소모하는 증폭된 반사 공격입니다.
이러한 유형의 공격에 대처하는 유일한 완화 접근법은 에지 네트워크 또는 코어 네트워크 또는 캐리어에서 미리 차단하는 것입니다.

우리는 현재 일리노이 주 시카고와 루마니아의 부카레스트에서 측정되지 않은 대역폭과 DDoS 보호 기능을 갖춘 VPS를 제공하고 있다는 것을 알고 계셨습니까?
그들이 서버에 연결할 수 있다면 DDoS로 보호되는 네트워크로 이동하는 것을 제외하고는 다중 Gbit / s 공격에 대해 할 수있는 일이 많지 않습니다.

IPtables 란?
netfilter iptables (곧 nftables로 대체)는 netfilter에서 개발 한 커널 패킷 필터링 규칙을 구성하는 사용자 공간 명령행 유틸리티입니다.
리눅스 시스템의 기본 방화벽 관리 유틸리티입니다. 리눅스 시스템을 사용하는 모든 사람들은 익숙하거나 적어도 들어 봤어야 합니다.
iptables는 특정 패킷을 필터링하고, 소스 또는 대상 포트 및 IP 주소를 차단하고, NAT를 통해 패킷을 전달하고 다른 많은 것들을 사용할 수 있습니다.
가장 일반적으로 대상 포트와 소스 IP 주소를 차단하는 데 사용됩니다.


왜 IPtables Anti-DDoS 규칙일까?

현재 iptables가 DDoS 공격을 막는 규칙을 이해하는 이유를 알아보기 위해 iptables의 작동 방식을 먼저 이해해야 합니다.

iptables는 IP 패킷 필터 규칙 테이블을 설정하고 제어하는 ​​데 사용되는 명령행 도구입니다. 목적에 따라 다른 테이블이 있습니다.

IPtables 테이블

Filter : Filter 테이블은 -t (-table) 옵션을 사용하지 않으면 규칙이 사용하는 가장 일반적으로 사용되는 기본 테이블입니다.

Nat :이 테이블은 NAT(Network Address Translation:네트웍 주소 전달)에 사용됩니다. 패킷이 새로운 연결을 생성하면 nat 테이블에 규칙이 있는지 검사합니다(역주: 쉽게 설명하자면 인터넷 공유기 역할).

Mangle : mangle 테이블은 패킷과 헤더 정보를 수정하거나 표시하는 데 사용됩니다.

Raw :이 테이블의 목적은 주로 NOTRACK 대상을 사용하는 연결 추적에서 특정 패킷을 제외하는 것입니다.

위에서 보듯이 표준이 아닌 커널 모듈이 로드되지 않은 일반 Linux 시스템에는 네 가지 테이블이 있습니다. 이 테이블 각각은 iptables 체인의 다른 세트를 지원합니다.


IPtables Chains

PREROUTING: raw, nat, mangle

  • 네트워크 인터페이스 카드 (NIC)에 들어가는 패킷에 적용됩니다.

INPUT: filter, mangle

  • 로컬 소켓을 대상으로하는 패킷에 적용됩니다.

FORWARD: filter, mangle

  • 서버를 통해 라우팅되는 패킷에 적용됩니다.

OUTPUT: raw, filter, nat, mangle

  • 서버가 전송하는 패킷 (로컬에서 생성 된 패킷)에 적용됩니다.

POSTROUTING: nat, mangle

  • 서버에서 나가는 패킷에 적용됩니다.


차단하거나 수정할 패킷의 종류에 따라 특정 iptables 테이블과 선택한 테이블이 지원하는 체인을 선택합니다.

물론, 우리는 여전히 iptables 타겟 (ACCEPT, DROP, REJECT 등)에 대한 설명을 하지 않고 있지만 이 기사를 읽는다면 이미 iptables를 다루는 방법을 알고 있다고 가정하고 있습니다.

우리는 왜 iptables 규칙이 DDoS를 멈추고, iptables 사용법을 가르쳐주지 않는지 설명 할 것입니다. 다시 생각해 봅시다.

iptables로 DDoS 공격을 차단하려면 iptables 규칙의 성능이 매우 중요합니다. 대부분의 TCP 기반 DDoS 공격 유형은 높은 패킷 전송률을 사용합니다. 즉, 초당 패킷 수는 서버가 다운되는 원인입니다.

따라서 가능한 한 초당 많은 패킷을 처리하고 차단할 수 있는지 확인해야합니다.

iptables를 사용하여 DDoS 공격을 차단하는 방법에 대한 대부분의 지침은 전부는 아니지만 대부분의 경우 DDoS 방지 규칙에 필터 테이블과 INPUT 체인을 사용합니다.

이 접근 방식의 문제점은 INPUT 체인이 PREROUTING 및 FORWARD 체인 후에만 ​​처리되므로 패킷이 이 두 체인 중 하나와 일치하지 않는 경우에만 적용된다는 것입니다.

이로 인해 자원을 소비하는 패킷의 필터링이 지연됩니다. 결론적으로 우리는 규칙을 최대한 효과적으로 만들기 위해서는 우리의 anti-DDoS 규칙을 가능하면 체인까지 멀리 옮겨야합니다.

패킷에 적용 할 수있는 첫 번째 체인은 PREROUTING 체인이므로 이상적으로 이 체인의 잘못된 패킷을 필터링해야합니다.

그러나 필터 테이블은 PREROUTING 체인을 지원하지 않습니다. 이 문제를 해결하기 위해 안티 DDoS iptables 규칙에 필터 테이블 대신 mangle 테이블을 사용하면됩니다.

필터 테이블이 지원하는 모든 규칙은 아니지만 대부분의 iptables 체인을 지원합니다.


그래서 iptables DDoS 보호 규칙이 왜 엉망이되는지 알고 싶습니까? 불량 패킷을 차단하기 위해 필터 테이블과 INPUT 체인을 사용하기 때문입니다!

iptables 규칙의 성능을 극적으로 향상시키고 따라서 필터링 할 수있는 (TCP) DDoS 공격 트래픽의 양을 극적으로 증가시키는 최상의 솔루션은 mangle 테이블과 PREROUTING 체인을 사용하는 것입니다!


DDoS를 완화하는 최고의 Linux 커널 설정

또 다른 일반적인 실수는 사람들이 DDoS 공격의 효과를 더 잘 완화하기 위해 최적화된 커널 설정을 사용하지 않는다는 것입니다.

이 가이드는 CentOS 7을 선택한 운영 체제로 중점적으로 설명합니다. CentOS 7에는 iptables의 최신 버전과 새로운 SYNPROXY 대상 지원 기능이 포함되어 있습니다.

iptables로 DDoS를 효과적으로 완화하기 위해 조정해야하는 모든 단일 커널 설정은 다루지 않습니다.

대신 CentOS 7 커널 설정을 제공합니다. 아래의 내용을 /etc/sysctl.conf 파일에 넣고 sysctl -p와 함께 설정을 적용하십시오.


Anti-DDoS Kernel Settings (sysctl.conf)

kernel.printk = 4 4 1 7 
kernel.panic = 10 
kernel.sysrq = 0 
kernel.shmmax = 4294967296 
kernel.shmall = 4194304 
kernel.core_uses_pid = 1 
kernel.msgmnb = 65536 
kernel.msgmax = 65536 
vm.swappiness = 20 
vm.dirty_ratio = 80 
vm.dirty_background_ratio = 5 
fs.file-max = 2097152 
net.core.netdev_max_backlog = 262144 
net.core.rmem_default = 31457280 
net.core.rmem_max = 67108864 
net.core.wmem_default = 31457280 
net.core.wmem_max = 67108864 
net.core.somaxconn = 65535 
net.core.optmem_max = 25165824 
net.ipv4.neigh.default.gc_thresh1 = 4096 
net.ipv4.neigh.default.gc_thresh2 = 8192 
net.ipv4.neigh.default.gc_thresh3 = 16384 
net.ipv4.neigh.default.gc_interval = 5 
net.ipv4.neigh.default.gc_stale_time = 120 
net.netfilter.nf_conntrack_max = 10000000 
net.netfilter.nf_conntrack_tcp_loose = 0 
net.netfilter.nf_conntrack_tcp_timeout_established = 1800 
net.netfilter.nf_conntrack_tcp_timeout_close = 10 
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10 
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 20 
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 20 
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20 
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 20 
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 10 
net.ipv4.tcp_slow_start_after_idle = 0 
net.ipv4.ip_local_port_range = 1024 65000 
net.ipv4.ip_no_pmtu_disc = 1 
net.ipv4.route.flush = 1 
net.ipv4.route.max_size = 8048576 
net.ipv4.icmp_echo_ignore_broadcasts = 1 
net.ipv4.icmp_ignore_bogus_error_responses = 1 
net.ipv4.tcp_congestion_control = htcp 
net.ipv4.tcp_mem = 65536 131072 262144 
net.ipv4.udp_mem = 65536 131072 262144 
net.ipv4.tcp_rmem = 4096 87380 33554432 
net.ipv4.udp_rmem_min = 16384 
net.ipv4.tcp_wmem = 4096 87380 33554432 
net.ipv4.udp_wmem_min = 16384 
net.ipv4.tcp_max_tw_buckets = 1440000 
net.ipv4.tcp_tw_recycle = 0 
net.ipv4.tcp_tw_reuse = 1 
net.ipv4.tcp_max_orphans = 400000 
net.ipv4.tcp_window_scaling = 1 
net.ipv4.tcp_rfc1337 = 1 
net.ipv4.tcp_syncookies = 1 
net.ipv4.tcp_synack_retries = 1 
net.ipv4.tcp_syn_retries = 2 
net.ipv4.tcp_max_syn_backlog = 16384 
net.ipv4.tcp_timestamps = 1 
net.ipv4.tcp_sack = 1 
net.ipv4.tcp_fack = 1 
net.ipv4.tcp_ecn = 2 
net.ipv4.tcp_fin_timeout = 10 
net.ipv4.tcp_keepalive_time = 600 
net.ipv4.tcp_keepalive_intvl = 60 
net.ipv4.tcp_keepalive_probes = 10 
net.ipv4.tcp_no_metrics_save = 1 
net.ipv4.ip_forward = 0 
net.ipv4.conf.all.accept_redirects = 0 
net.ipv4.conf.all.send_redirects = 0 
net.ipv4.conf.all.accept_source_route = 0 
net.ipv4.conf.all.rp_filter = 1

이 sysctl.conf 설정은 DDoS 하에서 서버의 성능을 최대화하는 것은 물론이 가이드에서 제공 할 iptables 규칙의 효과를 돕습니다.


실제 IPtables Anti-DDoS 규칙

DDoS 공격의 영향을 완화하기 위해 최적화 된 커널 설정뿐만 아니라 mangle 테이블과 PREROUTING 체인을 사용해야한다는 사실을 알았으므로 대부분의 TCP DDoS 공격을 완화하기위한 몇 가지 예제 규칙으로 이동합니다.

DDoS 공격은 복잡합니다.

DDoS에는 여러 가지 유형이 있으며 모든 서비스에 대해 서명 기반 규칙을 유지하는 것은 거의 불가능합니다.

다행히도 연결 추적 (nf_conntrack 커널 모듈)이라는 것이 있습니다. 이는 합법적인 것으로 보이는 SYN 패킷을 사용하지 않는 거의 모든 TCP 기반 DDoS 공격을 완화하는 데 도움이 될 수 있습니다.

여기에는 가짜 TCP 플래그를 사용하는 모든 유형의 ACK 및 SYN-ACK DDoS 공격과 DDoS 공격이 포함됩니다.

우리는 이미 5 개의 간단한 iptables 규칙으로 시작하여 많은 TCP 기반 DDoS 공격을 차단할 것입니다.


잘못된 패킷 차단

iptables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP

이 규칙은 SYN 패킷이 아니며 설정된 TCP 연결에 속하지 않는 모든 패킷을 차단합니다.


SYN이 아닌 새 패킷 차단

iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
이렇게하면 새 패킷 (설정된 연결에 속하지 않음)이 모두 차단되고 SYN 플래그는 사용되지 않습니다. 이 규칙은 "잘못된 패킷 차단" 규칙과 유사하지만 다른 패킷이 포착하지 않는 패킷을 발견합니다.

흔하지 않은 MSS 값 차단
iptables -t mangle -A PREROUTING -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP
위의 iptables 규칙은 공통적이지 않은 TCP MSS 값을 사용하는 새로운 패킷을 차단합니다 (SYN 패킷 만 이전 두 규칙에 따라 새 패킷이 될 수 있음). 이렇게 하면 벙어리 SYN 플러드를 차단하는 데 도움이됩니다.

가짜 TCP 플래그가있는 패킷 차단

iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags SYN,RST SYN,RST -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,RST FIN,RST -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,ACK FIN -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,URG URG -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,FIN FIN -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,PSH PSH -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL ALL -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL NONE -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j DROP 
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP

위의 룰셋은 가짜 TCP 플래그(합법적인 패킷이 사용하지 않는 TCP 플래그)를 사용하는 패킷을 차단합니다.

개인 서브넷의 패킷 차단 (스푸핑)

iptables -t mangle -A PREROUTING -s 224.0.0.0/3 -j DROP 
iptables -t mangle -A PREROUTING -s 169.254.0.0/16 -j DROP 
iptables -t mangle -A PREROUTING -s 172.16.0.0/12 -j DROP 
iptables -t mangle -A PREROUTING -s 192.0.2.0/24 -j DROP 
iptables -t mangle -A PREROUTING -s 192.168.0.0/16 -j DROP 
iptables -t mangle -A PREROUTING -s 10.0.0.0/8 -j DROP 
iptables -t mangle -A PREROUTING -s 0.0.0.0/8 -j DROP 
iptables -t mangle -A PREROUTING -s 240.0.0.0/5 -j DROP 
iptables -t mangle -A PREROUTING -s 127.0.0.0/8 ! -i lo -j DROP
이러한 규칙은 개인 (로컬) 서브넷에서 생성 된 스푸핑 된 패킷을 차단합니다. 공용 네트워크 인터페이스에서 일반적으로 개인 원본 IP로부터 패킷을 수신하지 않으려 고합니다.

이 규칙은 루프백 인터페이스가 127.0.0.0/8 IP 공간을 사용한다고 가정합니다.

이 다섯 가지 규칙만으로도 매우 높은 패킷 속도에서 많은 TCP 기반 DDoS 공격을 차단할 수 있습니다.

위에서 언급 한 커널 설정과 규칙을 사용하면 회선 속도로 ACK 및 SYN-ACK 공격을 필터링 할 수 있습니다.

추가 규칙

iptables -t mangle -A PREROUTING -p icmp -j DROP

이렇게하면 모든 ICMP 패킷이 삭제됩니다. ICMP는 호스트가 아직 살아 있는지를 핑 (ping)하는 데에만 사용됩니다. 일반적으로 필요하지 않으며 공격자가 악용 할 수있는 또 다른 취약점만을 나타 내기 때문에 Ping of Death (ping flood), ICMP flood 및 ICMP fragmentation flood를 완화하기 위해 모든 ICMP 패킷을 차단합니다.

iptables -A INPUT -p tcp -m connlimit --connlimit-above 80 -j REJECT --reject-with tcp-reset

이 iptables 규칙은 연결 공격에 도움이됩니다. 연결이 80 개 이상인 호스트의 연결을 거부합니다. 문제가 발생할 경우 제한을 늘려야합니다. 이는 많은 수의 TCP 연결을 설정하는 합법적 인 클라이언트에게 문제를 일으킬 수 있기 때문입니다.

iptables -A INPUT -p tcp -m conntrack --ctstate NEW -m limit --limit 60/s --limit-burst 20 -j ACCEPT 
iptables -A INPUT -p tcp -m conntrack --ctstate NEW -j DROP

클라이언트가 초당 설정할 수있는 새로운 TCP 연결을 제한합니다. 이것은 연결 공격에 유용 할 수 있지만 일반적으로 SYN 홍수에 대해서는 그다지 많지 않습니다. 왜냐하면 일반적으로 끝없이 다른 스푸핑 된 소스 IP를 사용하기 때문입니다.

iptables -t mangle -A PREROUTING -f -j DROP

이 규칙은 단편화 된 패킷을 차단합니다. 일반적으로 UDP는 필요하지 않으며 UDP 단편화를 완화합니다. 하지만 대부분의 시간 동안 UDP 조각화는 네트워크 카드의 용량을 소모 할 수있는 많은 양의 대역폭을 사용합니다.이 규칙은 선택 사항이며 아마도 가장 유용하지는 않습니다.

iptables -A INPUT -p tcp --tcp-flags RST RST -m limit --limit 2/s --limit-burst 2 -j ACCEPT 
iptables -A INPUT -p tcp --tcp-flags RST RST -j DROP

이것은 들어오는 TCP RST 패킷을 제한하여 TCP RST 플러드를 완화합니다. 이 규칙의 효과는 의문의 대상입니다.


SYNPROXY로 SYN 홍수를 완화하십시오

SYNPROXY는 Linux 커널 버전 3.12 및 iptables 1.4.21에 추가 된 iptables의 새로운 대상입니다. CentOS 7은이 기능을 백 포트하고 3.10 기본 커널에서 사용할 수 있습니다.

SYNPROXY의 목적은 SYN 패킷을 보낸 호스트가 실제로 전체 TCP 연결을 설정하는지 또는 SYN 패킷을 보낸 후에 아무 것도 수행하지 않는지 확인하는 것입니다.

아무것도 수행하지 않으면 성능에 미치는 영향을 최소화하면서 패킷을 버립니다.

위에서 제공 한 iptables 규칙은 대부분의 TCP 기반 공격을 차단하지만 SYN 홍수만큼 정교 해지면 여전히 공격 유형을 통과 할 수 있습니다.

패킷 길이 (-m 길이), TOS (-m tos), TTL (-m ttl) 또는 문자열과 같이 차단할 특정 패턴이나 서명을 발견하면 규칙의 성능이 항상 향상된다는 점에 유의해야합니다 및 16 진수 값 (고급 사용자의 경우 -m 문자열 및 -m u32)

그러나 드물기는하지만 가능하지 않거나 달성하기가 쉽지 않은 경우도 있습니다. 따라서 이러한 경우 SYNPROXY를 사용할 수 있습니다.

다음은 다른 규칙을 우회하는 SYN 플러드를 완화하는 데 도움이되는 iptables SYNPROXY 규칙입니다.

SYNPROXY IPtables 규칙
아래 단추 중 하나를 사용하여 SYNPROXY 규칙의 잠금을 해제하십시오.
처럼
짹짹
+1
오류

이 규칙은 모든 포트에 적용됩니다. 활성화 된 특정 TCP 포트에서만 SYNPROXY를 사용하려면 (권장 - 또한 mangle 테이블과 PREROUTING 체인을 사용하여 사용하지 않는 모든 TCP 포트를 차단해야 함) 각 규칙에 -dport 80을 추가하면됩니다 포트 80에서만 SYNPROXY를 사용하려는 경우.

SYNPROXY가 작동하는지 확인하려면 -n1 cat / proc / net / stat / synproxy 명령을 사용하십시오. SYNPROXY를 사용하는 포트에 대해 새 TCP 연결을 설정할 때 값이 변경되면 작동합니다.


The Complete IPtables Anti-DDoS Rules

이 기사에서 설명한 각 단일 규칙을 복사하여 붙여 넣지 않으려면 Linux 서버의 기본 DDoS 보호를 위해 아래의 규칙 집합을 사용할 수 있습니다.

### 1: Drop invalid packets ### 
/sbin/iptables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP  

### 2: Drop TCP packets that are new and are not SYN ### 
/sbin/iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP 
 
### 3: Drop SYN packets with suspicious MSS value ### 
/sbin/iptables -t mangle -A PREROUTING -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP  

### 4: Block packets with bogus TCP flags ### 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags SYN,RST SYN,RST -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,RST FIN,RST -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,ACK FIN -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,URG URG -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,FIN FIN -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,PSH PSH -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL ALL -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL NONE -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j DROP 
/sbin/iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP  

### 5: Block spoofed packets ### 
/sbin/iptables -t mangle -A PREROUTING -s 224.0.0.0/3 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 169.254.0.0/16 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 172.16.0.0/12 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 192.0.2.0/24 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 192.168.0.0/16 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 10.0.0.0/8 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 0.0.0.0/8 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 240.0.0.0/5 -j DROP 
/sbin/iptables -t mangle -A PREROUTING -s 127.0.0.0/8 ! -i lo -j DROP  

### 6: Drop ICMP (you usually don't need this protocol) ### 
/sbin/iptables -t mangle -A PREROUTING -p icmp -j DROP  

### 7: Drop fragments in all chains ### 
/sbin/iptables -t mangle -A PREROUTING -f -j DROP  

### 8: Limit connections per source IP ### 
/sbin/iptables -A INPUT -p tcp -m connlimit --connlimit-above 111 -j REJECT --reject-with tcp-reset  

### 9: Limit RST packets ### 
/sbin/iptables -A INPUT -p tcp --tcp-flags RST RST -m limit --limit 2/s --limit-burst 2 -j ACCEPT 
/sbin/iptables -A INPUT -p tcp --tcp-flags RST RST -j DROP  

### 10: Limit new TCP connections per second per source IP ### 
/sbin/iptables -A INPUT -p tcp -m conntrack --ctstate NEW -m limit --limit 60/s --limit-burst 20 -j ACCEPT 
/sbin/iptables -A INPUT -p tcp -m conntrack --ctstate NEW -j DROP  

### 11: Use SYNPROXY on all ports (disables connection limiting rule) ### 
# Hidden - unlock content above in "Mitigating SYN Floods With SYNPROXY" section

Bonus Rules

다음은 Linux 서버의 전반적인 보안을 향상시키는 데 유용한 iptables 규칙입니다.

### SSH brute-force protection ### 
/sbin/iptables -A INPUT -p tcp --dport ssh -m conntrack --ctstate NEW -m recent --set 
/sbin/iptables -A INPUT -p tcp --dport ssh -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 10 -j DROP  

### Protection against port scanning ### 
/sbin/iptables -N port-scanning 
/sbin/iptables -A port-scanning -p tcp --tcp-flags SYN,ACK,FIN,RST RST -m limit --limit 1/s --limit-burst 2 -j RETURN 
/sbin/iptables -A port-scanning -j DROP

결론

이 튜토리얼은 iptables를 사용하여 DDoS 공격을 막는 가장 강력하고 효과적인 방법을 보여줍니다.

우리는 iptables 규칙을 사용하여 초당 수백만 패킷으로 정점에 도달 한 DDoS 공격을 성공적으로 완화했습니다.

우리가 조사한 동일한 주제에 대한 모든 가이드는 DDoS 트래픽을 막을 수있는 비효율적 인 방법이나 iptables 규칙의 제한된 수를 제공했습니다.

정확하게 사용된다면, iptables는 1GigE NIC의 회선 속도와 10GigE NIC의 회선 속도에 가까운 DDoS 공격을 차단할 수있는 매우 강력한 도구입니다.

iptables의 힘을 과소 평가하지 마십시오!







조직의 보안 정책에서 인증되지 않은 게스트 액세스를 차단하므로 이 공유 폴더에 액세스 할 수 없습니다.

Windows/설치

\\computer-name

으로 윈도우 공유 폴더 액세스 시

조직의 보안 정책에서 인증되지 않은 게스트 액세스를 차단하므로 이 공유 폴더에 액세스 할 수 없습니다.

와 같은 에러 발생 시 해결 방법

Windows Creators Update 이후에 에듀케이션과 엔터프라이즈 버전, 그리고 윈도우즈 서버의 경우 발생된다고 합니다.


윈도우 버튼 + R 또는
좌측하단 윈도우 표시에서 우측 마우스 클릭 후 실행

gpedit.msc <확인>

그룹정책 편집기가 실행 되는데

컴퓨터 구성 - 관리 템플릿 - 네트워크 에서

Lanman 워크스테이션 을 선택 후

보안되지 않은 게스트 로그온 사용에서 더블클릭

설정 창이 나오면 좌측 상단의 사용을 선택 후

확인 버튼 클릭


이 후에는 예전 처럼 정상적으로 공유 폴더 액세스가 가능합니다.



Ubuntu 16.04.1 MariaDB + Tomcat 9 + Apache 2.4 + PHP 7.1 설치

리눅스/설치

1. MariaDB 설치

공식 미러를 통한 Maria DB 설치

아래 명령을 실행하여 공식 Repository로 부터 Maria DB를 설치한다.

# apt install software-properties-common
# apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
# add-apt-repository 'deb [arch=amd64,i386] http://ftp.kaist.ac.kr/mariadb/repo/10.2/ubuntu xenial main'

키를 가져오고 저장소가 추가되었으면, 다음 명령으로 Maria DB를 설치한다.

# apt update

# apt install mariadb-server

Maria DB 보안 설정

설치 후 DB접속 전에 Maria DB의 보안을 설정한다. (익명 사용자 및 Test DB 제거)

# mysql_secure_installation

Maria DB 연결

Maria DB 인스턴스에 접속한다.

# mysql -u root -p

로컬 사용자 추가

mysql> create user 'user'@'localhost' identified by 'password';

로컬 및 원격 사용자 추가

mysql> create user 'user'@'%' identified by 'password';

DB권한 설정

    grant all privileges on [DB_NAME].* to 'user'@'%'; //특정 DB

    grant all privileges on *.* to 'user'@'%'; //모든 DB

    flush privileges;

Character set UTF-8설정

다음 내용을 /etc/mysql/my.cnf 에 추가한다.

[client]

default-character-set=utf8

[mysqld]

init_connect='SET collation_connection = utf8_unicode_ci'

init_connect='SET NAMES utf8'

character-set-server=utf8

collation-server=utf8_unicode_ci

Maria DB restart

# service mariadb restart


2. Tomcat 9 설치

관련 파일 다운로드

다음 파일을 다운로드 한다.

Tomcat 9.0 : http://tomcat.apache.org/download-90.cgi

Tomcat Native: http://tomcat.apache.org/download-native.cgi

Tomcat Connector : http://tomcat.apache.org/download-connectors.cgi

apr: http://apache.mirror.cdnetworks.com/apr/apr-1.6.2.tar.gz

사용자 추가

Tomcat 구동 사용자를 추가한다.

# useradd -s /bin/false -d /usr/local/tomcat tomcat

의존성 파일 설치

설치를 위해 아래 파일을 먼저 설치한다.

# apt install libssl-dev libexpat1-dev

APR 설치

# tar zxvf apr-1.6.2.tar.gz

# cd apr-1.6.2

# ./configure --prefix=/usr/local/apr

#make && make install


만약 cannot remove 'libtoolT': No such file or directory 에러 발생 시 아래 명령 후 다시 실행

cp -arp libtool libtoolT

JDK 설치

오라클 공식 Repository를 통해 JDK 를 설치한다.

# add-apt-repository ppa:webupd8team/java

# apt update

# apt install oracle-java9-installer

Tomcat 9.0 설치

tar xvzf apache-tomcat-9.0.0.M26.tar.gz

mv apache-tomcat-9.0.0.M26 /usr/local/

ln -s /usr/local/apache-tomcat-9.0.0.M26/ /usr/local/tomcat

Tomcat 사용자 권한 설정

chown -R tomcat:tomcat /usr/local/tomcat/

chmod -R g+r /usr/local/tomcat/conf

chmod g+x /usr/local/tomcat/conf

JAVA_HOME 확인

Tomcat Native 컴파일 시 JAVA_HOME을 지정해 주어야 한다. 다음 명령으로 확인한다.

update-java-alternatives -l

Tomcat Native 설치

tar xvzf tomcat-native-1.2.14-src.tar.gz

cd tomcat-native-1.2.14-src/native

./configure --with-apr=/usr/local/apr --with-java-home=/usr/lib/jvm/java-9-oracle

make && make install

환경파일 구성

/etc/profile 파일의 마지막에 다음 행 추가

export LD_LIBRARAY_PATH=/usr/local/apr/lib

export TOMCAT_HOME=/usr/local/tomcat

export PATH=$TOMCAT_HOME/bin:$PATH

export CLASSPATH=.:$JAVA_HOME/lib

설치내용 확인

# source /etc/profile
# java -version
************* 다음 내용이 출력되면 성공 ***************************
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
**********************************************************************

방화벽 포트 오픈

다음 명령으로 방화벽에서 8080 포트를 열어 준다.

# iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 8080 -j ACCEPT
# iptables-save > /etc/iptables.rules
/etc/network/interfaces 파일에 아래 라인 추가
pre-up iptables-restore < /etc/iptables.rules

Tomcat 시작

# /usr/local/tomcat/bin/startup.sh

Tomcat 동작 확인

웹브라우저에서 서버의 IP와 포트를 입력하여 접속되는 지 확인

http://server-ip:8080/

구동파일 생성

다음 명령으로 tomcat Service 파일 생성

# vi /etc/systemd/system/tomcat.service

/etc/systemd/system/tomcat.service

[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking

Environment=JAVA_HOME=/usr/lib/jvm/java-9-oracle
Environment=CATALINA_PID=/usr/local/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/usr/local/tomcat
Environment=CATALINA_BASE=/usr/local/tomcat
Environment='CATALINA_OPTS=-Xms1024M -Xmx4096M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'

ExecStart=/usr/local/tomcat/bin/startup.sh
ExecStop=/usr/local/tomcat/bin/shutdown.sh

User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target
구동파일 권한 설정
# chmod 755 /etc/systemd/system/tomcat.service
서비스 파일 reload
# systemctl daemon-reload

Tomcat 서비스 시작/종료

시작

# systemctl start tomcat

종료

# systemctl stop tomcat

부팅 시 Tomcat 서비스 시작

# systemctl enable tomcat

3. Apache 설치

관련 파일 다운로드

다음 파일을 다운로드 한다.
apr-iconv: http://apache.mirror.cdnetworks.com/apr/apr-iconv-1.2.1.tar.gz
apr-util: http://apache.mirror.cdnetworks.com/apr/apr-util-1.6.0.tar.gz
apache: http://apache.mirror.cdnetworks.com/httpd/httpd-2.4.27.tar.gz
pcre: ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.41.tar.gz

apr-iconv 설치

# tar zxvf apr-iconv-1.2.1.tar.gz

# cd apr-iconv-1.2.1

# ./configure --prefix=/usr/local/apr-iconv --with-apr=/usr/local/apr

# make && make install

apr-util 설치

# tar zxvf apr-util-1.6.0.tar.gz
# cd apr-util-1.6.0
# ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr --with-apr-iconv=/usr/local/apr-iconv/bin/apriconv
# make && make install

pcre 설치

# tar zxvf pcre-8.41.tar.gz
# cd pcre-8.41/
# ./configure --libdir=/usr/local/lib
# make && make install

사용자 추가

apache 구동 사용자를 추가한다.

# useradd -s /bin/false -d /usr/local/apache apache

Apache 설치

# tar xvzf httpd-2.4.27.tgz
# cd httpd-2.4.27
# ./configure --prefix=/usr/local/apache \
--enable-authn-alias \
--enable-cache \
--enable-file-cache \
--enable-mem-cache \
--enable-disk-cache \
--enable-mods-shared=most \
--enable-proxy=shared \
--enable-proxy-ajp=shared \
--enable-proxy-ajp=shared \
--enable-dav=shared \
--with-apr=/usr/local/apr/ \
--with-apr-util=/usr/local/apr-util/ \
--enable-ssl \
--enable-dav \
--enable-cgi \
--with-mpm=event \
--enable-cgid \
--enable-vhost-alias \
--enable-rewrite \
--enable-forward=shared \
--enable-proxy-connect=shared \
--enable-proxy-http=shared \
--enable-deflate=shared \
--enable-so \
--enable-headers=shared \
--enable-include=shared \
--enable-rewrite=shared
# make&&make install
make 도중 undefined reference to `XML_GetErrorCode' 에러 발생 시
vi build/config_vars.mk 파일 수정해서
AP_LIBS = 시작하는 라인을 찾아
-lexpat를 추가 후 다시 make

Apache 구동 사용자 설정

vi /usr/local/apache/conf/httpd.conf 로 아래처럼 사용자 수정

User apache
Group apache

Apache 시작

아래 명령으로 아파치 시작

# /usr/local/apache/bin/apachectl start

Apache 테스트

브라우저에서 서버에 접속 후 It works! 페이지가 표시 되는 지 확인

http://server-ip

구동 파일 설정

서버 실행 시 아파치 자동 실행을 위해 구동 파일을 설정한다.

vi /etc/systemd/system/httpd.service

[Unit]
Description=Apache Web Server
After=network.target

[Service]
Type=forking
#PIDFile=/run/httpd/httpd.pid
Environment=HTTPD_PID=/usr/local/apache/logs/httpd.pid
EnvironmentFile=/usr/local/apache/bin/envvars
ExecStart=/usr/local/apache/bin/apachectl -k start
ExecStop=/usr/local/apache/bin/apachectl -k graceful-stop
ExecReload=/usr/local/apache/bin/apachectl -k graceful
KillSignal=SIGCONT
PrivateTmp=true
LimitNOFILE=infinity
User=apache
Group=apache

[Install]
WantedBy=multi-user.target

구동파일 권한 설정

# chmod 755 /etc/systemd/system/httpd.service
서비스 파일 reload
# systemctl daemon-reload

실행파일 권한 설정

# chown -R apache:apache /usr/local/apache/bin/apachectl
# chown -R root:root /usr/local/apache/bin/httpd
# chmod +s /usr/local/apache/bin/httpd

Apache 서비스 시작/종료

시작

# systemctl start httpd

종료

# systemctl stop httpd

부팅 시 Apache 서비스 시작

# systemctl enable httpd

 4. PHP 설치

관련 파일 다운로드

php7을 다운로드 한다.

http://php.net/downloads.php

의존성 패키지 설치

php에서 사용할 의존성 패키지를 설치한다.

apt install libxml2-dev libbz2-dev libcurl4-openssl-dev libldap2-dev libmysqlclient-dev pkg-config libssl-dev libsslcommon2-dev libjpeg-dev libpng-dev libfreetype6-dev libldb-dev libsasl2-dev

의존성 링크 설정

php에서 라이브러리 의존성을 위해 링크를 설정한다.

ln -s /usr/lib/x86_64-linux-gnu/libldap.so /usr/lib/libldap.so
ln -s /usr/lib/x86_64-linux-gnu/liblber.so /usr/lib/liblber.so
ln -s /usr/lib/x86_64-linux-gnu/libmysqlclient.so /usr/lib/x86_64-linux-gnu/libmysqlclient_r.so

libmcrypt 설치

PHP에서 암호화 해독 알고리즘인 mcrypt를 사용하기 위해서 설치해야 한다.

tar xvzf libmcrypt-2.5.8.tar.gz
cd libmcrypt-2.5.8
./configure --prefix=/usr/local --libdir=/usr/local/lib
make && make install
/sbin/ldconfig

cd libltdl/
./configure --enable-ltdl-install --libdir=/usr/local/lib
make && make install
cd ../../

mhash 설치

hash 암호화 알고리즘.

tar xvzf mhash-0.9.9.9.tar.gz
cd mhash-0.9.9.9
./configure --prefix=/usr/local --libdir=/usr/local/lib
make && make install

mcrypt 설치

암호화 해독 알고리즘.

tar xvzf mcrypt-2.6.8.tar.gz
cd mcrypt-2.6.8/
/sbin/ldconfig
./configure --prefix=/usr/local --libdir=/usr/local/lib
make && make install

php 설치

php 를 설치한다.

tar xvzf php-7.1.10.tar.gz
cd php-7.1.10
./configure --prefix=/usr/local/php \
--with-libdir=lib \
--with-config-file-path=/usr/local/php/etc \
--with-mysqli=/usr/bin/mysql_config \
--with-apxs2=/usr/local/apache/bin/apxs \
--with-pear=/usr/share/php \
--with-freetype-dir \
--with-jpeg-dir \
--with-png-dir \
--with-zlib \
--with-libxml-dir=/usr \
--enable-xml \
--disable-rpath \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-inline-optimization \
--enable-maintainer-zts \
--enable-opcache \
--with-curl \
--with-iconv=/usr/local \
--enable-mbregex \
--enable-fpm \
--enable-mbstring \
--with-mcrypt \
--with-gd \
--enable-gd-native-ttf \
--with-openssl \
--with-mhash \
--enable-pcntl \
--enable-sockets \
--with-ldap \
--with-ldap-sasl \
--with-xmlrpc \
--enable-zip \
--enable-soap \
--with-bz2 \
--enable-static \
--enable-intl \
--enable-exif \
--with-pdo-mysql=/usr \
--with-gettext
make && make install
make 도중 umachine.h:340:13: error: ‘char16_t’ does not name a type 에러 발생 시 Makefile 을 열어서 CXXFLAGS_CLEAN 항목에  -std=c++0x 구문을 추가 후 make

php.ini 생성

아래 명령으로 Php.ini 생성.

cp -f php.ini-production /usr/local/php/etc/php.ini

opcache 활성화

/usr/local/php/etc/php.ini 의 opcache 부분을 아래와 같이 변경.

[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
/usr/local/php/etc/php.ini 의 맨 끝에 아래와 같이 추가.
zend_extension=/usr/local/php/lib/php/extensions/no-debug-zts-20160303/opcache.so

5. PHP 확장모듈 설치

관련 파일 다운로드

ImageMagick 관련파일을 다운로드 한다.

https://www.imagemagick.org/download
http://pecl.php.net/package/imagick

의존성 패키지 설치

apt install autoconf libgtk2.0-dev

ImageMagicK 설치

ImageMagick은 TIFF, JPEG, GIF, PNG, PDF 및 기타 포토 CD 등 89가지 이상의 이미지의 읽기, 쓰기 및 처리하는 데 사용할 수 있는 개발 도구입니다.

# tar xvJf ImageMagick-7.0.7-4.tar.xz
# cd ImageMagick-7.0.7-4/
# ./configure --libdir=/usr/local/lib
# make && make install
# cd ..

imagick 설치

# tar xvzf imagick-3.4.3.tgz
# cd imagick-3.4.3
# /usr/local/php/bin/phpize
# ./configure --with-php-config=/usr/local/php/bin/php-config
# make && make install
/usr/local/php/etc/php.ini 에 아래 라인 추가
extension=/usr/local/php/lib/php/extensions/no-debug-zts-20160303/imagick.so

php library 추가

# echo "/usr/local/php/lib" > /etc/ld.so.conf.d/php.conf
# ldconfig

php-cgi 프로세스 시작

# cd /usr/local/php/etc
# cp php-fpm.conf.default php-fpm.conf
# cp php-fpm.d/www.conf.default php-fpm.d/www.conf

www.conf 의 User와 Group 을 nobody --> apache 로 변경 후 php-fpm 실행
# /usr/local/php/sbin/php-fpm

Apache 에서 php 지원 추가

/usr/local/apache/conf/httpd.conf 파일을 열어서 php를 검색 후 아래 라인을 추가

AddType application/x-httpd-php .php .php3 .inc
AddType application/x-httpd-php-source .phps

Apache 재시작

service httpd restart

PHP 동작 테스트

# echo "<?php phpinfo(); ?>" > /usr/local/apache/htdocs/phptest.php

한 후 웹 브라우저에서 http://server-ip/phptest.php
해서 결과가 정상적으로 나오는 지 확인

6. Apache 와 Tomcat 통합

mod_jk 생성

tar xvzf  tomcat-connectors-1.2.42-src.tar.gz
cd tomcat-connectors-1.2.42-src/
cd native
./configure --with-apxs=/usr/local/apache/bin/apxs
make
cp ./apache-2.0/mod_jk.so /usr/local/apache/modules/

설정 파일 복사

cd ../conf
cp httpd-jk.conf /usr/local/apache/conf/mod_jk.conf
cp workers.properties /usr/local/apache/conf/

mod_jk.conf 설정

/usr/local/apache/conf/mod_jk.conf 파일에 아래 내용 추가

JkWorkersFile /usr/local/apache/conf/workers.properties
JkLogFile /usr/local/apache/logs/mod_jk.log
JkLogLevel info
JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories
JkRequestLogFormat "%w %V %T"

JkMount /servlet/* worker1
JkMount /*.jsp         worker1
JkMount /application/* worker1
JkMount /*.do          worker1
JkMount /*.class       worker1
JkMount /*.jar         worker1

설정 파일 해석

1. JkMount 위치는 Tomcat이 해석을 담당, JkUnMount는 반대임
2.  JkMount /test/* worker1 의 의미는 test 디렉토리 아래는 Tomcat이 처리를 담당
3. jsp와servlet은 tomcat이 처리, 그 외 그림이나 정적 파일은 Apache가 처리

Tomcat 프로퍼티 설정

/usr/local/apache/conf/workers.properties 에 다음 내용 추가

worker.list=worker1
worker.worker1.type=ajp13
worker.worker1.host=127.0.0.1
worker.worker1.port=8009
worker.worker1.lbfactor=50
worker.worker1.socket_keepalive=1
worker.worker1.socket_timeout=300

Apache 설정파일 수정

/usr/local/apache/conf/httpd.conf 에 다음 내용 추가

Include /usr/local/apache/conf/mod_jk.conf

Tomcat 설정변경

/usr/local/tomcat/conf/server.xml에 다음 내용 수정 (docbase 경로는 원하는 위치로 변경)

<Host 를 찾음
(다음과 같음:  <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">)

다음 줄에 다음 내용을 추가:
<Context path="" docBase="/usr/local/tomcat/webapps"
 debug="0" reloadable="true" crossContext="true"/>

Apache / Tomcat 재시작

service httpd start
service tomcat restart

성공여부 확인

다음 명령으로 실행 여부 확인 해서 아래 메시지가 나오면 성공

# tail -f /usr/local/apache/logs/mod_jk.log
[Sun Oct 01 11:10:43 2017][22961:139625235212032] [info] init_jk::mod_jk.c (3595): mod_jk/1.2.42 initialized

연동 테스트

/usr/local/tomcat/webapps/test.jsp 파일에 다음 내용 추가 후

웹 브라우저에서 http://server-ip/test.jsp 해서 Hello World! 가 출력되면 성공

<html>
<h1>JSP test<hr>
<%out.println("Hello World!");%>
</html>

Tomcat port 차단

Apache를 통해 접속하므로 Tomcat 으로 직접 연결은 차단

/etc/iptables.rules 에서 8080 접속 관련 라인 삭제하고 아래 처럼 생성

*filter
:INPUT ACCEPT [377:27429]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [231:101581]
-A INPUT -i enp0s3 -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -i enp0s3 -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -i enp0s3 -j DROP
COMMIT

iptables rule reload

# iptables-restore < /etc/iptables.rules





GIT 여러 사용자 공유하기

리눅스/설치

GIT 여러 사용자 공유


리모트 서버의 GIT를 여러 명의 사용자가 공유할 경우 아래의 방법을 사용


1. GIT 서버에서 Remote 관리용 사용자 생성 (ex: gitteam)


2. test 용 저장소 생성

mkdir TestProject.git

cd TestProject.git

git --bare init


3. 클라이언트 PC에서 git 사용자 액세스용 공개키 생성

ssh-keygen -t rsa

--> .ssh/id_rsa.pub 파일 생성됨


4. id_rsa.pub 파일을 scp 명령으로 GIT서버로 복사

scp .ssh/id_rsa.pub to_user@111.222.333.444:~


5. 복사된 사용자 계정의 .ssh/authorized_keys 에 파일 내용 추가

cat id_rsa.pub >> .ssh/authorized_keys


6. 사용자 계정에서 사용자명 추가

git config --global user.name "John"

git config --global user.email "John@gmail.com"


7. git clone 으로 제대로 받아와 지는 지 확인


git clone gitteam@IPADDR:TestProject.git


정상적으로 받아와 지면 성공

SVN to GIT

리눅스/설치

SVN migration to GIT with history


먼저 git 에 svn sub command 추가

# yum install git-svn.noarch


이 후 git 로컬 저장소를 생성할 id로 로그인

$ svn log ^/ --xml | grep -P "^<author" | sort -u | perl -pe 's/<author>(.*?)<\/author>/$1 = /' > users.txt

users.txt 를 열어서 아래 형식으로 수정

username = 사용자명 <이메일@도메인>


$ git svn clone file:///home/svn/저장소/  --authors-file=users.txt --no-metadata  --trunk /  -s <project-dir>

$ cd <project-dir>

$ git for-each-ref refs/remotes/tags | cut -d / -f 4- | grep -v @ | while read tagname; do git tag "$tagname" "tags/$tagname"; git branch -r -d "tags/$tagname"; done
$ git for-each-ref refs/remotes | cut -d / -f 3- | grep -v @ | while read branchname; do git branch "$branchname" "refs/remotes/$branchname"; git branch -r -d "$branchname"; done

$ git remote add origin ssh://user@www.example.com:2222/home/git/project.git

$ git push origin --all

$ git remote show origin



다른 머신 또는 사용자로 로그인


$git clone ssh://user@www.example.com:2222/home/git/project.git <project-dir>




HP-UX 에서 외부 접속이 안될 때

HPUX/설치

HP-UX 서버를 새로 설치 하거나 잘 되던 서버를 리부팅 하고 나니 갑자기 네트웍이 안되는 경우

신기하게도 내부망은 잘 되는 걸 봐서는 네트웍 장비 문제라고 보기는 애매한 상황

traceroute <외부IP> 하면 딱 자기 IP에서 끝남


이유는 ip_ire_gw_probe 설정 때문

이 기능은 gateway가 죽었는 지 살았는 지를 (ping을 사용해서) 주기적으로 검사해서, 죽었을 경우 gateway로 보내는 패킷을 무시해 버림

문제는 요즘 대부분 gateway가 ping 응답을 하지 않는다는 점


아래는 ip_ire_gw_probe 현재 상태 확인 방법

ndd -get /dev/ip ip_ire_gw_probe

(1 = ON, 0 = OFF)


부팅 시 nddconf 설정

#vi /etc/rc.config.d/nddconf

TRANSPORT_NAME[0]=ip

NDD_NAME[0]=ip_ire_gw_probe

NDD_VALUE[0]=0



HPUX ntp 서버 설치

HPUX/설치

인터넷에 보면 HPUX xntp 서버 설치 방법은 많다

근데 실제 해 보면 잘 되지 않는다.


아래는 편법으로 설치한 것이다.


1. depothelper 로 ntp 를 설치한다.

2. HPUX 방화벽을 체크한다.

- ntp 서비스는 udp 포트 123을 사용한다.

- /etc/opt/ipf/ipf.conf 파일 안에서 123번 포트가 막혀 있지 않은 지 확인한다.

막혀 있다면 

pass in quick on lan0 from any to any port = 123

구문을 block 구문 앞에 추가한다.

- 방화벽 서비스를 재시작한다.

/sbin/init.d/ipfboot stop

/sbin/init.d/ipfboot start


3. /etc/ntp.conf 파일에 server 를 추가한다.

server kr.pool.ntp.org

server ntp.postech.ac.kr


4. /etc/rc.config.d/netdaemons 파일의 다음 항목을 수정한다.

export NTPDATE_SERVER=132.163.4.103

export XNTPD=1

export XNTPD_ARGS=


5. /sbin/init.d/xntpd 파일에서 /usr/sbin/xntpd --> /usr/local/bin/ntpd 로 변경한다.

/sbin/init.d/xntpd start 

로 시작 후에
ntpq -pn 으로 확인해 본다.


시그널의 모든것 (All about Linux signals)

프로그래밍/C++
퍼온글 번역본 입니다.
원본 링크


시그널의 모든것 (All about Linux signals)

프로그램 안에서 시그널 처리를 하려고 할 때 대부분의 경우 간단히 아래 구문을 이용해서 처리한다:

void handler (int sig)

그리고 프로세스에 시그널을 전달할 때는 시스템 펑션 signal(2) 을 사용한다. 이것은 매우매우 단순한 경우이고, 시그널은 그것보다는 더 흥미롭다. 이 문서는 데몬을 만들 때 또는 현재 동작 또는 전체 프로그램을 인터럽팅  하지 않도록 프로그램 안에서 인터럽트를 처리해야 할 때 유용한 정보를 기술하고 있다.


여기서 다루는 내용은?

이 문서는 리눅스 안에서 어떻게 시그널이 동작되는 지 그리고 시그널을 처리하기 위해 어떻게 POSIX API를 사용하는 지에 대해 기술한다. 모든 최신 리눅스 시스템 상에서 동작하는 펑션을 다룰 것이고, 특별히 명시적으로 언급하는 한 대부분의 POSIX 시스템에도 적용될 것이다. 오래된 펑션들은 다루지 않는다. 이 문서를 이해하려면 시그널에 대한 기반 지식은 필요하다는 점도 먼저 말해 둔다.


What is signaled in Linux


프로세스는 다음과 같은 경우에 시그널을 받는다:
  • 사용자 공간에서 다른 프로세스가 kill(2) 펑션을 호출했을 때.
  • 프로세스 자체에서 abort(3) 펑션을 사용해서 시그널을 보냈을 때.
  • 자식 프로세스가 종료 될 때 OS(운영체제) 가 SIGCHLD 시그널을 보냄.
  • 제어터미널 상에서 부모 프로세스가 죽거나 멈춘 게(hangup) 감지되면 SIGHUP 시그널을 보냄.
  • 사용자가 키보드에서 SIGINT 를 보냄.
  • 프로그램이 잘못된 행위를 하면 SIGILL, SIGFPE, SIGSEGV 중의 하나를 받음.
  • 프로그램이 mmap(2) 를 사용해서 매핑한 메모리에 액세스할 때 이용 불가능일 경우(예를 들면 다른 프로세스에 의해 truncate 되었을 때) - mmap()을 사용해서 파일에 액세스 할 때 정말 끝장인 상황임. 이 경우에는 처리를 위해 더 좋은 방법이 없다.
  • gprof 같은 프로파일러가 사용되는 경우 프로그램은 때때로 SIGPROF를 받는다. 이것은 read(2) 같은 시스템 펑션 인터럽트에 대한 적절한 처리를 깜빡했을 때 때때로 문제가 된다(errno == EINTR).
  • write(2) 또는 비슷한 데이터 전송 펑션을 사용할 때 받을 상대방이 없을 경우 SIGPIPE 가 발생된다. 이것은 매우 흔한 상황으로 이러한 펑션이 단순히 errno 를 설정하고 종료할 뿐만 아니라 SIGPIPE 를 발생시키는 요인이 된다는 것을 꼭 기억해야 한다. 이 경우에 대한 간단한 예를 위해 표준 출력에 내용을 쓰고 사용자가 이 출력을 파이프라인을 통해 다른 프로그램으로 리디렉션할 때를 가정하자. 만약 데이터를 보내는 도중에 다른 프로그램이 종료된다면 SIGPIPE 가 발생된다. 또한 시그널은 이러한 이벤트가 비동기이며 어느 정도 데이터가 성공적으로 리턴했는 지 알 수 없기 때문에 펑션 에러와 함께 리턴되도록 사용된다. 이것은 소켓에 데이터를 보낼 때도 발생된다. 이것은 데이터가 버퍼되어 있으며 와이어를 타고 전송되어서 상대편에 즉시 전송되지 않기 때문에 OS가 전송 펑션이 종료된 시점에 바로 알 수 없기 때문이다. (원본글: A signal is used in addition to the normal function return with error because this event is asynchronous and you can't actually tell how much data has been successfully sent. This can also happen when you are sending data to a socket. This is because data are buffered and/or send over a wire so are not delivered to the target immediately and the OS can realize that can't be delivered after the sending function exits.)

시스널의 전체 리스트는 signal(7) man 페이지를 참조하라.


시그널 핸들러

전통적인 signal() 은 사지 않는다.


signal(2) 펑션은 시그널 핸들러를 구축하는 오래되고 가장 간단한 방법이지만 더 이상 사용되지 않는다. 사용되지 않는 이유는 몇가지 있지만 가장 중요한 이유는 오리지널 Unix구현이 시그널을 받은 후에 디폴트 값으로 핸들러를 리셋해 버리기 때문이다. 만약 죽는 프로세스를 잡기 위해 SIGCHLD 각각을 별도로 핸들링 할 필요가 있을 경우 여기는 경쟁이 필요하다. 이걸 위해서는 시그널 핸들러 안에서 다시 시그널 핸들러를 설정할 필요가 있고 signal(2) 펑션을 호출 하기 전에 다른 시그널이 도착하게 될 것이다. 이 행동은 시스템에 따라서 다르다. 또한 signal(2) 펑션은 sigaction(2) 는 제공해 주는 때때로 필요로 하는 기능들을 지원해 주지 못한다.


시그널 설정을 위해 권장하는 방법은: sigaction 

sigaction(2) 펑션은 시그널을 설정하는 가장 좋은 방법이다. 프로토타입은 다음과 같다:

  1. int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);

위에 보이는 것처럼 시그널 핸들러 펑션에의 포인터를 직접 넘길수 없고 대신에 struct sigaction 에 대한 포인터를 넘긴다. struct sigaction 은 다음과 같이 정의되어 있다:

  1. struct sigaction {
  2. void (*sa_handler)(int);
  3. void (*sa_sigaction)(int, siginfo_t *, void *);
  4. sigset_t sa_mask;
  5. int sa_flags;
  6. void (*sa_restorer)(void);
  7. };

이 구조체 필드에 대한 자세한 정의는 sigaction(2) man 페이지를 참고하라. 중요한 필드들은 다음과 같다:

  • sa_handler - 핸들러 펑션에 대한 포인터이며, signal(2)을 위한 핸들러와 같은 프로토타입을 가진다.
  • sa_sigaction - 시그널 핸들러를 실행하는 다른 방법이다. 이 펑션은 시그널 번호 외에 2개의 추가적인 인수를 가지는 데 여기서 thesiginfo_t *가 더 흥미롭다. 이것은 받은 시그널에 대한 더 많은 정보를 제공하며 나중에 자세히 설명할 것이다.
  • sa_mask 핸들러 실행 중에 시그널이 블로킹 될 지를 명시적으로 설정할 수 있도록 허용한다. 추가적으로 만약 SA_NODEFER flag를 사용하지 않으면 트리거된 시그널은 블록될 수 있다.
  • sa_flags 는 시그널 핸들링 프로세스의 동작 변경을 허용한다. 이 필드에 대한 자세한 설명은 man 페이지를 보라. sa_sigaction 핸들러를 사용하기 위해서는 여기에 SA_SIGINFO flag 를 사용해야 한다.

만약 sigaction(2) 의 추가적인 기능을 사용하지 않는다면 두 펑션의 차이는 무엇일까? 그 답은 확율과 경쟁조건이 없다는 것이다. sigaction(2) 의 기본동작은 시그널 핸들러를 재설정하지 않고 실행되는 동안 시그널이 블록되므로, 호출된 후 시그널 핸들러가 리셋되는 이슈는 sigaction(2) 에는 영향을 주지 않는다. 그래서 경쟁이 없으며 이 동작이 POSIX 규격으로 기술되었다. 다른 차이점은 signal(2) 의 경우 일부 시스템  콜은 자동적으로 재시작 하는 데 반해 경우에는 sigaction(2) 의 경우 디폴트로는 그렇지 않다는 점이다.


sigaction() 예제


다른 인수와 함께 sigaction()을 사용해서 시그널 핸들러를 설정하는 예제이다.

이 예제에서 SIGTERM 을 위한 3개 인수버전의 핸들러를 사용한다. SA_SIGINFO flag 의 설정 없이 전통적인 1개의 인수만 사용하는 시그널 핸들러 버전 사용하고 sa_handler 필드를 통해 넘겨 준다. 이것은 signal(2) 의 단순 대체이다. 이 프로그램을 실행시키고 kill PID를 해서 어떤 일이 발생되는 지 확인해 봐라.

시그널 핸들러에서 siginfo_t *siginfo 필드를 통해 송신 쪽의 PID 와 UID를 알 수 있다 . 이 구조체는 더 많은 필드를 가지고 있지만 이에 대해서는 후에 다룬다.

시그널이 도착하고 멈추고 나서 다시 호출되어야 하기 때문에 sleep(3) 펑션이 루프 안에서 사용된다.


SA_SIGINFO handler


이전 예제에서 시그널 핸들러에 더 많은 정보를 넘기기 위해 SA_SIGINFO 가 사용된다. 위에서는 시그널을 전달한 프로세스의 UID와 PID를 알려 주는 siginfo_t구조체의  si_pid 와 si_uid 필드를 봤지만 이외에도 몇개의 필드가 더 있다. 이에 대해서는 sigaction(2) man 페이지에 설명되어 있다. 리눅스에서는 오직 si_signo (signal number) 와 si_code (signal code) 필드만 모든 시그널에 대해 이용가능하다. 다른 필드의 존재여부는 시그널 타입에 의존적이다. 일부 다른 필드들은:

  • si_code - 시그널이 보내진 이유. kill(2) 또는 raise(3) 에 의해 보내졌다면 SI_USER 일 것이다. 만약 커널이 보냈다면 SI_KERNEL 일 것이다. 잘못된 어드레싱 모드로 인해 보내지는 SIGILL 은 ILL_ILLADR 같은 특별한 값을 들어 있다.
  • SIGCHLD 인 경우 필드 si_status, si_utime, si_stime 은 종료 상태 또는 죽은 프로세스의 시그널, 사용자/시스템 시간이 채워진다.
  • SIGILL, SIGFPE, SIGSEGV, SIGBUS 의 경우 si_addr 문제가 발생된 메모리 어드레스를 가진다.

추후 siginfo_t 에 대한 다른 예제를 볼 것이다.

Compiler optimization and data in signal handler


다음 예를 보자:
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <signal.h>
  4. #include <string.h>
  5.  
  6. static int exit_flag = 0;
  7.  
  8. static void hdl (int sig)
  9. {
  10. exit_flag = 1;
  11. }
  12.  
  13. int main (int argc, char *argv[])
  14. {
  15. struct sigaction act;
  16.  
  17. memset (&act, '\0', sizeof(act));
  18. act.sa_handler = &hdl;
  19. if (sigaction(SIGTERM, &act, NULL) < 0) {
  20. perror ("sigaction");
  21. return 1;
  22. }
  23.  
  24. while (!exit_flag)
  25. ;
  26.  
  27. return 0;
  28. }

이게 뭘까? 컴파일러의 최적화 설정에 따라 다르다. 최적화가 없다면 SIGTERM 또는 프로세스를 끝내는 다른 시그널을 받을  때까지 루프를 돌다가 종료한다. -O3 gcc flag 로 컴파일 하면 SIGTERM 을 받아도 종료되지 않는다. 왜 그럴까? while 루프가  -O3 로 최적화되면 exit_flag 변수가 일단 프로세서 레지스터 안으로 로드되고 나면 루프안에서는 메모리로 부터 읽어들이지 않는다. 컴파일러는 루프를 실행할 때 루프가 프로그램에서 변수를 액세스하는 유일한 장소라는 것을 인식하지 않는다. (프로그램의 다른 부분에서 액세스 되는 시그널 핸들러안의 변수를 수정하는) 그러한 경우 컴파일러에게 이 변수는 읽기 쓰기를 위해 메모리에서 액세스 해야 한다는 것을 알려야 한다. 이를 위해 변수 선언시에 volatile 키워드를 사용한다:

  1. static volatile int exit_flag = 0;


위와 같이 수정하면 모든 것이 기대대로 동작할 것이다.

Atomic Type


시그널 핸들러 안에서 원자적으로 읽기와 쓰기가 보증되는 데이터 타입으로 it:sig_atomic_t 타입이 정의되어 있다. 이 타입의 크기는 정의되어 있지 않으며, 정수형 타입이다. 이론적으로 이것은 시그널 핸들러 안에서 안전하게 읽기와 쓰기를 할 수 있는 유일한 타입이다. 다음을 명심하자:

  • 이것은 뮤텍스처럼 동작하지 않는다: 이것은 이 타입의 읽기 또는 쓰기가 인터럽터블 하지 않는 것을 보장하는 것이지 다음과 같은 코드가 안전한 것은 아니다:

  1. sig_atomic_t i = 0;
  2.  
  3. void sig_handler (int sig)
  4. {
  5. if (i++ == 5) {
  6. // ...
  7. }
  8. }

: if 동작 안에 읽기와 쓰기 동작이 있으며, 오직 단일 읽기와 단일 쓰기만이 원자적이다.

  • 이 타입을 mutex 없이 사용될 수 있는 멀티 쓰레드 프로그램에 사용하지 말아라. 이것은 단지 시그널 핸들러를 위한 것이고 뮤텍스와는 아무 상관이 없다!
  • 만약 시그널 핸들러 안에서 수정되거나 읽어들인 데이터가 또다시 수정되거나 읽히는 경우 만약 그것이 시그널이 블록된 부분에서 발생하는 경우 걱정할 필요가 없다. 하지만 여전히 volatile 키워드를 사용해야 한다.


Signal-safe functions


당신은 시그널 핸들러 안에서 아무것도 할 수 없다. 프로그램이 인터럽트 되었을 때 어떤 포인트인지 모르며 어떤 데이터가 수정되는 중간 시점에 있는 지 알수 없다는 것을 기억하라. 그것은 당신의 코드 뿐 아니라 당신이 사용하는 라이브러리 또는 표준 C 라이브러리 일 수 있다. signal(7) 안의 시그널 핸들러로 부터 안전하게 호출할 수 있는 펑션의 목록은 사실 몇개 되지 않는다. 예를 들면  open(2) 로 파일을 열 수 있고 unlink(2) 로 삭제하고, _exit(2) (exit(3) 가 아님!) 를 호출할 수 있다. 실제로 이 목록은 제한되어 있어서 할 수 있는 최선의 방법은 종료 같은 어떤 것을 프로세스에게 알려 주는 글로벌 flag 를 설정하는 것이다. wait(2) 와 waitpid(2) 펑션은 사용될 수 있고 unlink(2) 를 이용할 수 있어서 SIGCHLD 안에서 죽은 프로세스를 정리할 수 있고 pid 파일을 삭제할 수 있다. 


시그널을 처리하는 다른 방법: signalfd()


signalfd(2) 는 커널 2.6.22 부터 이용할 수 있는 꽤나 새로운 리눅스 특유의 시스템콜로 파일 기술자를 사용해서 시그널을 받을 수 있다. 이것은 핸들러 펑션을 제공하지 않고, 동기 방식으로 시그널을 처리할 수 있다. signalfd() 사용예 를 보라.

먼저 우리는 signalfd(2) 를 사용해서 sigprocmask(2) 로 처리할 시그널을 블록해야 된다. 이 펑션은 다음에 설명한다. 다음 우리는 들어오는 시그널을 읽기 위해 사용될 파일 기술자를 생성하기 위해 signalfd(2) 를 호출한다. 이 시점에 SIGTERM 또는 SIGINT 이 프로그램에 도착하면 인터럽트되지  않고 핸들러가 호출되지 않는다. 그것은 큐에 쌓이고 sfd 기술자에서 그것에 대한 정보를 읽을 수 있다. 이전에 설명했던 siginfo_t 와 비슷한 정보가 struct signalfd_siginfo  개체에 채워지며 이 개체를 읽을 때는 반드시 충분히 큰 버퍼를 제공해야 한다. 이 두 구조체는 약간 다른 필드명(si_signo 대신에 ssi_signo)을 가진다. 흥미로운 점은 sfd 기술자가 다른 파일 기술자에 대해서 할 수 있는 것처럼 동일하게 할 수 있다는 것이고 특히 당신이 다음의 것을 할 수 있다는 점이다.

  • select(2)poll(2) 그리고 다른 펑션들을 사용할 수 있다.
  • 넌 블로킹으로 만들수 있다.
  • 많은 기술자를 생성하고 각각 다른 시그널에 대해 select(2) 를 사용해서 다른 기술자를 리턴하게 해서 다른 처리를 할 수 있다.
  • fork() 후에도 파일 기술자는 닫히지 않으므로, 자식 프로세스가 부모 프로세스에 전달된 시그널을 읽을 수 있다.

이것은 많은 연결을 처리하기 위해 poll(2) 같은 펑션을 실행하는 메인루프를 가진 싱글 프로세스 서버에 사용되기에 완벽하다. 이것은 다른 비동기 처리 없이 시그널 기술자를 다른 것들과 같이 폴 되는 기술자 배열에 추가하고 처리함으로서 시그널 처리를 단순화 한다. 당신은 당신의 프로그램이 인터럽트 되지 않으므로 준비 되었을 때 시그널을 처리한다.


SIGCHLD 처리


프로그램에서 새로운 프로세스를 생성하고, 실제로 그들이  끝나기 기다리기를 원하지 않고 자식 프로세스의 종료 상태에 관심이 없고 단순히 좀비 프로세스만은 청소하기를 바란다면, SIGCHLD 핸들러는 생성한 프로세스에 대해 잊게 만들어 줄 것이다. 이 핸들러는 아래와 같이 될 수 있다.


Fragment of example: SIGCHLD handler

  1. static void sigchld_hdl (int sig)
  2. {
  3. /* Wait for all dead processes.
  4. * We use a non-blocking call to be sure this signal handler will not
  5. * block if a child was cleaned up in another part of the program. */
  6. while (waitpid(-1, NULL, WNOHANG) > 0) {
  7. }
  8. }

이 방법은 자식이 종료 될 때마다 청소되지만 프로세스가 왜 죽었는 지, 상태가 어땠는 지에 대한 정보는 잊어 버린다. 좀 더 똑똑한 핸들러도 만들수 있지만 싱글-세이프로 리스트된 어떤 펑션도 사용할 수 없다.

만약 자식 프로세스를 만든다면 SIGCHLD 는 핸들러를 가져야 한다는 걸 기억하자. 이 시그널을 무시하는 것은 정의되지 않았으므로 아무것도 하지 않는 핸들러라도 필요하다.


Handling SIGBUS


SIGBUS 시그널은 파일에 대응되지 않은 (mmap(2)로) 매핑된 메모리를 액세스할 때 프로세스에 보내진다. 일반적인 예는 매핑된 파일을 (아마도 다른 프로그램에 의해) 트렁케이트 된 후 현재의 끝을 지나 읽을 경우이다. 이러한 방식으로 파일을 액세스하는 것은 마치 그것이 힙 또는 스택 과 같은 메모리로 부터 읽어 들이는 것처럼 에러를 리턴하는 시스템 펑션을 요구하지 않습니다. 파일읽기 에러 후에 프로그램을 종료하기를 원치 않을 경우 이것은 실제로아주 나쁜 상황이다. 불행히도 SIGBUS 를 처리하는 것은 단순하고 깔끔하지는 않지만 가능하기는 하다. 만약 프로그램이 계속되기를 바란다면 longjmp(3) 를 사용해야만 한다. 이것은 goto 와 비슷하지만 더 나쁘다. 우리는 SIGBUS 를 받으면 mmap()된 메모리가 액세스되지 않는 프로그램 안에서 다른 장소로 점프 해야만 한다. 만약 이 시그널을 위한 빈 핸들러를 둘 경우 읽기 오류의 경우 프로그램이 중단된다. 시그널 핸들러는 실행 되고 제어는 에러를 발생시킨 같은 장소로 리턴한다. 그래서 우리는 시그널 핸들러로부터 다른 장소로 점프할 필요가 있다. 이것은 저 수준 으로 들리지만 표준 POSIX 펑션을 이용해서 가능하다.

예제 보기: SIGBUS handling

시그널 세이프 펑션의 리스트를 기억해야만 한다. 이 예제에서 우리는 시그널 핸들러로부터 실제로 리턴하지 않는다. 스택은 깨끗해지고 프로그램은 완전히 다른 장소에서 재시작 된다. 그래서 만약 다음과 같은 뮤텍스 잠금 중에 발생된다면:

  1. pthread_mutex_lock (&m);
  2. for (l = 0; l < 1000; l++)
  3. if (mem[l] == 'd') // BANG! SIGBUS here!
  4. j++;
  5. pthread_mutex_unlock (&m);

longjmp(3) 후에도 뮤텍스는 다른 모든 상황은 뮤텍스가 풀려야 함에도 여전히 홀딩되어 있다.

그래서 SIGBUS 처리는 가능하지만 매우 까다롭고 디버깅하기 어렵다. 프로그램 코드 또한 걸레처럼 되 버린다.


SIGSEGV 처리


SIGSEGV (segmentation fault) 시그널 처리도 가능하다. 시그널 핸들러에서 리턴되는  대부분의 경우 Seg falut 문제를 발생시킨 지점에서 다시 시작되기 때문에 아무런 의미가 없다. 그래서 만약 충돌 지점에서 프로그램을 계속할 수 있도록 프로그램의 상태를 해결할 방법이 없다면 프로그램을 그대로 종료해야만 한다. 프로그램을 재시작 할 수 있는 한가지 예는 mmap(2)를 사용하여 획득한 메모리가 읽기 전용일 때, Seg fault 의 원인이 이 메모리에 쓰기를 실행한 경우인지 체크하고 이 메모리의 보호를 변경하기 위해 mprotect(2) 를 사용하도록 시그널 핸들러를 작성하는 것이다. 얼마나 실용적인가? 나도 잘 모른다.

스택 공간을 소비하는 것은 Seg fault 의 한가지 원인이다. 이 경우 시그널 핸들러는 스택 상의 공간을 요구하기 때문에 실행 시킬 수 없다. 그러한 경우에 SIGSEGV 를 처리 하기 위해서는 sigaltstack(2) 펑션을 이용해서 시그널 핸들러를 위한 별도의 공간을 설정할 수 있다.


Handling SIGABRT


이 시그널을 처리할 때 abort(3) 펑션이 어떻게 동작하는 지를 명심해야 된다. : 이것은 시그널을 2번 발생시키지만, 두번째는 SIGABRT 핸들러가 디폴트 상태로 복원되므로, 지정된 핸들러를 가지고 있어도 프로그램은 종료된다. 그래서 프로그램이 종료 되기 전에 뭔가 할 수 있는 기회를 가진다. 앞에서 언급한 longjmp(3) 를 사용하는 대신 시그널 핸들러에서 빠져나감으로써 프로그램이 종료되지 않는 것이 가능하다.


시그널을 받았을 때 프로세스에는 어떤일이 생기나?


Default actions


시그널은 특별한 시그널 핸들러를 제공하지 않고 시그널을 블록하지 않았을 경우 각각의 시그널에 따라 정의된 디폴트 동작이 있다. 그것은 다음과 같다:

  • 프로세스를 종료한다. 이것은 SIGTERM 또는 SIGQUIT 뿐만 아니라  SIGPIPE, SIGUSR1, SIGUSR2 및 다른 시그널들 대부분 공통 동작이다. 
  • 코어 덤프와 함께 종료한다. 이것은 SIGSEGV, SIGILL, SIGABRT 류 시그널의 공통 동작이다.
  • SIGCHLD 와 같은 몇몇 시그널은 무시된다.
  • SIGSTOP (및 비슷한 시그널) 프로그램이 대기상태로 들어가도록 하고 SIGCOND 로 계속하게 한다. 쉘에서 CTRL-Z 를 누르면 발생되는 상황이 대표적이다.

디폴트 동작에 대한 자세한 내용은 signal(7) man 페이지 참조.


Interrupting system calls


만약 프로그램 안에 시그널 핸들러를 설정한다면 어떤 시스템 콜이 시그널에 의해 인터럽트되는 경우를 준비해야 한다. 어떤 시그널 핸들러를 설정하지 않았더라도, 프로그램으로 시그널이 전달될 수 있고 그래서 그것을 준비하는 것이 최선입니다. 예를 들자면 프로그램을 컴파일 할 때 gcc 옵션 중 -pg (프로파일링 활성화)를 주었다면 프로그램 실행 중에 미리 알지 못한 상태에서 때때로 SIGPROF 를 받을 때 시스템 콜이 인터럽트되는 요인이 된다.


인터럽트 되는 게 뭐야? (What is interrupted?)


시스템 콜에 사용되는 모든 시스템 또는 표준 라이브러리 펑션은 잠재적으로 인터럽트 될 수 있으며 정확히는 man 페이지를 참고해야 한다. (I/O 동작완료를 위해 기다리거나 슬립하지 않고) 바로 리턴되는 일반적인 펑션은 인터럽트 될 수 없다. 예를 들면 socket(2) 펑션은 소켓을 생성하고 아무것도 기다리지 않고 바로 리턴된다.

반면에 (네트웍 전송, 파이프 읽기, 특정한 슬립 등) 어떤 것을 기다리는 펑션들은 인터럽트 된다. 예를 들면 select(2)read(2)connect(2) 펑션은 인터럽트 된다. 이러한 펑션들이 완료를 위해 기다리는 동안 시그널이 도착해서 무슨 일이 정확히 발생되는 지는 각각의 man 페이지에 기술되어 있다.


signal 을 고려한 간단한 code


가장 단순한 경우는 nanosleep(2)을 이용해서 구현된 sleep(3) 이다. 시그널에 의해 인터럽트 되면 슬립에 들어가 있던 초를 리턴한다. 만약 시그널에 관계 없이 10초동안 슬립에 빠져 있게 하려면 아래처럼 하면 된다:

  1. #include <unistd.h>
  2. #include <signal.h>
  3.  
  4. static void hdl (int sig)
  5. {
  6. }
  7.  
  8. void my_sleep (int seconds)
  9. {
  10. while (seconds > 0)
  11. seconds = sleep (seconds);
  12. }
  13.  
  14. int main (int argc, char *argv[])
  15. {
  16. signal (SIGTERM, hdl);
  17.  
  18. my_sleep (10);
  19.  
  20. return 0;
  21. }

이 예제는 동작한다. 하지만 슬립에 빠져 있는 동안 몇개의 시그널을 보내면 다른 시간 동안 슬립에 빠지게 된다. 그 이유는 sleep(3) 인수를 받아들이고 인터럽트 후 얼마 동안 잠 들었었는 지 알려 줄 수 있도록 1초단위 값을 리턴하기 때문이다.


데이터 전송과 시그널(Data transferring and signals)


데몬 프로그램에서 매우 중요한 것은 시스템 펑션의 인터럽트 처리이다. 문제의 한 부분은 recv(2)write(2) 와 select(2) 같은 데이터를 전송하는 펑션이 시그널에 의해 인터럽트 되어서 시그널 처리 후에 하던 처리를 계속해야 한다는 점이다. 우리는 sleep(3)의 경우에 어떻게 처리하는 지를 보았다.

See an example of how to handle interruption of system calls.

This program reads from it's standard input and copies the data to the standard output. Additionally, when SIGUSR1 is received it prints tostderr how many bytes has been already read and written. It installs a signal handler which sets a global flag to 1 if called. Whatever the program does at the moment it receives the signal, the numbers are immediately printed. It works because read(2) and write(2) functions are interrupted by signals even during operation. In case of those functions two things might happen:

  • When read(2) waits for data or write(2) waits for stdout to put some data and no data were yet transfered in the call and SIGUSR1 arrives those functions exit with return value of -1. You can distinguish this situation from other errors by reading the value of the errnovariable. If it's EINTR it means that the function was interrupted without any data transfered and we can call the function again with the same parameters.
  • Another case is that some data were transfered but the function was interrupted before it finished. In this case the functions don't return an error but a value less that the supplied data size (or buffer size). Neither the return value nor the errno variable tells us that the function was interrupted by a signal, if we want to distinguish this case we need to set some flag in the signal handler (as we do in this example). To continue after interruption we need to call the function again keeping in mind that some data were consumed or read adn we must restart from the right point. In our example only the write(2) must be properly restarted, we use the written variable to track how many bytes were actually written and properly call write(2) again if there are data left in the buffer.

Remember that not all system calls behave exactly the same way, consult their manual page to make sure.

Reading the sigaction(2) manual page you can think that setting the SA_RESTART flag is simpler that handling system call interruption. The documentation says that setting it will make certain system calls automatically restartable across signals. It's not specified which calls are restarted. This flag is mainly used for compatibility with older systems, don't use it.

Blocking signals


How to block signals


There is sometime a need to block receiving some signals, not handling them. Traditional way is to use the deprecated signal(2) function with SIG_IGN constant as a signal handler. There is also newer, recommended function to do that: sigprocmask(2). It has a bit more complex usage, let's see an example of signal blocking with sigprocmask().

This program will sleep for 10 seconds and will ignore the SIGTERM signal during the sleep. It works this way because we've block the signal with sigprocmask(2). The signal is not ignored, it's blocked, it means that are queued by the kernel and delivered when we unblock the signal. This is different than ignoring the signal with signal(2). First sigprocmask(2) is more complicated, it operates in a set of signals represented by sigset_t, not on one signal. The SIG_BLOCK parameter tells that the the signals in set are to be blocked (in addition to the already blocked signals). The SIG_SETMASK tells that the signals in set are to be blocked, and signals that are not present in the set are to be unblocked. The third parameter, if not NULL, is written with the current signal mask. This allows to restore the mask after modifying the process' signal mask. We do it in this example. The first sleep(3) function is executed with SIGTERM blocked, if the signal arrives at this moment, it's queued. When we restore the original signal mask, we unblock SIGTERM and it's delivered, the signal handler is called.

See the sigprocmask(2) manual on how to use this function and sigsetops(3) on how to manipulate signal sets.

Preventing race conditions.


In the previous example nothing really useful was presented, such use of sigprocmask(2) isn't very interesting. Here is a bit more complex example of code that really needs sigprocmask(2):

Fragment of example: Signal race with select() and accept()

  1. while (!exit_request) {
  2. fd_set fds;
  3. int res;
  4.  
  5. /* BANG! we can get SIGTERM at this point. */
  6.  
  7. FD_ZERO (&fds);
  8. FD_SET (lfd, &fds);
  9.  
  10. res = select (lfd + 1, &fds, NULL, NULL, NULL);
  11.  
  12. /* accept connection if listening socket lfd is ready */
  13. }

Let's say it's an example of a network daemon that accepts connections using select(2) and accept(2). It can use select(2) because it listens on multiple interfaces or waits also for some events other than incoming connections. We want to be able to cleanly shut it down with a signal like SIGTERM (remove the PID file, wait for pending connections to finish etc.). To do this we have a handler for the signal defined which sets global flag and relay on the fact that select(2) will be interrupted when the signal arrives at the moment we are just waiting for some events. If the main loop in the program looks similarly as the above code everything works... almost. There is a specific case in which the signal will not interrupt the program even if it does nothing at all at the moment. When it arrives between checking the while condition and executing select(2). The select(2) function will not be interrupted (because signal was handled) and will sleep until some file descriptor it monitors will be ready.

This is where the sigprocmask(2) and other "new" functions are useful. Let's see an improved version:

Fragment of example: Using pselect() to avoid a signal race

  1. sigemptyset (&mask);
  2. sigaddset (&mask, SIGTERM);
  3.  
  4. if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
  5. perror ("sigprocmask");
  6. return 1;
  7. }
  8.  
  9. while (!exit_request) {
  10.  
  11. /* BANG! we can get SIGTERM at this point, but it will be
  12. * delivered while we are in pselect(), because now
  13. * we block SIGTERM.
  14. */
  15.  
  16. FD_ZERO (&fds);
  17. FD_SET (lfd, &fds);
  18.  
  19. res = pselect (lfd + 1, &fds, NULL, NULL, NULL, &orig_mask);
  20.  
  21. /* accept connection if listening socket lfd is ready */
  22. }

What's the difference between select(2) and pselect(2)? The most important one is that the later takes an additional argument of typesigset_t with set of signals that are unblocked during the execution of the system call. The idea is that the signals are blocked, then global variables/flags that are changed in signal handlers are read and then pselect(2) runs. There is no race because pselect(2) unblocks the signals atomically. See the example: the exit_request flag is checked while the signal is blocked, so there is no race here that would lead to executing pselect(2) just after the signal arrives. In fact, in this example we block the signal all the time and the only place where it can be delivered to the program is the pselect(2) execution. In real world you may block the signals only for the part of the program that contains the flag check and the pselect(2) call to allow interruption in other places in the program.
Another difference not related to the signals is that select(2)'s timeout parameter is of type struct timeval * and pselect(2)'s is const structtimespec *. See the pselect(2) manual page for more information.

If you like poll(2) there is analogous ppoll(2) functions, but in contrast of pselect(2) ppoll(2) is not a standard POSIX function.

Waiting for a signal


Suppose we want to execute an external command and wait until it exits. We don't want to wait forever, we want to set some timeout after which we will kill the child process. How to do this? To run a command we use fork(2) and execve(2). To wait for a specific process to exit we can use the waitpid(2) function, but it has no timeout parameter. We can also create a loop in which we call sleep(3) with the timeout as an argument and use the fact that sleep(3) will be interrupted by the SIGCHLD signal. This solution will work... almost. It would contain a race condition: if the process exits immediately, before we call sleep(3) we will wait until the timeout expires. It's a race similar to the one described previously.

The proper solution is to use a dedicated function to wait for a signal: see an example of using sigtimedwait().

This program creates a child process that sleeps few seconds (in a real world application this process would do something like execve(2)) and waits for it to finish. We want to implement a timeout after which the process is killed. The waitpid(2) function does not have a timeout parameter, but we use the SIGCHLD signal that is sent when the child process exits. One solution would be to have a handler for this signal and a loop with sleep(3) in it. The sleep(3) will be interrupted by the SIGCHLD signal or will sleep for the whole time which means the timeout occurred. Such a loop would have a race because the signal could arrive not in the sleep(3), but somewhere else like just before thesleep(3). To solve this we use the sigtimedwait(2) function that allows us to wait for a signal without any race. We can do this because we block the SIGCHLD signal before fork(2) and then call sigtimedwait(2) which atomically unblock the signal and wait for it. If the signal arrives it block it again and returns. It can also take a timeout parameter so it will not sleep forever. So without any trick we can wait for the signal safely.

One drawback is that if sigtimedwait(2) is interrupted by another signal it returns with an error and doesn't tell us how much time elapsed, so we don't know how to properly restart it. The proper solution is to wait for all signals we expect at this point in hte program or block other signals. There is another small bug i the program: when we kill the process, SIGCHLD is sent and we don't handle it anywhere. We should unblock the signal before waitpid(2) and have a handler for it.

Other functions to wait for a signal


There are also other functions that can be used to wait for a signal:

  • sigsuspend(2) - waits for any signal. It takes a signal mask of signals that are atomically unblocked, co it doesn't introduce race conditions.
  • sigwaitinfo(2) - like sigtimedwait(2), but without the timeout parameter.
  • pause(2) - simple function taking no argument. Just waits for any signal. Don't use it, you will introduce a race condition similar to the described previously, use sigsuspend(2).

Sending signals


Sending signal from keyboard


There are two special key combinations that can be used in a terminal to send a signal to the running application:
  • CTRL-C - sends SIGINT which default action is to terminate the application.
  • CTRL-\ - sends SIGQUIT which default action is to terminate the application dumping core.
  • CTRL-Z - sends SIGSTOP that suspends the program.

kill()


The simplest way to send a signal to the process is to use kill(2). It takes two arguments: pid (PID of the process) and sig (the signal to send). Although the function has a simple interface it's worth to read the manual page because there are few more things we can do than just sending a signal to a process:
  • The pid can be 0, the signal will be sent to all processes in the process group.
  • The pid can be -1, the signal is sent to every process you have permission to send signals except init and system processes (you won't kill system threads).
  • The pid can be less than -1 to send signal to all processes in the process group whose ID is -pid.
  • You can check is a process exists sending signal 0. Nothing is really sent, but the kill(2) return value will be as if it sent a signal, so if it's OK it means that the process exists.

Sending signals to yourself


There are two standard function that will help you to send signals to yourself:
  • raise(3) - Just send the specified signal to yourself, but if it's a multithreaded program it sends the signal to the thread, not the process.
  • abort(3) - Sends SIGABRT, but before that it will unblock this signal, so this function works always, you don't need to bother about unblocking this signal. It will also terminates you program even if you have handler for SIGABRT by restoring the default signal handler and sending the signal again. You can prevent it as was mentioned in signal handling chapter.

Sending data along with signal - sigqueue()


The sigqueue(2) function works very similar to kill(2) but is has a third argument of type const union sigval which can be used to send an integer value or a pointer that can be read in the signal handler if it reads the siginfo_t argument. If you use this function instead of 32) the handler can distinguish this with the si_code field because it will have SI_QUEUE value.

Real-time signals


POSIX 표준은 리얼타임 시그널이라 불리는 것을 정의하고 리눅스는 그것을 지원한다. 이것은 프로그래머에 의해 사용되고 의미가 미리 정의되어 있지는 않다. 시그널의 범위를 알기 위해 SIGRTMIN 과 SIGRTMAX 2개의 매크로를 이용할 수있다. 이것을 SIGRTMIN+n(n = 임의의 번호) 과 같은 식으로 사용할 수 있다. 리얼타임 시그널은 쓰레드 라이브러리 (리눅스 쓰레드와 NPTL(Native Posix Thread Library) 양쪽 다) 에서 사용되고 있으므로 실행시에 SIGRTMIN 을 조정하게 된다. 따라서 결코 시그널 숫자를 하드코딩해서는 안된다.

RT 시그널과 표준 시그널을 뭐가 다를까? 다음 2가지가 다르다:

  • 누군가 그것을 보내는 동안 시그널이 블록된다면 2개 이상의  RT 시그널이 큐에 쌓일 수 있다. 표준 시그널에서는 오직 1개만이 큐잉되고 나머지는 무시된다.
  • RT 시그널의 전송 순서는 보낸 순서와 같은 것이 보장된다.
  • 전송 프로세스의 PID 와 UID 는 siginfo_t 의 si_pid 와 si_uid 필드에 저장된다. 더 자세한 정보는 signal(7) man 페이지의 RT시그널 부분을 참고하라.


Signals and fork()


What happens with signals and signal-related settings after fork(2)? A new child starts with the signal queue empty even if some signals were queued for the parent at the time fork(2) was invoked. Signal handers and blocked signal state is inherited by the child. Attributes of file descriptors associated with signals are also inherited. In conclusion: no unexpected behavior here, you don't need to set up any signal handlers or mask in the child.

Signals and threads


단일 쓰레드 프로그램과 멀티 쓰레드 프로그램 사이의 시그널 처리는 차이가 있다. 

멀티 쓰레드 프로그램 POSIX 사양에 따르면 하나의 PID를 가지는 하나의 프로세스는 시그널이 도착했을 때 어떤 쓰레드가 인터럽트 될까? 

만약 지원되지 않는 구식 리눅스쓰레드 구현을 가지고 있다면 답은 간단하다. 모든 쓰레드는 분리된 PID를 가진다. 따라서 시그널은 kill(2)에 의해 지정된 PID를 가지는 쓰레드로 전달되며 이 경우에 모든 쓰레드는 분리된 프로세스로 처리된다. 이러한 사실은 그다지 중요하지 않다. 현대의 모든 리눅스 배포판은 더 이상 이 구현을 사용하지 않기 때문이다.

네이티브 POSIX 쓰레드 라이브러리를 사용하는 경우는 보다 흥미롭다. 여기서 설명하는 것은 POSIX 호환 구현에 대해 설명하는 것이므로 다른 POSIX 시스템에도 적용된다.


어떤 쓰레드가 시그널을 받을까?


이것은 대단히 흥미로운 질문이다. 다음 두가지 경우가 있다:

  • (kill(2) 같은 펑션을 사용해서 PID 에 보내는)프로세스 지향 시그널 : 쓰레드는 sigprocmask(2) 와 비슷한 pthread_sigmask(2)를 이용해서 분리된 시그널 마스크를 가진다. 따라서 그러한 시그널은 블록된 쓰레드에 전달되지 않는다. 그것은 시그널이 블록되지 않은 쓰레드 중 하나에 전달된다. 어떤 쓰레드가 받는 지는 정의되지 않았다. 만약 모든 쓰레드가 시그널이 블록되어 있다면 그것은 per-process 큐에 쌓인다. 만약 시그널을 위해 정의된 핸들러가 없고 디폴트 액션이 프로세스를 종료하는 거라면 전체 프로세스는 종료된다.
  • 쓰레드 지향 시그널 : 특정한 쓰레드에 시그널을 보내는 특별한 펑션 pthread_kill(2) 이 있다. 이것은 어떤 쓰레드에서 다른 쓰레드(또는 그 자신에게)로 시그널을 보내는 데 사용될 수 있다. 이 방법으로 쓰레드는 특정한 쓰레드에 보내지거나 큐잉 될 수 있다. SIGSEGV 같은 OS(운영체제)에 의해 발생된 쓰레드 지향 시그널도 있다. 만약 시그널을 위해 지정된 핸들러가 없고 디폴트 액션이 프로세스를 종료하는 것이라면 전체 프로세스가 종료된다.

위에서 본 것처럼 프로세스 범위의 큐와 쓰레드 단위의 큐가 있다.


Signal handlers


시그널 액션은 전체 프로세스를 위해 설정 된다. 멀티 쓰레드를 위한 signal(2) 의 동작은 정의되지 않았고 sigaction(2) 을 사용해야 한다. signal(7) 안의 싱글 세이프로 기술된 pthread  관련 펑션은 존재하지 않는다는 것을 명심해야 한다. 특히 시그널 핸들러 안에서 뮤텍스의 사용은 아주 나쁜 생각이다.


sigwaitinfo()/sigtimedwait() and process-directed signals


프로세스 지향 시그널을 위한 sigwaitinfo(2) 와 sigtimedwait(2) 펑션을 신뢰성 있는 동작을 얻으려면, 기다리는 모든 시그널은 모든 쓰레드에 대해 블록되어야 한다. 특히 프로세스 지향 시그널을 위해 pause()를 사용하는 것은 나쁜 생각이 될 수 있다.

Real-time signals


이전에도 말한 것처럼, 양쪽 쓰레드 구현 (LinuxThreads 와 NPTL) 은 내부적으로 일정 개수의 리얼타임 시그널을 사용한다. 그래서 그러한 시그널을 참조할 때는 SIGRTMIN+n 표현을 사용하는 것이 좋은 방법이다.

Other uses of signals


Here I'll present non-traditional uses of signals, but mainly for historical reasons. We have better mechanisms to do the same things now, but it might be interesting that signals may be used this way.

It's possible to be notified of I/O availability by a signal. It's an alternative to functions like select(2). It's done by setting the O_ASYNC flag on the file descriptor. If you do so and if I/O is available (as select(2) would consider it) a signal is sent to the process. By default it's SIGIO, but using Real-time signals is more practical and you can set up the file descriptor using fcntl(2) so that you get more information in siginfo_tstructure. See the links at the bottom of this article for more information. There is now a better way to do it on Linux: epoll(7) and similar mechanisms are available on other systems.

The dnotify mechanism uses similar technique: you are notified about file system actions using signals related to file descriptors of monitored directories or files. The recommended way of monitoring files is now inotify.

Related websites


Here are some places worth visiting that describe some topics in more details.

http://www.visolve.com/squid/whitepapers/squidrtsignal.php - Describes Squid's modifications to use RT signals to polling sockets.
http://www.linuxjournal.com/article/3985 - Old (2000), but still interesting article on how signals are implemented in Linux kernel.

It's not really everything...


The title of this article is misleading. UNIX/Linux signals is a big topic. When I was writing it I found many aspects of signals I had not known about. I'm also not a big expert, so as always: there could be bugs, not all important things may be mentioned. Comments are welcome!