SlideShare uma empresa Scribd logo
1 de 48
Baixar para ler offline
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.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만개 비교
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: 객체 생성 부분 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")
]
자식 노드 보관 용도
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])
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
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");
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: 자식 노드 검색 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
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 != 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
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,deny
default true
allow from 1.2.3.4
allow from 1.2.3.* # 뒤에 주석
...
...
deny from all
● 장점
○ DSL로서 이해가 쉬움(Domain Specific
Language)
○ 파일 등으로 설정시 작성/편집이 용이
○ HTTP 등으로 설정 정보 제공시 응답 데이터 생
성 용이
● 필요한 것
○ 문자열로부터 Config 객체 생성하기
○ 문법을 만들고, 파서로 좀 해보고 싶은데...
문맥 자유 문법과 파서
-- 문법은 대충 이런식 (컴파일러 시간에 배웠던 기억은 있으나, 내용 자체는 거의 기억 안 남)
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 선택
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로 언제든 연락주세요)

Mais conteúdo relacionado

Mais procurados

KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613
KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613
KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613KTH, 케이티하이텔
 
빠르게 활용하는 파이썬3 스터디(ch1~4)
빠르게 활용하는 파이썬3 스터디(ch1~4)빠르게 활용하는 파이썬3 스터디(ch1~4)
빠르게 활용하는 파이썬3 스터디(ch1~4)SeongHyun Ahn
 
cuda포스터-박일남
cuda포스터-박일남cuda포스터-박일남
cuda포스터-박일남DzH QWuynh
 
Coursera Machine Learning으로 기계학습 배우기 : week2
Coursera Machine Learning으로 기계학습 배우기 : week2Coursera Machine Learning으로 기계학습 배우기 : week2
Coursera Machine Learning으로 기계학습 배우기 : week2Kwangsik Lee
 
[ETHCon Korea 2019] Park joohyung 박주형
[ETHCon Korea 2019] Park joohyung 박주형[ETHCon Korea 2019] Park joohyung 박주형
[ETHCon Korea 2019] Park joohyung 박주형ethconkr
 
Java Final Report
Java Final ReportJava Final Report
Java Final ReportHwangcy
 

Mais procurados (7)

KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613
KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613
KTH_Detail day_화성에서 온 개발자 금성에서 온 기획자 시리즈_5차_데이터분석_조범석_20120613
 
빠르게 활용하는 파이썬3 스터디(ch1~4)
빠르게 활용하는 파이썬3 스터디(ch1~4)빠르게 활용하는 파이썬3 스터디(ch1~4)
빠르게 활용하는 파이썬3 스터디(ch1~4)
 
cuda포스터-박일남
cuda포스터-박일남cuda포스터-박일남
cuda포스터-박일남
 
Coursera Machine Learning으로 기계학습 배우기 : week2
Coursera Machine Learning으로 기계학습 배우기 : week2Coursera Machine Learning으로 기계학습 배우기 : week2
Coursera Machine Learning으로 기계학습 배우기 : week2
 
[ETHCon Korea 2019] Park joohyung 박주형
[ETHCon Korea 2019] Park joohyung 박주형[ETHCon Korea 2019] Park joohyung 박주형
[ETHCon Korea 2019] Park joohyung 박주형
 
Nexacro
NexacroNexacro
Nexacro
 
Java Final Report
Java Final ReportJava Final Report
Java Final Report
 

Destaque

자바8 람다식 소개
자바8 람다식 소개자바8 람다식 소개
자바8 람다식 소개beom kyun choi
 
자바8 스트림 API 소개
자바8 스트림 API 소개자바8 스트림 API 소개
자바8 스트림 API 소개beom kyun choi
 
TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나beom kyun choi
 
java 8 람다식 소개와 의미 고찰
java 8 람다식 소개와 의미 고찰java 8 람다식 소개와 의미 고찰
java 8 람다식 소개와 의미 고찰Sungchul Park
 
keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)beom kyun choi
 
Tdd live spring camp 2013
Tdd live spring camp 2013Tdd live spring camp 2013
Tdd live spring camp 2013beom kyun choi
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Testbeom kyun choi
 
세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍
세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍
세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍Jay JH Park
 
세션3. geth 클라이언트 실습 및 모니터링과 시각화
세션3. geth 클라이언트 실습 및 모니터링과 시각화세션3. geth 클라이언트 실습 및 모니터링과 시각화
세션3. geth 클라이언트 실습 및 모니터링과 시각화Jay JH Park
 
세션5. web3.js와 Node.js 를 사용한 dApp 개발
세션5. web3.js와 Node.js 를 사용한 dApp 개발세션5. web3.js와 Node.js 를 사용한 dApp 개발
세션5. web3.js와 Node.js 를 사용한 dApp 개발Jay JH Park
 
세션2. 이더리움 합의 알고리즘과 마이닝
세션2. 이더리움 합의 알고리즘과 마이닝세션2. 이더리움 합의 알고리즘과 마이닝
세션2. 이더리움 합의 알고리즘과 마이닝Jay JH Park
 
세션1. block chain as a platform
세션1. block chain as a platform세션1. block chain as a platform
세션1. block chain as a platformJay JH Park
 

Destaque (13)

자바8 람다식 소개
자바8 람다식 소개자바8 람다식 소개
자바8 람다식 소개
 
자바8 스트림 API 소개
자바8 스트림 API 소개자바8 스트림 API 소개
자바8 스트림 API 소개
 
TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나
 
java 8 람다식 소개와 의미 고찰
java 8 람다식 소개와 의미 고찰java 8 람다식 소개와 의미 고찰
java 8 람다식 소개와 의미 고찰
 
keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)
 
Tdd live spring camp 2013
Tdd live spring camp 2013Tdd live spring camp 2013
Tdd live spring camp 2013
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
 
Spring Boot 소개
Spring Boot 소개Spring Boot 소개
Spring Boot 소개
 
세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍
세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍
세션4. 예제로 배우는 스마트 컨트랙트 프로그래밍
 
세션3. geth 클라이언트 실습 및 모니터링과 시각화
세션3. geth 클라이언트 실습 및 모니터링과 시각화세션3. geth 클라이언트 실습 및 모니터링과 시각화
세션3. geth 클라이언트 실습 및 모니터링과 시각화
 
세션5. web3.js와 Node.js 를 사용한 dApp 개발
세션5. web3.js와 Node.js 를 사용한 dApp 개발세션5. web3.js와 Node.js 를 사용한 dApp 개발
세션5. web3.js와 Node.js 를 사용한 dApp 개발
 
세션2. 이더리움 합의 알고리즘과 마이닝
세션2. 이더리움 합의 알고리즘과 마이닝세션2. 이더리움 합의 알고리즘과 마이닝
세션2. 이더리움 합의 알고리즘과 마이닝
 
세션1. block chain as a platform
세션1. block chain as a platform세션1. block chain as a platform
세션1. block chain as a platform
 

Semelhante a 간단 Ip 필터 구현 이야기

7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성HyeonSeok Choi
 
아두이노 2015-2 한동대학교 공학설계입문
아두이노 2015-2 한동대학교 공학설계입문아두이노 2015-2 한동대학교 공학설계입문
아두이노 2015-2 한동대학교 공학설계입문Sangjun Han
 
이산수학 C1 프로젝트 7
이산수학 C1 프로젝트 7이산수학 C1 프로젝트 7
이산수학 C1 프로젝트 7pkok15
 
어플 개발자의 서버개발 삽질기
어플 개발자의 서버개발 삽질기어플 개발자의 서버개발 삽질기
어플 개발자의 서버개발 삽질기scor7910
 
코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011Esun Kim
 
안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법mosaicnet
 
Deview 2019 눈발자국
Deview 2019 눈발자국Deview 2019 눈발자국
Deview 2019 눈발자국hanbeom Park
 
Visual term project2
Visual term project2Visual term project2
Visual term project2ssuser8de143
 
안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트병한 유
 
Test-SDIC2018-1(Answer)
Test-SDIC2018-1(Answer)Test-SDIC2018-1(Answer)
Test-SDIC2018-1(Answer)Yong Heui Cho
 
02장 자료형과 연산자
02장 자료형과 연산자02장 자료형과 연산자
02장 자료형과 연산자웅식 전
 
Blockchain 2nd ethereum_core
Blockchain 2nd ethereum_coreBlockchain 2nd ethereum_core
Blockchain 2nd ethereum_coreihpark92
 
Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택JinTaek Seo
 
2 1. variables & data types
2 1. variables & data types2 1. variables & data types
2 1. variables & data types웅식 전
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010Ryan Park
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10Ryan Park
 
불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 명신 김
 

Semelhante a 간단 Ip 필터 구현 이야기 (20)

7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성
 
아두이노 2015-2 한동대학교 공학설계입문
아두이노 2015-2 한동대학교 공학설계입문아두이노 2015-2 한동대학교 공학설계입문
아두이노 2015-2 한동대학교 공학설계입문
 
이산수학07
이산수학07이산수학07
이산수학07
 
이산수학 C1 프로젝트 7
이산수학 C1 프로젝트 7이산수학 C1 프로젝트 7
이산수학 C1 프로젝트 7
 
HI-ARC PS 101
HI-ARC PS 101HI-ARC PS 101
HI-ARC PS 101
 
어플 개발자의 서버개발 삽질기
어플 개발자의 서버개발 삽질기어플 개발자의 서버개발 삽질기
어플 개발자의 서버개발 삽질기
 
코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011
 
Meteor IoT
Meteor IoTMeteor IoT
Meteor IoT
 
안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법
 
Deview 2019 눈발자국
Deview 2019 눈발자국Deview 2019 눈발자국
Deview 2019 눈발자국
 
Visual term project2
Visual term project2Visual term project2
Visual term project2
 
안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트
 
Test-SDIC2018-1(Answer)
Test-SDIC2018-1(Answer)Test-SDIC2018-1(Answer)
Test-SDIC2018-1(Answer)
 
02장 자료형과 연산자
02장 자료형과 연산자02장 자료형과 연산자
02장 자료형과 연산자
 
Blockchain 2nd ethereum_core
Blockchain 2nd ethereum_coreBlockchain 2nd ethereum_core
Blockchain 2nd ethereum_core
 
Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택
 
2 1. variables & data types
2 1. variables & data types2 1. variables & data types
2 1. variables & data types
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
 
불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14
 

Mais de beom kyun choi

옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개beom kyun choi
 
DDD로 복잡함 다루기
DDD로 복잡함 다루기DDD로 복잡함 다루기
DDD로 복잡함 다루기beom kyun choi
 
Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀beom kyun choi
 
Ddd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksugDdd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksugbeom kyun choi
 
파이썬 언어 기초
파이썬 언어 기초파이썬 언어 기초
파이썬 언어 기초beom kyun choi
 
도메인구현 KSUG 20151128
도메인구현 KSUG 20151128도메인구현 KSUG 20151128
도메인구현 KSUG 20151128beom kyun choi
 
Event source 학습 내용 공유
Event source 학습 내용 공유Event source 학습 내용 공유
Event source 학습 내용 공유beom kyun choi
 
모델링 연습 리뷰
모델링 연습 리뷰모델링 연습 리뷰
모델링 연습 리뷰beom kyun choi
 
ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료beom kyun choi
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)beom kyun choi
 
리뷰의 기술 소개
리뷰의 기술 소개리뷰의 기술 소개
리뷰의 기술 소개beom kyun choi
 
스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해beom kyun choi
 
하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기beom kyun choi
 
차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)beom kyun choi
 
객체 지향 발담그기 JCO 컨퍼런스 14회
객체 지향 발담그기 JCO 컨퍼런스 14회객체 지향 발담그기 JCO 컨퍼런스 14회
객체 지향 발담그기 JCO 컨퍼런스 14회beom kyun choi
 
Hive 입문 발표 자료
Hive 입문 발표 자료Hive 입문 발표 자료
Hive 입문 발표 자료beom kyun choi
 

Mais de beom kyun choi (20)

옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
 
DDD로 복잡함 다루기
DDD로 복잡함 다루기DDD로 복잡함 다루기
DDD로 복잡함 다루기
 
DDD 준비 서문래
DDD 준비 서문래DDD 준비 서문래
DDD 준비 서문래
 
Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀
 
Ddd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksugDdd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksug
 
MVP 패턴 소개
MVP 패턴 소개MVP 패턴 소개
MVP 패턴 소개
 
파이썬 언어 기초
파이썬 언어 기초파이썬 언어 기초
파이썬 언어 기초
 
도메인구현 KSUG 20151128
도메인구현 KSUG 20151128도메인구현 KSUG 20151128
도메인구현 KSUG 20151128
 
Event source 학습 내용 공유
Event source 학습 내용 공유Event source 학습 내용 공유
Event source 학습 내용 공유
 
모델링 연습 리뷰
모델링 연습 리뷰모델링 연습 리뷰
모델링 연습 리뷰
 
ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)
 
리뷰의 기술 소개
리뷰의 기술 소개리뷰의 기술 소개
리뷰의 기술 소개
 
스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해
 
Zookeeper 소개
Zookeeper 소개Zookeeper 소개
Zookeeper 소개
 
하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기
 
차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)
 
객체 지향 발담그기 JCO 컨퍼런스 14회
객체 지향 발담그기 JCO 컨퍼런스 14회객체 지향 발담그기 JCO 컨퍼런스 14회
객체 지향 발담그기 JCO 컨퍼런스 14회
 
Storm 훑어보기
Storm 훑어보기Storm 훑어보기
Storm 훑어보기
 
Hive 입문 발표 자료
Hive 입문 발표 자료Hive 입문 발표 자료
Hive 입문 발표 자료
 

간단 Ip 필터 구현 이야기

  • 1. IP 필터 구현 이야기 최범균 (madvirus@madvirus.net, 트위터: @madvirus)
  • 2. 이야기의 시작.... ● 2011년 어느 날 ○ 장애 신고: "고객이 웹 게임에 연결이 안 된데요!" ○ 게임 관련된 서버들 확인 ■ 게임 웹 서버: 팡 팡 놀고 있음 ■ DB 서버: 팡 팡 놀고 있음 ■ 게임 배치 서버: 팡 팡 놀고 있음 ○ 증상 ■ 내부 네트워크에서 잘 연결 됨 ■ 외부 네트워크에서 연결 매우 느림 ○ 장애 지점 ■ 더 뒤져보니.......
  • 3. 이야기의 시작.... ● 웹 방화벽이 장애 지점이었음!! ○ 컥, 웹 방화벽의 CPU 사용률이 100% 가까이 치솟음 ● 원인 ○ 웹 게임으로 인해 웹 트래픽 급증 ○ 웹 방화벽의 차단 IP 목록의 개수가 무지 많음 ○ 각 클라이언트 IP가 차단 IP인지 검사하느라 CPU가 무지 바쁘게 일하게 됨 ○ 방화벽이 버벅되면서 외부에서 접근하는 모든 웹 연 결에 문제가 발생함 ● 일단 문제 해소부터 ○ 웹 게임에 대해 차단 규칙 제외해서 일단 급한 불은 껐으나,,, 보안에 찜찜함이 남음...
  • 4. 그래, 만들어볼까? ● 의심나는 것 ○ 클라이언트 IP가 차단 IP인지 검사할 때, 전체 차단 IP 목록/패턴을 검색하는 건 아닐까? ○ 마치 DB에서 풀(full) 스캔하는 것과 같은 상황? ● 문뜩 떠오른 생각 ○ DB에 인덱스를 만들 듯, IP 목록을 인덱스 방식으로 관리하면 차단 IP인지 여부를 빠르게 확인할 수 있을 텐데..... ● 만들어볼까? ○ 생각만 하고, 2년이 지난뒤에 만들게 된 ip-filter !
  • 5. 내용 ● 트리 기반 차단/허용 IP 목록 관리/검색 구현 ○ 검색 성능 비교 ● SLF4J 방식 컴포넌트 구현 ● 문자열 기반 설정 ○ Scala Combinator Parser
  • 6. 1. 트리를 이용한 IP 패턴 목록 구성
  • 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. 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. 적용할 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. IP 패턴의 트리 표현위한 두 클래스 루트 1 10 2 12 3 4 128/25 13 14 20 * 30 40 51 52 NumberNode IpTree
  • 11. NumberNode의 역할 ● 트리 구조 상의 한 노드를 표현 ● 노드 데이터 보관 ○ 노드의 값을 가짐 ■ 정확한 값: 예 - 1, 10, 128 ■ 패턴: 전체 또는 네트워크 ● * ● 64/26 ● 특정 숫자가 노드 데이터와 매칭되는 여부 ● 노드 구성 관련 ○ 자식 노드 생성 기능 ○ 특정 숫자에 매칭 되는 자식 노드 찾아주는 기능
  • 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. 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. 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. 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. 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. 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. 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. 다음 차례는 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. 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. 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. 2. IpTree를 이용한 IP 필터 모듈
  • 23. IpFilter ● 주요 기능 ○ IP가 차단 IP인지 확인 ○ IP가 허용 IP인지 확인 ○ 차단/허용 중 어떤 규칙을 먼저 적용할지 ○ 두 규칙에 일치하지 않을 경우 허용할지 여부 지정 ● 구성 ○ Config: 설정 정보 ○ IpFilter: 인터페이스 ○ ConfigIpFilter: IpFilter의 구현
  • 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. 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. 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. 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. IpFilter의 성능 3 ● 멀티 쓰레드 상황 ○ 쓰레드 10개, 20개, 50개 실행 ○ 각 쓰레드 마다 '위 테스트 방법'으로 실행 ○ 단, 각 쓰레드는 5개 패턴의 전체 IP 개수가 아닌 최대 10만개만 검사 ■ 각 쓰레드가 최대한 겹처서 실행되도록 하기 위함
  • 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. 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. 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. 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. 3. SLF4J 방식 컴포넌트 구현
  • 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. SLF4J 컴포넌트 구성 2 - api 소스 빌드 과정에서 api에 포함된 impl 패키지는 jar 파일에 포함되지 않음 컴파일 위한 코드
  • 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. 서블릿용 모듈: 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 삭제
  • 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. 문맥 자유 문법과 파서 -- 문법은 대충 이런식 (컴파일러 시간에 배웠던 기억은 있으나, 내용 자체는 거의 기억 안 남) 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. Scala Combinator Parser ● Scala가 기본으로 제공하는 파서 ● 기본적인 문맥 자유 문법 지원 ○ 코드에서 문법과 결과를 바로 표현 ● 자바 코드에서 손쉽게 호출 가능
  • 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. 쉬운 사용 위한 보조 클래스 // 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) } }
  • 45. 내용 정리 ● 트리 기반의 IP 패턴 목록 관리 ○ 패턴 개수에 상관없이 일정한 탐색 속도 제공 ○ 다중 쓰레드 접근 시에도 성능 저하 상대적 낮음 ● SLF4J 방식 컴포넌트 구성 ○ 동적 클래스 로딩이 아닌 jar 교체 방식 ● Scala를 이용한 문법/파서 구현 ○ 외부 DSL 구현을 쉽게 할 수 있도록 도와줌 ○ 자바와의 연계가 쉬움
  • 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. 광고 내가 만든 코드를 함께 리뷰할 선배 프로그래머가 없나요? 주변 프로그래머들이 너무 바빠서 코드 리뷰할 시간이 없나요? 이런 상황이라면, 고민하지 마시고 연락주세요. 함께 코드를 보고 논의하고 수정하는 시간을 가져보아요~ 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. Q&A, 논의 (이메일 madvirus@madvirus.net, 트위터 @madvirus로 언제든 연락주세요)