간단 Ip 필터 구현 이야기

48
IP 필터 구현 이야기 최범균 ([email protected], 트위터: @madvirus)

description

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

Transcript of 간단 Ip 필터 구현 이야기

Page 1: 간단 Ip 필터 구현 이야기

IP 필터 구현 이야기최범균 ([email protected], 트위터: @madvirus)

Page 2: 간단 Ip 필터 구현 이야기

이야기의 시작....

● 2011년 어느 날○ 장애 신고: "고객이 웹 게임에 연결이 안 된데요!"○ 게임 관련된 서버들 확인

■ 게임 웹 서버: 팡 팡 놀고 있음■ DB 서버: 팡 팡 놀고 있음■ 게임 배치 서버: 팡 팡 놀고 있음

○ 증상■ 내부 네트워크에서 잘 연결 됨■ 외부 네트워크에서 연결 매우 느림

○ 장애 지점■ 더 뒤져보니.......

Page 3: 간단 Ip 필터 구현 이야기

이야기의 시작....

● 웹 방화벽이 장애 지점이었음!!○ 컥, 웹 방화벽의 CPU 사용률이 100% 가까이 치솟음

● 원인○ 웹 게임으로 인해 웹 트래픽 급증○ 웹 방화벽의 차단 IP 목록의 개수가 무지 많음○ 각 클라이언트 IP가 차단 IP인지 검사하느라 CPU가 무지 바쁘게 일하게 됨

○ 방화벽이 버벅되면서 외부에서 접근하는 모든 웹 연결에 문제가 발생함

● 일단 문제 해소부터○ 웹 게임에 대해 차단 규칙 제외해서 일단 급한 불은 껐으나,,, 보안에 찜찜함이 남음...

Page 4: 간단 Ip 필터 구현 이야기

그래, 만들어볼까?

● 의심나는 것○ 클라이언트 IP가 차단 IP인지 검사할 때, 전체 차단 IP 목록/패턴을 검색하는 건 아닐까?

○ 마치 DB에서 풀(full) 스캔하는 것과 같은 상황?● 문뜩 떠오른 생각

○ DB에 인덱스를 만들 듯, IP 목록을 인덱스 방식으로 관리하면 차단 IP인지 여부를 빠르게 확인할 수 있을텐데.....

● 만들어볼까?○ 생각만 하고, 2년이 지난뒤에 만들게 된 ip-filter !

Page 5: 간단 Ip 필터 구현 이야기

내용

● 트리 기반 차단/허용 IP 목록 관리/검색 구현○ 검색 성능 비교

● SLF4J 방식 컴포넌트 구현● 문자열 기반 설정

○ Scala Combinator Parser

Page 6: 간단 Ip 필터 구현 이야기

1. 트리를 이용한 IP 패턴 목록 구성

Page 7: 간단 Ip 필터 구현 이야기

IP 패턴을 목록으로 관리하면

1.2.3.41.2.3.64/265.6.7.*10.20.*.........30.*10.30.40.5110.30.40.52

10.30.40.51 검사10.20.1.2 검사

3만개 목록일 경우,평균 1.5만개 비교

6만개 목록일 경우,평균 3만개 비교

Page 8: 간단 Ip 필터 구현 이야기

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레벨 깊이 탐색

Page 9: 간단 Ip 필터 구현 이야기

적용할 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

Page 10: 간단 Ip 필터 구현 이야기

IP 패턴의 트리 표현위한 두 클래스

루트

1 10

2 12

3

4 128/25

13

14

20

*

30

40

51 52

NumberNode

IpTree

Page 11: 간단 Ip 필터 구현 이야기

NumberNode의 역할

● 트리 구조 상의 한 노드를 표현● 노드 데이터 보관

○ 노드의 값을 가짐■ 정확한 값: 예 - 1, 10, 128■ 패턴: 전체 또는 네트워크

● *● 64/26

● 특정 숫자가 노드 데이터와 매칭되는 여부● 노드 구성 관련

○ 자식 노드 생성 기능○ 특정 숫자에 매칭 되는 자식 노드 찾아주는 기능

Page 12: 간단 Ip 필터 구현 이야기

NumberNode: 객체 생성 부분 1public 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")]

자식 노드 보관 용도

Page 13: 간단 Ip 필터 구현 이야기

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 = 26this.filterNumber = 0xC0 (filterNumbers[26-24])

Page 14: 간단 Ip 필터 구현 이야기

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") ==> trueall.isMatch("100") ==> trueall.isMatch("200") ==> true

one.isMatch("1") ==> trueone.isMatch("2") ==> falseone.isMathc("100") ==> false

// 64 = 0100 0000range.isMatch("1") ==> false // 1100 0000 & 0000 0001 => 0000 0000range.isMatch("63") ==> false // 1100 0000 & 0011 1111 => 0000 0000range.isMatch("64") ==> true // 1100 0000 & 0100 0000 => 0100 0000range.isMatch("67") ==> true // 1100 0000 & 0100 0011 => 0100 0000range.isMatch("128") ==> false // 1100 0000 & 1000 0000 => 0000 0000

Page 15: 간단 Ip 필터 구현 이야기

NumberNode: 자식 노드 생성 1NumberNode 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");

Page 16: 간단 Ip 필터 구현 이야기

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

Page 17: 간단 Ip 필터 구현 이야기

NumberNode: 자식 노드 검색 1public 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") == b1a2.findMatchingChild("3") == nulla2.findMatchingChild("9") == b3

c1 = b1.createOrGetChildNumber("10");c2 = b2.createOrGetChildNumber("*");

b2.findMatchingChild("1") == c2b2.findMatchingChild("100") == c2

Page 18: 간단 Ip 필터 구현 이야기

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

Page 19: 간단 Ip 필터 구현 이야기

다음 차례는 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; }}

Page 20: 간단 Ip 필터 구현 이야기

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")

Page 21: 간단 Ip 필터 구현 이야기

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 != nullnode.isAllAccept() == false

number = "10"node = node.findMatchingChild("10")node != nullnode.isAllAccept() == false

number = "100"node = node.findMatchingChild("100")node != nullnode.isAllAccept() == false

number = "101"node = node.findMatchingChild("101")node != nullnode.isAllAccept() == false

Page 22: 간단 Ip 필터 구현 이야기

2. IpTree를 이용한 IP 필터 모듈

Page 23: 간단 Ip 필터 구현 이야기

IpFilter

● 주요 기능○ IP가 차단 IP인지 확인○ IP가 허용 IP인지 확인○ 차단/허용 중 어떤 규칙을 먼저 적용할지○ 두 규칙에 일치하지 않을 경우 허용할지 여부 지정

● 구성○ Config: 설정 정보○ IpFilter: 인터페이스○ ConfigIpFilter: IpFilter의 구현

Page 24: 간단 Ip 필터 구현 이야기

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.*");

Page 25: 간단 Ip 필터 구현 이야기

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

Page 26: 간단 Ip 필터 구현 이야기

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천만

Page 27: 간단 Ip 필터 구현 이야기

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)

Page 28: 간단 Ip 필터 구현 이야기

IpFilter의 성능 3

● 멀티 쓰레드 상황○ 쓰레드 10개, 20개, 50개 실행○ 각 쓰레드 마다 '위 테스트 방법'으로 실행○ 단, 각 쓰레드는 5개 패턴의 전체 IP 개수가 아닌최대 10만개만 검사■ 각 쓰레드가 최대한 겹처서 실행되도록 하기 위함

Page 29: 간단 Ip 필터 구현 이야기

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

Page 30: 간단 Ip 필터 구현 이야기

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

Page 31: 간단 Ip 필터 구현 이야기

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

Page 32: 간단 Ip 필터 구현 이야기

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

Page 33: 간단 Ip 필터 구현 이야기

3. SLF4J 방식 컴포넌트 구현

Page 34: 간단 Ip 필터 구현 이야기

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; }

Page 35: 간단 Ip 필터 구현 이야기

SLF4J 컴포넌트 구성 2 - api 소스

빌드 과정에서 api에포함된 impl 패키지는jar 파일에 포함되지 않음

컴파일 위한 코드

Page 36: 간단 Ip 필터 구현 이야기

서블릿용 모듈: SLF4J 흉내내기 1

public abstract class IpBlockerFactory { public static IpBlockerFactory getInstance() { return new IpBlockerFactoryImpl(); }

abstract public IpBlocker create(Map<String, String> config) throws IpBlockerCreationException;}

Page 37: 간단 Ip 필터 구현 이야기

서블릿용 모듈: 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 삭제

Page 38: 간단 Ip 필터 구현 이야기

4. Scala Combinator Parser

Page 39: 간단 Ip 필터 구현 이야기

문자열로 설정하고 싶어요

# 주석도 넣고,order allow,denydefault trueallow from 1.2.3.4allow from 1.2.3.* # 뒤에 주석......deny from all

● 장점○ DSL로서 이해가 쉬움(Domain Specific

Language)○ 파일 등으로 설정시 작성/편집이 용이○ HTTP 등으로 설정 정보 제공시 응답 데이터 생성 용이

● 필요한 것○ 문자열로부터 Config 객체 생성하기○ 문법을 만들고, 파서로 좀 해보고 싶은데...

Page 40: 간단 Ip 필터 구현 이야기

문맥 자유 문법과 파서-- 문법은 대충 이런식 (컴파일러 시간에 배웠던 기억은 있으나, 내용 자체는 거의 기억 안 남)

conf : confPart? (eol confPart)*confPart: commentPart | orderPart | defaultPart | allowOrDenyPart | emptyLinecommentPart: '#' ANYorderPart: "order" orderValue commentPart?orderValue: "allow" "," "deny" | "deny" "," "allow"defaultPart: "default" BOOLEAN commentPart?allowOrDenyPart: allow | denyallow: "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 선택

Page 41: 간단 Ip 필터 구현 이야기

Scala Combinator Parser

● Scala가 기본으로 제공하는 파서● 기본적인 문맥 자유 문법 지원

○ 코드에서 문법과 결과를 바로 표현● 자바 코드에서 손쉽게 호출 가능

Page 42: 간단 Ip 필터 구현 이야기

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}

Page 43: 간단 Ip 필터 구현 이야기

쉬운 사용 위한 보조 클래스 // 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) }}

Page 44: 간단 Ip 필터 구현 이야기

정리

Page 45: 간단 Ip 필터 구현 이야기

내용 정리

● 트리 기반의 IP 패턴 목록 관리○ 패턴 개수에 상관없이 일정한 탐색 속도 제공○ 다중 쓰레드 접근 시에도 성능 저하 상대적 낮음

● SLF4J 방식 컴포넌트 구성○ 동적 클래스 로딩이 아닌 jar 교체 방식

● Scala를 이용한 문법/파서 구현○ 외부 DSL 구현을 쉽게 할 수 있도록 도와줌○ 자바와의 연계가 쉬움

Page 46: 간단 Ip 필터 구현 이야기

관련 자료

● 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

Page 47: 간단 Ip 필터 구현 이야기

광고

내가 만든 코드를 함께 리뷰할 선배 프로그래머가 없나요?주변 프로그래머들이 너무 바빠서 코드 리뷰할 시간이 없나요?

이런 상황이라면, 고민하지 마시고 연락주세요.함께 코드를 보고 논의하고 수정하는 시간을 가져보아요~

1. 시간/장소: 저녁 시간대, 당산~사당 사이의 커피집2. 준비물: 함께 코드를 볼 수 있는 노트북 및 코드 수정이 가능한 개발도구(이클립스 등)

3. 코드 리뷰 가능한 범위: 자바 기반의 코드4. 연락 방법

a. 카페 댓글(http://cafe.daum.net/javacan/MsBU/13 글에 댓글)b. 트위터 멘션 또는 DM (@madvirus)c. 이메일 ([email protected])d. 페이스북 (https://www.facebook.com/beomkyun.choi)

5. 개발 얘기도 합니다.

Page 48: 간단 Ip 필터 구현 이야기

Q&A, 논의(이메일 [email protected], 트위터 @madvirus로 언제든 연락주세요)