IP 필터 구현 이야기
최범균 (madvirus@madvirus.net, 트위터: @madvirus)
이야기의 시작....
● 2011년 어느 날
○ 장애 신고: "고객이 웹 게임에 연결이 안 된데요!"
○ 게임 관련된 서버들 확인
■ 게임 웹 서버: 팡 팡 놀고 있음
■ DB 서버: 팡 팡 놀고 있음
■ 게임 배치 서...
이야기의 시작....
● 웹 방화벽이 장애 지점이었음!!
○ 컥, 웹 방화벽의 CPU 사용률이 100% 가까이 치솟음
● 원인
○ 웹 게임으로 인해 웹 트래픽 급증
○ 웹 방화벽의 차단 IP 목록의 개수가 무지 많음
○...
그래, 만들어볼까?
● 의심나는 것
○ 클라이언트 IP가 차단 IP인지 검사할 때, 전체 차단 IP
목록/패턴을 검색하는 건 아닐까?
○ 마치 DB에서 풀(full) 스캔하는 것과 같은 상황?
● 문뜩 떠오른 생각
○ ...
내용
● 트리 기반 차단/허용 IP 목록 관리/검색 구현
○ 검색 성능 비교
● SLF4J 방식 컴포넌트 구현
● 문자열 기반 설정
○ Scala Combinator Parser
1. 트리를 이용한 IP 패턴 목록 구성
IP 패턴을 목록으로 관리하면
1.2.3.4
1.2.3.64/26
5.6.7.*
10.20.*
...
...
...
30.*
10.30.40.51
10.30.40.52
10.30.40.51 검사
10.20.1.2 검사
...
IP 패턴을 트리로 표현하면
루트
1 10
2 12
3
4 128/25
13
14
20
*
30
40
51 52
10.30.40.51
10.20.1.2
1.2.3.200
3만개 목록일 경우,
최대 5레벨 깊이 탐색
6만...
적용할 IP 패턴
● 1.2.3.4: 정확한 매칭
● 1.2.3.n/m: 네트워크 주소를 이용한 범위 표현
○ 예:
■ 1.2.3.64/26: 1.2.3.64~127 (01000000 ~ 01111111)
■ 1.2.3...
IP 패턴의 트리 표현위한 두 클래스
루트
1 10
2 12
3
4 128/25
13
14
20
*
30
40
51 52
NumberNode
IpTree
NumberNode의 역할
● 트리 구조 상의 한 노드를 표현
● 노드 데이터 보관
○ 노드의 값을 가짐
■ 정확한 값: 예 - 1, 10, 128
■ 패턴: 전체 또는 네트워크
● *
● 64/26
● 특정 숫자가 노...
NumberNode: 객체 생성 부분 1
public class NumberNode {
private Map<String, NumberNode>
simpleChildNodeMap = // <값, 자식노드> 구성
new ...
NumberNode: 객체 생성 부분 2
public NumberNode(String number) {
this.number = number;
processPattern();
}
private void processPa...
NumberNode: 값 일치 확인
public boolean isMatch(String number) {
if (allAccept) return true;
if (isSimpleNumber) return this.nu...
NumberNode: 자식 노드 생성 1
NumberNode createOrGetChildNumber(String numberPattern) {
if (simpleChildNodeMap.containsKey(number...
NumberNode: 자식 노드 생성 2
a1 = new NumberNode("0");
b1 = a1.createOrGetChildNumber("1");
b2 = a1.createOrGetChildNumber("2");...
NumberNode: 자식 노드 검색 1
public NumberNode findMatchingChild(String number) {
NumberNode simpleChildNode =
simpleChildNodeMa...
NumberNode: 자식 노드 검색 2
// 숫자 1.10.100에 해당하는 노드 검색
child1 = a1.findMatchingChild("1");
[child1 == b1]
child2 = child1.findM...
다음 차례는 IpTree
● 루트 노드 가짐
● 입력한 IP 패턴에
맞게 트리 노드
생생
● 특정 IP가
노드 트리에
매핑되는지 검사
public class IpTree {
private NumberNode root =...
IpTree의 노드 생성 과정
IpTree ipTree = new IpTree();
ipTree.add("1.10.100.101");
// IpTree 코드
public class IpTree {
private Numb...
IpTree의 IP 포함 여부
IpTree ipTree = new IpTree();
ipTree.containsIp("1.10.100.101");
// IpTree 코드
public boolean containsIp(S...
2. IpTree를 이용한 IP 필터 모듈
IpFilter
● 주요 기능
○ IP가 차단 IP인지 확인
○ IP가 허용 IP인지 확인
○ 차단/허용 중 어떤 규칙을 먼저 적용할지
○ 두 규칙에 일치하지 않을 경우 허용할지 여부 지정
● 구성
○ Config: 설...
Config 클래스: 설정 정보 표현
public class Config {
private boolean defaultAllow;
private boolean allowFirst;
private List<String> ...
ConfigIpFilter 클래스
public class ConfigIpFilter implements IpFilter {
private boolean defaultAllow;
private IpTree allowIpT...
IpFilter의 성능 1
● 성능 검사에는 비교 대상 필요
○ 비교 대상 구현 (ListIpFilter)
■ 리스트 이용 IP 패턴 목록 유지
■ 순차적으로 IP 패턴 비교
● IP 패턴
○ IP 패턴 37,538개
...
IpFilter의 성능 2
● 테스트 방법
○ 37,538개 IP 패턴을 차단 IP 목록으로 설정
○ 이 IP 패턴 목록 중 랜덤하게 5개 패턴 도출
○ 5개 패턴에 속한 전체 IP들을 검사
● 트리 기반 IpFilte...
IpFilter의 성능 3
● 멀티 쓰레드 상황
○ 쓰레드 10개, 20개, 50개 실행
○ 각 쓰레드 마다 '위 테스트 방법'으로 실행
○ 단, 각 쓰레드는 5개 패턴의 전체 IP 개수가 아닌
최대 10만개만 검사
■...
IpFilter 성능 결과1 - 1개 쓰레드
트리 방식 리스트 방식
회차 실행 회수 실행 시간
(밀리초)
평균
(밀리초)
실행 회수 실행 시간
(밀리초)
평균
(밀리초)
1 199,680 678 0.003400 50,9...
IpFilter 성능 결과2 - 10개 쓰레드
트리 방식 리스트 방식
회차 실행 회수 실행 시간
(밀리초)
평균
(밀리초)
실행 회수 실행 시간
(밀리초)
평균
(밀리초)
1 695,840 7,592 0.010912 6...
IpFilter 성능 결과3 - 20개 쓰레드
트리 방식 리스트 방식
회차 실행 회수 실행 시간
(밀리초)
평균
(밀리초)
실행 회수 실행 시간
(밀리초)
평균
(밀리초)
1 1,215,400 21,850 0.01797...
IpFilter 성능 결과4 - 평균/편차
트리 방식 리스트 방식
쓰레드 평균 편차 평균 편차
1 0.003980 0.002794 0.334767 0.221089
10 0.009039 0.001183 1.205352 0...
3. SLF4J 방식 컴포넌트 구현
SLF4J 컴포넌트 구성 1 - jar 파일
slf4j-api.jar
- LoggerFactory
- ILoggerFactory
slf4j-jdk14.jar
- StaticLoggerBinder
- JDK14Logger...
SLF4J 컴포넌트 구성 2 - api 소스
빌드 과정에서 api에
포함된 impl 패키지는
jar 파일에 포함되지 않음
컴파일 위한 코드
서블릿용 모듈: SLF4J 흉내내기 1
public abstract class IpBlockerFactory {
public static IpBlockerFactory getInstance() {
return new I...
서블릿용 모듈: SLF4J 흉내내기 2
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plug...
4. Scala Combinator Parser
문자열로 설정하고 싶어요
# 주석도 넣고,
order allow,deny
default true
allow from 1.2.3.4
allow from 1.2.3.* # 뒤에 주석
...
...
deny from all
...
문맥 자유 문법과 파서
-- 문법은 대충 이런식 (컴파일러 시간에 배웠던 기억은 있으나, 내용 자체는 거의 기억 안 남)
conf : confPart? (eol confPart)*
confPart: commentPart...
Scala Combinator Parser
● Scala가 기본으로 제공하는 파서
● 기본적인 문맥 자유 문법 지원
○ 코드에서 문법과 결과를 바로 표현
● 자바 코드에서 손쉽게 호출 가능
Scala Combinator Parser 적용 코드
class Conf extends JavaTokenParsers {
override val whiteSpace = """[ t]+""".r
def conf: Pars...
쉬운 사용 위한 보조 클래스
// Java 코드
public class FileConfigFactory
extends ConfigFactory {
@Override
public Config create(String va...
정리
내용 정리
● 트리 기반의 IP 패턴 목록 관리
○ 패턴 개수에 상관없이 일정한 탐색 속도 제공
○ 다중 쓰레드 접근 시에도 성능 저하 상대적 낮음
● SLF4J 방식 컴포넌트 구성
○ 동적 클래스 로딩이 아닌 jar ...
관련 자료
● ip-filter 소스
○ 소스: https://github.com/madvirus/ip-filter
○ 사용법: https://github.com/madvirus/ip-
filter/wiki/HOME_k...
광고
내가 만든 코드를 함께 리뷰할 선배 프로그래머가 없나요?
주변 프로그래머들이 너무 바빠서 코드 리뷰할 시간이 없나요?
이런 상황이라면, 고민하지 마시고 연락주세요.
함께 코드를 보고 논의하고 수정하는 시간을 가져보...
Q&A, 논의
(이메일 madvirus@madvirus.net, 트위터 @madvirus로 언제든 연락주세요)
Upcoming SlideShare
Loading in...5
×

간단 Ip 필터 구현 이야기

12,498

Published on

ip-filter 구현 이야기
- 트리 기반 IP 목록 구성
- SLF4J 방식 컴포넌트
- Scala Combinator Parser

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
12,498
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
14
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

간단 Ip 필터 구현 이야기

  1. 1. IP 필터 구현 이야기 최범균 (madvirus@madvirus.net, 트위터: @madvirus)
  2. 2. 이야기의 시작.... ● 2011년 어느 날 ○ 장애 신고: "고객이 웹 게임에 연결이 안 된데요!" ○ 게임 관련된 서버들 확인 ■ 게임 웹 서버: 팡 팡 놀고 있음 ■ DB 서버: 팡 팡 놀고 있음 ■ 게임 배치 서버: 팡 팡 놀고 있음 ○ 증상 ■ 내부 네트워크에서 잘 연결 됨 ■ 외부 네트워크에서 연결 매우 느림 ○ 장애 지점 ■ 더 뒤져보니.......
  3. 3. 이야기의 시작.... ● 웹 방화벽이 장애 지점이었음!! ○ 컥, 웹 방화벽의 CPU 사용률이 100% 가까이 치솟음 ● 원인 ○ 웹 게임으로 인해 웹 트래픽 급증 ○ 웹 방화벽의 차단 IP 목록의 개수가 무지 많음 ○ 각 클라이언트 IP가 차단 IP인지 검사하느라 CPU가 무지 바쁘게 일하게 됨 ○ 방화벽이 버벅되면서 외부에서 접근하는 모든 웹 연 결에 문제가 발생함 ● 일단 문제 해소부터 ○ 웹 게임에 대해 차단 규칙 제외해서 일단 급한 불은 껐으나,,, 보안에 찜찜함이 남음...
  4. 4. 그래, 만들어볼까? ● 의심나는 것 ○ 클라이언트 IP가 차단 IP인지 검사할 때, 전체 차단 IP 목록/패턴을 검색하는 건 아닐까? ○ 마치 DB에서 풀(full) 스캔하는 것과 같은 상황? ● 문뜩 떠오른 생각 ○ DB에 인덱스를 만들 듯, IP 목록을 인덱스 방식으로 관리하면 차단 IP인지 여부를 빠르게 확인할 수 있을 텐데..... ● 만들어볼까? ○ 생각만 하고, 2년이 지난뒤에 만들게 된 ip-filter !
  5. 5. 내용 ● 트리 기반 차단/허용 IP 목록 관리/검색 구현 ○ 검색 성능 비교 ● SLF4J 방식 컴포넌트 구현 ● 문자열 기반 설정 ○ Scala Combinator Parser
  6. 6. 1. 트리를 이용한 IP 패턴 목록 구성
  7. 7. IP 패턴을 목록으로 관리하면 1.2.3.4 1.2.3.64/26 5.6.7.* 10.20.* ... ... ... 30.* 10.30.40.51 10.30.40.52 10.30.40.51 검사 10.20.1.2 검사 3만개 목록일 경우, 평균 1.5만개 비교 6만개 목록일 경우, 평균 3만개 비교
  8. 8. IP 패턴을 트리로 표현하면 루트 1 10 2 12 3 4 128/25 13 14 20 * 30 40 51 52 10.30.40.51 10.20.1.2 1.2.3.200 3만개 목록일 경우, 최대 5레벨 깊이 탐색 6만개 목록일 경우, 최대 5레벨 깊이 탐색
  9. 9. 적용할 IP 패턴 ● 1.2.3.4: 정확한 매칭 ● 1.2.3.n/m: 네트워크 주소를 이용한 범위 표현 ○ 예: ■ 1.2.3.64/26: 1.2.3.64~127 (01000000 ~ 01111111) ■ 1.2.3.0/26: 1.2.3.0~63 (00000000 ~ 00111111) ● 1.2.3.*: 전체 범위 ○ 예 ■ 1.2.3.*: 1.2.3.0~1.2.3.255 ■ 1.2.*: 1.2.0.0~1.2.255.255
  10. 10. IP 패턴의 트리 표현위한 두 클래스 루트 1 10 2 12 3 4 128/25 13 14 20 * 30 40 51 52 NumberNode IpTree
  11. 11. NumberNode의 역할 ● 트리 구조 상의 한 노드를 표현 ● 노드 데이터 보관 ○ 노드의 값을 가짐 ■ 정확한 값: 예 - 1, 10, 128 ■ 패턴: 전체 또는 네트워크 ● * ● 64/26 ● 특정 숫자가 노드 데이터와 매칭되는 여부 ● 노드 구성 관련 ○ 자식 노드 생성 기능 ○ 특정 숫자에 매칭 되는 자식 노드 찾아주는 기능
  12. 12. NumberNode: 객체 생성 부분 1 public class NumberNode { private Map<String, NumberNode> simpleChildNodeMap = // <값, 자식노드> 구성 new HashMap<String, NumberNode>(); private List<NumberNode> patternChildNodes = // 패턴 자식 목록 new ArrayList<NumberNode>(); private final String number; // 노드가 가진 값 private boolean isSimpleNumber; // 정확한 매칭 값인지 여부 private int filterNumber; // 네트워크 주소인 경우 사용 private int lastValueOfNetworkNumber; // 네트워크 주소인 경우 사용 private boolean allAccept; // 값이 "*" 인지 여부 private static int[] filterNumbers = { // 네트워크 주소 처리에 사용 0x00, // 24 0x80, // 25 0xC0, // 26 0xE0, // 27 0xF0, // 28 0xF8, // 29 0xFC // 30 }; 자식 노드 구성 예: simpleChildNodeMap = { "1": childNodeX("1"), "2": childNodeY("2"), "5": childNodeZ("5") } patternChildNodes = [ childNodeP("*"), childNodeQ("64/26") ] 자식 노드 보관 용도
  13. 13. NumberNode: 객체 생성 부분 2 public NumberNode(String number) { this.number = number; processPattern(); } private void processPattern() { if (number.equals("*")) { isSimpleNumber = false; allAccept = true; return; } int slashIdx = number.indexOf("/"); if (slashIdx == -1) { isSimpleNumber = true; return; } this.lastValueOfNetworkNumber = // a/b에서 a Integer.parseInt(number.substring(0, slashIdx)); int bitsOfNetworkNumber = // a/b 에서 b Integer.parseInt(number.substring(slashIdx + 1)); this.filterNumber = filterNumbers[bitsOfNetworkNumber - 24]; this.isSimpleNumber = false; } new NumberNode("*"); - number = "*" - isSimpleNumber = false - allAccept = true new NumberNode("1") - number = "1" - isSimpleNumber = true - allAccept = false new NumberNode("64/26"); - number = "64/26" - isSimpleNumber = false - allAccept = false - lastValueOfNetworkNumber = 64 (0100 0000) * 실제범위: 0100 0000 ~ 0111 1111 - filterNumber = 0xC0 (1100 0000) // 64/26인 경우 private static int[] filterNumbers = { 0x00 /* 24 */, 0x80 /* 25 */, 0xC0 /* 26 */ , 0xE0 , 0xF0 , 0xF8 , 0xFC }; lastValueOfNetworkNumber = 64, bitsOfNetworkNumber = 26 this.filterNumber = 0xC0 (filterNumbers[26-24])
  14. 14. NumberNode: 값 일치 확인 public boolean isMatch(String number) { if (allAccept) return true; if (isSimpleNumber) return this.number.equals(number); int filtered = filterNumber & Integer.parseInt(number); return filtered == lastValueOfNetworkNumber; } all = new NumberNode("*"); - number = "*" - isSimpleNumber = false - allAccept = true one = new NumberNode("1") - number = "1" - isSimpleNumber = true - allAccept = false range = new NumberNode("64/26"); - number = "64/26" - isSimpleNumber = false - allAccept = false - lastValueOfNetworkNumber = 64 - filterNumber = 0xC0 (1100 0000) all.isMatch("1") ==> true all.isMatch("100") ==> true all.isMatch("200") ==> true one.isMatch("1") ==> true one.isMatch("2") ==> false one.isMathc("100") ==> false // 64 = 0100 0000 range.isMatch("1") ==> false // 1100 0000 & 0000 0001 => 0000 0000 range.isMatch("63") ==> false // 1100 0000 & 0011 1111 => 0000 0000 range.isMatch("64") ==> true // 1100 0000 & 0100 0000 => 0100 0000 range.isMatch("67") ==> true // 1100 0000 & 0100 0011 => 0100 0000 range.isMatch("128") ==> false // 1100 0000 & 1000 0000 => 0000 0000
  15. 15. NumberNode: 자식 노드 생성 1 NumberNode createOrGetChildNumber(String numberPattern) { if (simpleChildNodeMap.containsKey(numberPattern)) return simpleChildNodeMap.get(numberPattern); for (NumberNode patternChild : patternChildNodes) if (patternChild.number.equals(numberPattern)) return patternChild; NumberNode childNode = new NumberNode(numberPattern); if (childNode.isSimpleNumber) simpleChildNodeMap.put(numberPattern, childNode); else patternChildNodes.add(childNode); return childNode; } a1 = new NumberNode(""); b1 = a1.createOrGetChildNumber("1"); b2 = a1.createOrGetChildNumber("2"); b3 = a1.createOrGetChildNumber("8/29"); c1 = b1.createOrGetChildNumber("10"); c2 = b2.createOrGetChildNumber("*"); d1 = c1.createOrGetChildNumber("100"); d2 = c1.createOrGetChildNumber("64/26");
  16. 16. NumberNode: 자식 노드 생성 2 a1 = new NumberNode("0"); b1 = a1.createOrGetChildNumber("1"); b2 = a1.createOrGetChildNumber("2"); b3 = a1.createOrGetChildNumber("8/29"); c1 = b1.createOrGetChildNumber("10"); c2 = b2.createOrGetChildNumber("*"); d1 = c1.createOrGetChildNumber("100"); d2 = c1.createOrGetChildNumber("64/26"); a1 [number = "0"] "1" 0 1 2"2" b1 [number = "1"] "10" 0 1 b2 [number = "2"] 0 1 2 b3 [number = "8/29"] 0 1 2 c1 [number = "10"] "100" 0 1 2 c2 [number = "*"] 0 0 1 2 d1 [number = "100"] 0 1 2 d1 [number = "64/26"] 0 1 2
  17. 17. NumberNode: 자식 노드 검색 1 public NumberNode findMatchingChild(String number) { NumberNode simpleChildNode = simpleChildNodeMap.get(number); if (simpleChildNode != null) return simpleChildNode; for (NumberNode patternChildNode : patternChildNodes) if (patternChildNode.isMatch(number)) return patternChildNode; return null; } public boolean isMatch(String number) { if (allAccept) return true; if (isSimpleNumber) return this.number.equals(number); int filtered = filterNumber & Integer.parseInt(number); return filtered == lastValueOfNetworkNumber; } a1 = new NumberNode(""); b1 = a1.createOrGetChildNumber("1"); b2 = a1.createOrGetChildNumber("2"); b3 = a1.createOrGetChildNumber("8/29"); // 8/29 = 0000 1000 ~ 0000 1111 (8~15) a1.findMatchingChild("1") == b1 a2.findMatchingChild("3") == null a2.findMatchingChild("9") == b3 c1 = b1.createOrGetChildNumber("10"); c2 = b2.createOrGetChildNumber("*"); b2.findMatchingChild("1") == c2 b2.findMatchingChild("100") == c2
  18. 18. NumberNode: 자식 노드 검색 2 // 숫자 1.10.100에 해당하는 노드 검색 child1 = a1.findMatchingChild("1"); [child1 == b1] child2 = child1.findMatchingChild("10"); [child2 == c1] child3 = child2.findMatchingChild("100"); [child3 == d1] a1 [number = "0"] "1" 0 1 2"2" b1 [number = "1"] "10" 0 1 b2 [number = "2"] 0 1 2 b3 [number = "8/29"] 0 1 2 c1 [number = "10"] "100" 0 1 2 c2 [number = "*"] 0 0 1 2 d1 [number = "100"] 0 1 2 d1 [number = "64/26"] 0 1 2
  19. 19. 다음 차례는 IpTree ● 루트 노드 가짐 ● 입력한 IP 패턴에 맞게 트리 노드 생생 ● 특정 IP가 노드 트리에 매핑되는지 검사 public class IpTree { private NumberNode root = new NumberNode(""); public void add(String ip) { String[] ipNumbers = ip.split("."); NumberNode node = root; for (String number : ipNumbers) { node = node.createOrGetChildNumber(number); } } public boolean containsIp(String ip) { String[] ipNumbers = ip.split("."); NumberNode node = root; for (String number : ipNumbers) { node = node.findMatchingChild(number); if (node == null) return false; if (node.isAllAccept()) return true; } return true; } }
  20. 20. IpTree의 노드 생성 과정 IpTree ipTree = new IpTree(); ipTree.add("1.10.100.101"); // IpTree 코드 public class IpTree { private NumberNode root = new NumberNode (""); public void add(String ip) { String[] ipNumbers = ip.split("."); NumberNode node = root; for (String number : ipNumbers) node = node.createOrGetChildNumber(number); } root [number = ""] "1" 0 1 2"2" [number = "1"] "10" 0 1 [number = "10"] "100" 0 1 2 [number = "100"] "101" 0 1 2 [number = "101"] 0 1 2 ipNumbers = ["1", "10", "100", "101"] node = root number = "1" node.createOrGetChildNumber("1") number = "10" node.createOrGetChildNumber("10") number = "100" node.createOrGetChildNumber("100") number = "101" node.createOrGetChildNumber("101")
  21. 21. IpTree의 IP 포함 여부 IpTree ipTree = new IpTree(); ipTree.containsIp("1.10.100.101"); // IpTree 코드 public boolean containsIp(String ip) { String[] ipNumbers = ip.split("."); NumberNode node = root; for (String number : ipNumbers) { node = node.findMatchingChild (number); if (node == null) return false; if (node.isAllAccept()) return true; } return true; } root [number = "0"] [number = "1"] [number = "10"] [number = "100"] [number = "101"] ipNumbers = ["1", "10", "100", "101"] node = root number = "1" node = node.findMatchingChild("1") node != null node.isAllAccept() == false number = "10" node = node.findMatchingChild("10") node != null node.isAllAccept() == false number = "100" node = node.findMatchingChild("100") node != null node.isAllAccept() == false number = "101" node = node.findMatchingChild("101") node != null node.isAllAccept() == false
  22. 22. 2. IpTree를 이용한 IP 필터 모듈
  23. 23. IpFilter ● 주요 기능 ○ IP가 차단 IP인지 확인 ○ IP가 허용 IP인지 확인 ○ 차단/허용 중 어떤 규칙을 먼저 적용할지 ○ 두 규칙에 일치하지 않을 경우 허용할지 여부 지정 ● 구성 ○ Config: 설정 정보 ○ IpFilter: 인터페이스 ○ ConfigIpFilter: IpFilter의 구현
  24. 24. Config 클래스: 설정 정보 표현 public class Config { private boolean defaultAllow; private boolean allowFirst; private List<String> allowList = new ArrayList<String>(); private List<String> denyList = new ArrayList<String>(); public void setDefaultAllow(boolean defaultAllow) { this.defaultAllow = defaultAllow; } public boolean isDefaultAllow() { return defaultAllow; } public void allow(String ip) { allowList.add(ip); } public void deny(String ip) { denyList.add(ip); } public void setAllowFirst(boolean allowFirst) { this.allowFirst = allowFirst; } public boolean isAllowFirst() { return allowFirst; } public List<String> getAllowList() { return allowList; } public List<String> getDenyList() { return denyList; } } Config config = new Config(); config.setDefaultAllow(false); config.setAllowFirst(flase); config.allow("1.2.3.4"); config.allow("10.20.30.40"); config.deny("1.2.3.5"); config.deny("10.30.*"); config.deny("10.40.80.*");
  25. 25. ConfigIpFilter 클래스 public class ConfigIpFilter implements IpFilter { private boolean defaultAllow; private IpTree allowIpTree; private IpTree denyIpTree; private boolean allowFirst; public ConfigIpFilter(Config config) { defaultAllow = config.isDefaultAllow(); allowFirst = config.isAllowFirst(); allowIpTree = makeIpTree(config.getAllowList()); denyIpTree = makeIpTree(config.getDenyList()); } private IpTree makeIpTree(List<String> ipList) { IpTree ipTree = new IpTree(); for (String ip : ipList) ipTree.add(ip); return ipTree; } @Override public boolean accept(String ip) { if (allowFirst) { if (allowIpTree.containsIp(ip)) return true; if (denyIpTree.containsIp(ip)) return false; } else { if (denyIpTree.containsIp(ip)) return false; if (allowIpTree.containsIp(ip)) return true; } return defaultAllow; } } Config config = new Config(); config.setDefaultAllow(false); config.setAllowFirst(flase); config.allow("1.2.3.4"); config.allow("10.20.30.40"); config.deny("1.2.3.4"); config.deny("10.30.*"); config.deny("10.40.80.*"); IpFilter filter = new ConfigIpFilter(config); filter.accept("10.20.30.40"); // true: allow 규칙 filter.accept("10.30.50.51"); // false: deny 규칙 filter.accept("1.2.3.4"); // false: deny 규칙 먼저 filter.accept("101.1.2.3"); // false: defaultAllow
  26. 26. IpFilter의 성능 1 ● 성능 검사에는 비교 대상 필요 ○ 비교 대상 구현 (ListIpFilter) ■ 리스트 이용 IP 패턴 목록 유지 ■ 순차적으로 IP 패턴 비교 ● IP 패턴 ○ IP 패턴 37,538개 ■ https://github.com/madvirus/ip-filter/wiki/ip-list-config-for-performance-test ○ 패턴에 포함되는 IP 총 개수 약 3억 3천만
  27. 27. IpFilter의 성능 2 ● 테스트 방법 ○ 37,538개 IP 패턴을 차단 IP 목록으로 설정 ○ 이 IP 패턴 목록 중 랜덤하게 5개 패턴 도출 ○ 5개 패턴에 속한 전체 IP들을 검사 ● 트리 기반 IpFilter와 ListIpFilter에 대해 ○ 위 테스트 방법을 5회 진행해서 결과 값 구함 ■ 실행 시간 ■ 1개 당 검사 시간 = 실행 시간 / IP 개수 ● 테스트 장비 (노트북) ○ Intel Core i5-2457M @1.6 GHz ○ Win 7 64b ○ JDK 6 (1.6.0_26)
  28. 28. IpFilter의 성능 3 ● 멀티 쓰레드 상황 ○ 쓰레드 10개, 20개, 50개 실행 ○ 각 쓰레드 마다 '위 테스트 방법'으로 실행 ○ 단, 각 쓰레드는 5개 패턴의 전체 IP 개수가 아닌 최대 10만개만 검사 ■ 각 쓰레드가 최대한 겹처서 실행되도록 하기 위함
  29. 29. IpFilter 성능 결과1 - 1개 쓰레드 트리 방식 리스트 방식 회차 실행 회수 실행 시간 (밀리초) 평균 (밀리초) 실행 회수 실행 시간 (밀리초) 평균 (밀리초) 1 199,680 678 0.003400 50,944 28,631 0.562029 2 1,450,240 3,648 0.002516 1,212,928 181,893 0.152436 3 22,016 196 0.008931 12,800 6,397 0.499768 4 804,352 2,109 0.002622 377,088 152,709 0.404970 5 1,120,256 2,723 0.002431 273,920 14,964 0.054632 평균 0.003980 평균 0.334767
  30. 30. IpFilter 성능 결과2 - 10개 쓰레드 트리 방식 리스트 방식 회차 실행 회수 실행 시간 (밀리초) 평균 (밀리초) 실행 회수 실행 시간 (밀리초) 평균 (밀리초) 1 695,840 7,592 0.010912 672,033 745,404 1.110667 2 816,640 6,325 0.007746 847,712 837,813 0.988323 3 698,304 6,301 0.009024 720,576 633,170 1.101748 4 901,120 8,216 0.009118 792,000 976,851 1.233398 5 664,096 5,576 0.008397 693,024 1,162,127 1.677894 평균 0.009039 평균 1.205352
  31. 31. IpFilter 성능 결과3 - 20개 쓰레드 트리 방식 리스트 방식 회차 실행 회수 실행 시간 (밀리초) 평균 (밀리초) 실행 회수 실행 시간 (밀리초) 평균 (밀리초) 1 1,215,400 21,850 0.017978 1,693,024 3,436,681 2.029907 2 1,549,088 22,001 0.014203 1,248,832 1,447,470 1.159059 3 1,676,480 25,228 0.015048 1,630,304 5,131,852 3.147789 4 1,383,552 17,147 0.012394 1,633,728 4,168,919 2.551783 5 1,598,049 20,416 0.012776 1,516,736 2,786,563 1.837211 평균 0.014480 평균 2.145150
  32. 32. IpFilter 성능 결과4 - 평균/편차 트리 방식 리스트 방식 쓰레드 평균 편차 평균 편차 1 0.003980 0.002794 0.334767 0.221089 10 0.009039 0.001183 1.205352 0.280404 20 0.014480 0.002230 2.145150 0.750186 50 0.033292 0.007773 X X
  33. 33. 3. SLF4J 방식 컴포넌트 구현
  34. 34. SLF4J 컴포넌트 구성 1 - jar 파일 slf4j-api.jar - LoggerFactory - ILoggerFactory slf4j-jdk14.jar - StaticLoggerBinder - JDK14LoggerFactory (impl ILoggerFactory) slf4j-logj12.jar - StaticLoggerBinder - Log4jLoggerFactory (impl ILoggerFactory) // LoggerFactory 클래스 public static ILoggerFactory getILoggerFactory() { ... return StaticLoggerBinder.getSingleton() .getLoggerFactory(); ... } public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } public class StaticLoggerBinder implements LoggerFactoryBinder { private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); public static final StaticLoggerBinder getSingleton() { return SINGLETON; } private final ILoggerFactory loggerFactory; private StaticLoggerBinder() { loggerFactory = new org.slf4j.impl.JDK14LoggerFactory(); } public ILoggerFactory getLoggerFactory() { return loggerFactory; }
  35. 35. SLF4J 컴포넌트 구성 2 - api 소스 빌드 과정에서 api에 포함된 impl 패키지는 jar 파일에 포함되지 않음 컴파일 위한 코드
  36. 36. 서블릿용 모듈: SLF4J 흉내내기 1 public abstract class IpBlockerFactory { public static IpBlockerFactory getInstance() { return new IpBlockerFactoryImpl(); } abstract public IpBlocker create(Map<String, String> config) throws IpBlockerCreationException; }
  37. 37. 서블릿용 모듈: SLF4J 흉내내기 2 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <phase>prepare-package</phase> <goals> <goal>run</goal> </goals> </execution> </executions> <configuration> <tasks> <delete dir="target/classes/org/chimi/ipfilter/web/impl"/> </tasks> </configuration> </plugin> </plugins> </build> jar 파일 생성시 impl 삭제
  38. 38. 4. Scala Combinator Parser
  39. 39. 문자열로 설정하고 싶어요 # 주석도 넣고, order allow,deny default true allow from 1.2.3.4 allow from 1.2.3.* # 뒤에 주석 ... ... deny from all ● 장점 ○ DSL로서 이해가 쉬움(Domain Specific Language) ○ 파일 등으로 설정시 작성/편집이 용이 ○ HTTP 등으로 설정 정보 제공시 응답 데이터 생 성 용이 ● 필요한 것 ○ 문자열로부터 Config 객체 생성하기 ○ 문법을 만들고, 파서로 좀 해보고 싶은데...
  40. 40. 문맥 자유 문법과 파서 -- 문법은 대충 이런식 (컴파일러 시간에 배웠던 기억은 있으나, 내용 자체는 거의 기억 안 남) conf : confPart? (eol confPart)* confPart: commentPart | orderPart | defaultPart | allowOrDenyPart | emptyLine commentPart: '#' ANY orderPart: "order" orderValue commentPart? orderValue: "allow" "," "deny" | "deny" "," "allow" defaultPart: "default" BOOLEAN commentPart? allowOrDenyPart: allow | deny allow: "allow" "from" ipPattern commentPart? deny: "deny" "from" ipPattern commentPart? ipPattern: "all" | (d+.){1,3}(*) | (d+.d+.d+.d+/d+) | (d+.d+.d+.d+) ● 자바용 파서 생성기 : ANTLR 등 존재 ○ 사용법이 다소 복잡 (문법 파일 만들고, 코드 생성하고 등등) ● 사용법이 쉬우면서 자바와 연동되는 파서 필요 ○ 마침 공부중이던 Scala의 Combinator Parser 선택
  41. 41. Scala Combinator Parser ● Scala가 기본으로 제공하는 파서 ● 기본적인 문맥 자유 문법 지원 ○ 코드에서 문법과 결과를 바로 표현 ● 자바 코드에서 손쉽게 호출 가능
  42. 42. Scala Combinator Parser 적용 코드 class Conf extends JavaTokenParsers { override val whiteSpace = """[ t]+""".r def conf: Parser[Config] = repsep(confPart, eol) ^^ (... 코드 생략 ... ) def confPart: Parser[Any] = commentPart | orderPart | defaultPart | allowOrDenyPart | emptyLine def commentPart: Parser[String] = """#(.*)""".r ^^ (x => x) def orderPart: Parser[Tuple2[String, Boolean]] = "order" ~ orderValue ~ opt(commentPart) ^^ (...) def orderValue: Parser[Boolean] = { "allow" ~ "," ~ "deny" ^^ (x => true) | "deny" ~ "," ~ "allow" ^^ (x => false) } def defaultPart: Parser[Tuple2[String, Boolean]] = "default" ~ booleanValue ^^ (x => ("default", x._2)) def booleanValue: Parser[Boolean] = "true" ^^ (x => true) | "false" ^^ (x => false) def allowOrDenyPart: Parser[Tuple2[String, String]] = allow ^^ (x => ("allow", x)) | deny ^^ (x => ("deny", x)) def allow: Parser[String] = "allow" ~ "from" ~ ipPattern ~ opt(commentPart) ^^ (x => x._1._2) def deny: Parser[String] = "deny" ~ "from" ~ ipPattern ~ opt(commentPart) ^^ (x => x._1._2) def ipPattern: Parser[String] = "all" ^^ (x => "*") | """(d+.){1,3}(*)""".r ^^ (x => x) | """(d+.d+.d+.d+/d+)""".r ^^ (x => x) | """(d+.d+.d+.d+)""".r ^^ (x => x) def emptyLine: Parser[String] = "" def eol: Parser[String] = """(r?n)+""".r }
  43. 43. 쉬운 사용 위한 보조 클래스 // Java 코드 public class FileConfigFactory extends ConfigFactory { @Override public Config create(String value) { return new ConfParser() .parse(readFromFile(value)); } private String readFromFile(String fileName) { try { return IOUtil.read( new FileReader(fileName)); } catch (IOException e) { throw new ConfigFactoryException(e); } } // Scala 코드 class ConfParser extends Conf { def parse(confText: String): Config = { val result = parseAll(conf, confText) if (result.successful) result.get else throw new ConfParserException(result.toString) } }
  44. 44. 정리
  45. 45. 내용 정리 ● 트리 기반의 IP 패턴 목록 관리 ○ 패턴 개수에 상관없이 일정한 탐색 속도 제공 ○ 다중 쓰레드 접근 시에도 성능 저하 상대적 낮음 ● SLF4J 방식 컴포넌트 구성 ○ 동적 클래스 로딩이 아닌 jar 교체 방식 ● Scala를 이용한 문법/파서 구현 ○ 외부 DSL 구현을 쉽게 할 수 있도록 도와줌 ○ 자바와의 연계가 쉬움
  46. 46. 관련 자료 ● ip-filter 소스 ○ 소스: https://github.com/madvirus/ip-filter ○ 사용법: https://github.com/madvirus/ip- filter/wiki/HOME_kr ● SLF4J 소스 ○ https://github.com/qos-ch/slf4j ● Scala ○ http://www.scala-lang.org/ ○ 쉽게 배워서 빨리 써먹는 스칼라 프로그래밍 (번역) ■ http://kangcom.com/sub/view.asp? sku=201304120013
  47. 47. 광고 내가 만든 코드를 함께 리뷰할 선배 프로그래머가 없나요? 주변 프로그래머들이 너무 바빠서 코드 리뷰할 시간이 없나요? 이런 상황이라면, 고민하지 마시고 연락주세요. 함께 코드를 보고 논의하고 수정하는 시간을 가져보아요~ 1. 시간/장소: 저녁 시간대, 당산~사당 사이의 커피집 2. 준비물: 함께 코드를 볼 수 있는 노트북 및 코드 수정이 가능한 개발도 구(이클립스 등) 3. 코드 리뷰 가능한 범위: 자바 기반의 코드 4. 연락 방법 a. 카페 댓글(http://cafe.daum.net/javacan/MsBU/13 글에 댓글) b. 트위터 멘션 또는 DM (@madvirus) c. 이메일 (madvirus@madvirus.net) d. 페이스북 (https://www.facebook.com/beomkyun.choi) 5. 개발 얘기도 합니다.
  48. 48. Q&A, 논의 (이메일 madvirus@madvirus.net, 트위터 @madvirus로 언제든 연락주세요)
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×