Your SlideShare is downloading. ×

간단 Ip 필터 구현 이야기

11,305

Published on

ip-filter 구현 이야기 …

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

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

No Downloads
Views
Total Views
11,305
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
14
Comments
0
Likes
4
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 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 삭제
  • 38. 4. Scala Combinator Parser
  • 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) } }
  • 44. 정리
  • 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로 언제든 연락주세요)

×