Post on 21-May-2015
description
IP 필터 구현 이야기최범균 (madvirus@madvirus.net, 트위터: @madvirus)
이야기의 시작....
● 2011년 어느 날○ 장애 신고: "고객이 웹 게임에 연결이 안 된데요!"○ 게임 관련된 서버들 확인
■ 게임 웹 서버: 팡 팡 놀고 있음■ DB 서버: 팡 팡 놀고 있음■ 게임 배치 서버: 팡 팡 놀고 있음
○ 증상■ 내부 네트워크에서 잘 연결 됨■ 외부 네트워크에서 연결 매우 느림
○ 장애 지점■ 더 뒤져보니.......
이야기의 시작....
● 웹 방화벽이 장애 지점이었음!!○ 컥, 웹 방화벽의 CPU 사용률이 100% 가까이 치솟음
● 원인○ 웹 게임으로 인해 웹 트래픽 급증○ 웹 방화벽의 차단 IP 목록의 개수가 무지 많음○ 각 클라이언트 IP가 차단 IP인지 검사하느라 CPU가 무지 바쁘게 일하게 됨
○ 방화벽이 버벅되면서 외부에서 접근하는 모든 웹 연결에 문제가 발생함
● 일단 문제 해소부터○ 웹 게임에 대해 차단 규칙 제외해서 일단 급한 불은 껐으나,,, 보안에 찜찜함이 남음...
그래, 만들어볼까?
● 의심나는 것○ 클라이언트 IP가 차단 IP인지 검사할 때, 전체 차단 IP 목록/패턴을 검색하는 건 아닐까?
○ 마치 DB에서 풀(full) 스캔하는 것과 같은 상황?● 문뜩 떠오른 생각
○ DB에 인덱스를 만들 듯, IP 목록을 인덱스 방식으로 관리하면 차단 IP인지 여부를 빠르게 확인할 수 있을텐데.....
● 만들어볼까?○ 생각만 하고, 2년이 지난뒤에 만들게 된 ip-filter !
내용
● 트리 기반 차단/허용 IP 목록 관리/검색 구현○ 검색 성능 비교
● SLF4J 방식 컴포넌트 구현● 문자열 기반 설정
○ Scala Combinator Parser
1. 트리를 이용한 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만개 비교
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레벨 깊이 탐색
적용할 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
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: 객체 생성 부분 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")]
자식 노드 보관 용도
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])
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
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");
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
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
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
다음 차례는 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; }}
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")
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
2. IpTree를 이용한 IP 필터 모듈
IpFilter
● 주요 기능○ IP가 차단 IP인지 확인○ IP가 허용 IP인지 확인○ 차단/허용 중 어떤 규칙을 먼저 적용할지○ 두 규칙에 일치하지 않을 경우 허용할지 여부 지정
● 구성○ Config: 설정 정보○ IpFilter: 인터페이스○ ConfigIpFilter: IpFilter의 구현
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.*");
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
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천만
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)
IpFilter의 성능 3
● 멀티 쓰레드 상황○ 쓰레드 10개, 20개, 50개 실행○ 각 쓰레드 마다 '위 테스트 방법'으로 실행○ 단, 각 쓰레드는 5개 패턴의 전체 IP 개수가 아닌최대 10만개만 검사■ 각 쓰레드가 최대한 겹처서 실행되도록 하기 위함
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
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
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
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
3. SLF4J 방식 컴포넌트 구현
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; }
SLF4J 컴포넌트 구성 2 - api 소스
빌드 과정에서 api에포함된 impl 패키지는jar 파일에 포함되지 않음
컴파일 위한 코드
서블릿용 모듈: SLF4J 흉내내기 1
public abstract class IpBlockerFactory { public static IpBlockerFactory getInstance() { return new IpBlockerFactoryImpl(); }
abstract public IpBlocker create(Map<String, String> config) throws IpBlockerCreationException;}
서블릿용 모듈: 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 삭제
4. Scala Combinator Parser
문자열로 설정하고 싶어요
# 주석도 넣고,order allow,denydefault trueallow from 1.2.3.4allow from 1.2.3.* # 뒤에 주석......deny from all
● 장점○ DSL로서 이해가 쉬움(Domain Specific
Language)○ 파일 등으로 설정시 작성/편집이 용이○ HTTP 등으로 설정 정보 제공시 응답 데이터 생성 용이
● 필요한 것○ 문자열로부터 Config 객체 생성하기○ 문법을 만들고, 파서로 좀 해보고 싶은데...
문맥 자유 문법과 파서-- 문법은 대충 이런식 (컴파일러 시간에 배웠던 기억은 있으나, 내용 자체는 거의 기억 안 남)
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 선택
Scala Combinator Parser
● Scala가 기본으로 제공하는 파서● 기본적인 문맥 자유 문법 지원
○ 코드에서 문법과 결과를 바로 표현● 자바 코드에서 손쉽게 호출 가능
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}
쉬운 사용 위한 보조 클래스 // 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) }}
정리
내용 정리
● 트리 기반의 IP 패턴 목록 관리○ 패턴 개수에 상관없이 일정한 탐색 속도 제공○ 다중 쓰레드 접근 시에도 성능 저하 상대적 낮음
● SLF4J 방식 컴포넌트 구성○ 동적 클래스 로딩이 아닌 jar 교체 방식
● Scala를 이용한 문법/파서 구현○ 외부 DSL 구현을 쉽게 할 수 있도록 도와줌○ 자바와의 연계가 쉬움
관련 자료
● 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
광고
내가 만든 코드를 함께 리뷰할 선배 프로그래머가 없나요?주변 프로그래머들이 너무 바빠서 코드 리뷰할 시간이 없나요?
이런 상황이라면, 고민하지 마시고 연락주세요.함께 코드를 보고 논의하고 수정하는 시간을 가져보아요~
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. 개발 얘기도 합니다.
Q&A, 논의(이메일 madvirus@madvirus.net, 트위터 @madvirus로 언제든 연락주세요)