diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | COPYING | 18 | ||||
-rw-r--r-- | Makefile | 53 | ||||
-rw-r--r-- | README.skel.md | 73 | ||||
-rw-r--r-- | bangbang | 1 | ||||
-rw-r--r-- | common.sh | 28 | ||||
-rwxr-xr-x | get-kisa-as-list | 46 | ||||
-rw-r--r-- | image.png | bin | 0 -> 103789 bytes | |||
-rw-r--r-- | image.webp | bin | 0 -> 19436 bytes | |||
-rw-r--r-- | image2.webp | bin | 0 -> 55616 bytes | |||
-rwxr-xr-x | produce-readme | 19 | ||||
-rwxr-xr-x | produce-table | 74 | ||||
-rwxr-xr-x | query-prefixes | 42 | ||||
-rwxr-xr-x | query-rpki-prefixes | 28 |
14 files changed, 383 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1944fd6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.tmp @@ -0,0 +1,18 @@ +Copyright 2024 David Timber + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..77dabe5 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +all: README.md result.csv result6.csv + +README.md: table.md table6.md + ./produce-readme table.md table6.md < README.skel.md > README.md.tmp + mv README.md.tmp README.md + +.PHONY: clean + +clean: + rm -f \ + *.tmp \ + aslist \ + routelist \ + route6list \ + rpki-enabled \ + rpki6-enabled \ + table.md \ + table6.md \ + result.csv \ + result6.csv \ + README.md + +aslist: + ./get-kisa-as-list > aslist.tmp + mv aslist.tmp aslist + +routelist: aslist + ./query-prefixes < aslist > routelist.tmp + mv routelist.tmp routelist +route6list: aslist + ./query-prefixes -6 < aslist > route6list.tmp + mv route6list.tmp route6list + +rpki-enabled: routelist + ./query-rpki-prefixes < routelist > rpki-enabled.tmp + mv rpki-enabled.tmp rpki-enabled +rpki6-enabled: route6list + ./query-rpki-prefixes < route6list > rpki6-enabled.tmp + mv rpki6-enabled.tmp rpki6-enabled + +table.md: aslist routelist rpki-enabled + ./produce-table aslist routelist rpki-enabled > table.md.tmp + mv table.md.tmp table.md +table6.md: aslist route6list rpki6-enabled + ./produce-table aslist route6list rpki6-enabled > table6.md.tmp + mv table6.md.tmp table6.md + +result.csv: aslist routelist rpki-enabled + ./produce-table -c aslist routelist rpki-enabled > result.csv.tmp + mv result.csv.tmp result.csv +result6.csv: aslist route6list rpki6-enabled + ./produce-table -c aslist route6list rpki6-enabled > result6.csv.tmp + mv result6.csv.tmp result6.csv diff --git a/README.skel.md b/README.skel.md new file mode 100644 index 0000000..27d3881 --- /dev/null +++ b/README.skel.md @@ -0,0 +1,73 @@ +# 한국 인터넷 BGP 네트워크 RPKI 적용률 트래커 +- 나무위키 [2021년 10월 KT 인터넷 장애 사건](https://namu.wiki/w/2021%EB%85%84%2010%EC%9B%94%20KT%20%EC%9D%B8%ED%84%B0%EB%84%B7%20%EC%9E%A5%EC%95%A0%20%EC%82%AC%EA%B1%B4?from=2021%EB%85%84%2010%EC%9B%94%2025%EC%9D%BC%20KT%20%EC%9D%B8%ED%84%B0%EB%84%B7%20%EC%9E%A5%EC%95%A0%20%EC%82%AC%EA%B1%B4#s-4.5) +- MTN 뉴스 [세계 표준되는 RPKI…우리나란 예산 '0원'](https://news.mtn.co.kr/news-detail/2024090813332917792) +- KISA [라우팅 인증(RPKI)](https://한국인터넷정보센터.한국/jsp/resources/rpki.jsp) + +<a href="https://youtu.be/WGY_d4XFcjo"> +MBC 보도: KT 인터넷 서비스 장애 "상황 파악중" + +<img + src="image2.webp" + alt="MBC 보도: KT 인터넷 서비스 장애 '상황 파악중'"> +</a> + + + + +https://stat.ripe.net/ui2013/widget/rpki-by-country#w.resource=kr%2Cus%2Cjp%2Cau + +RPKI가 BGP 설정 오류로 인한 장애 예방에 좋으면 빨리 적용해야겠죠? 각 사업자의 +RPKI 적용률을 여기서 확인하세요. + +UTC+9 기준 매주 월요일 자정에 업데이트 자동 업데이트 중(Github Action). + +## 스크립트 실행 절차 +[Makefile 레시피](Makefile) 설명. + +1. KISA [AS번호 사용자 + 현황](https://krnic.kisa.or.kr/jsp/business/management/asList.jsp) 페이지 + 로드, 테이블 파싱 +1. 파싱한 AS번호들 이용하여 [whois.radb.net](radb) 쿼리하여 라우팅 프리픽스 + 정보 받아오기 +1. 받아온 라우팅 프리픽스를 radb에 쿼리하여 `rpki-ov-state` 값이 `valid`인 + 프리픽스만 추려냄 +1. 데이터 취합 후 markdown테이블과 csv 파일 생성 +1. READMD.md 생성, 테이블 삽입 + +## 프리픽스 현황 +https://krnic.kisa.or.kr/jsp/business/management/asList.jsp + +- ASN: 자율시스템 번호 +- Name: 자율시스템 운영사 +- RPKI: 유효한 ROA가 적용된 라우팅 프리픽스 개수 +- Total: 총 라우팅 프리픽스 개수 +- %: RPKI/Total 백분율 +- Bars: 20점 만점 점수 + +**업데이트: %LAST_UPDATE_ISOTIME%** + +### IPv4 +%THE_TABLE_V4% + +### IPv6 +%THE_TABLE_V6% + +## Usage +### INSTALL +```sh +dnf install make python3 +``` + +### Generate +실행 시간 5분 내외. 메모리 사용량(RSS): 약 50MB + +```sh +git clone https://github.com/dxdxdt/rpki-tracker-kr +make clean +make +``` + +<!-- +## Director's Commentary +> [They're bashing they're bashing hard right now.](https://www.youtube.com/clip/Ugkxcic7nqMOsjDFaM1mGLm0f2_Kd82YeJbe) +--> diff --git a/bangbang b/bangbang new file mode 100644 index 0000000..79d8956 --- /dev/null +++ b/bangbang @@ -0,0 +1 @@ +!! diff --git a/common.sh b/common.sh new file mode 100644 index 0000000..d07a69f --- /dev/null +++ b/common.sh @@ -0,0 +1,28 @@ +open_db () { + cat bangbang - | nc whois.radb.net 43 +} + +extract_answer () { + set -e + local c + local l + local eot + + while read l + do + c="${l:0:1}" + + if [ "$c" == "A" ]; then + c="${l:1}" + read -N "$c" l + # consume "C" + read eot && [ "$eot" == "C" ] || echo "Protocol error" >&2 || exit 1 + printf '%s\0' "$l" + elif [ "$c" == "%" ]; then + read + printf '\0' + elif [ "$c" == "D" ]; then + printf '\0' + fi + done +} diff --git a/get-kisa-as-list b/get-kisa-as-list new file mode 100755 index 0000000..a57a95e --- /dev/null +++ b/get-kisa-as-list @@ -0,0 +1,46 @@ +#!/bin/env python3 +import html +import html.parser +import requests + +TARGET_URL = 'https://krnic.kisa.or.kr/jsp/business/management/asList.jsp' + +def sanitize_as_name (s: str): + return ' '.join(s.split()) + +class KISAASListExtractor (html.parser.HTMLParser): + _table_started = False + _td = list[str]() + _tag_stack = list[str]() + + def handle_starttag (self, tag, attrs): + self._tag_stack.append(tag) # FIXME: don't push void elements + + if self._table_started: + if tag == 'tr': + self._td.clear() + else: + if tag == 'table': + attr_map = dict[str, str](attrs) + + if attr_map.get('class') == 'datatable': + self._table_started = True + + def handle_endtag (self, tag): + if self._table_started and tag == 'table': + self._table_started = False + if tag == 'tr' and self._td: + print("%-12s\t%s" % (self._td[1], sanitize_as_name(self._td[0]))) + + self._tag_stack.pop() + + def handle_data (self, data): + if self._table_started and self._tag_stack[-1] == 'td': + self._td.append(data) + + +doc_parser = KISAASListExtractor() + +with requests.get(TARGET_URL) as req: + raw = req.content.decode(req.encoding or 'utf-8') + doc_parser.feed(raw) diff --git a/image.png b/image.png Binary files differnew file mode 100644 index 0000000..3a4f9ec --- /dev/null +++ b/image.png diff --git a/image.webp b/image.webp Binary files differnew file mode 100644 index 0000000..f272789 --- /dev/null +++ b/image.webp diff --git a/image2.webp b/image2.webp Binary files differnew file mode 100644 index 0000000..c3a0de5 --- /dev/null +++ b/image2.webp diff --git a/produce-readme b/produce-readme new file mode 100755 index 0000000..fa1bfd3 --- /dev/null +++ b/produce-readme @@ -0,0 +1,19 @@ +#!/bin/env python3 +import datetime +import getopt +import sys + +opts, args = getopt.getopt(sys.argv[1:], '') + +now = datetime.datetime.now(datetime.UTC) +with open(args[0]) as f: + table = f.read() +with open(args[1]) as f: + table6 = f.read() +doc = sys.stdin.read() + +doc = doc.replace('%LAST_UPDATE_ISOTIME%', now.isoformat()) +doc = doc.replace('%THE_TABLE_V4%', table) +doc = doc.replace('%THE_TABLE_V6%', table6) + +sys.stdout.write(doc) diff --git a/produce-table b/produce-table new file mode 100755 index 0000000..8057ad9 --- /dev/null +++ b/produce-table @@ -0,0 +1,74 @@ +#!/bin/env python3 +import csv +import getopt +import os +import sys + +NB_BARS = 20 +output_format = "md" + +def key_f (element: tuple[str, str, int, int, float, str]): + return element[3] + +opts, args = getopt.getopt(sys.argv[1:], 'c') +for optname, optval in opts: + if optname == '-c': + output_format = "csv" + +out = list[tuple[str, str, int, int, float, str]]() +asn_ln_map = dict[str, str]() +route_map = dict[str, set[str]]() +rpki_enabled_set = set[str]() + +with open(args[0]) as f: + for line in f: + r = line.split('\t') + asn_ln_map[r[0].strip()] = r[1].strip() + +with open(args[1]) as f: + for line in f: + r = line.split('\t') + asn = r[0].strip() + s = route_map.get(asn) + route_map[asn] = set[str](r[1].split()) + +with open(args[2]) as f: + for line in f: + rpki_enabled_set.add(line.strip()) + +for asn, v in route_map.items(): + nb_total = len(v) + if nb_total == 0: + continue + + nb_rpki_enabled = 0 + for prefix in v: + if prefix in rpki_enabled_set: + nb_rpki_enabled += 1 + + localname = asn_ln_map.get(asn, '') + ratio = nb_rpki_enabled / nb_total + nb_i = int(NB_BARS * ratio) + nb_e = NB_BARS - nb_i + bars = '█' * nb_i + '▒' * nb_e + + out.append(( asn, localname, nb_rpki_enabled, nb_total, ratio, bars )) + +out.sort(key = key_f, reverse = True) + +if output_format == 'md': + print( +'''| ASN | Name | RPKI | Total | % | Bars | +|-|-|-:|-:|-:|-|''' + ) + for r in out: + print( + "| %s | %s | %d | %d | %.2f%% | %s |" % + ( r[0], r[1], r[2], r[3], r[4] * 100, r[5] )) +elif output_format == 'csv': + w = csv.writer(sys.stdout) + w.writerow([ 'ASN', 'Name', 'RPKI', 'Total', '%' ]) + for row in out: + w.writerow(row[:5]) +else: + os.abort() diff --git a/query-prefixes b/query-prefixes new file mode 100755 index 0000000..dc1bff6 --- /dev/null +++ b/query-prefixes @@ -0,0 +1,42 @@ +#!/bin/bash +. common.sh + +PIPE_NAME="$$.fifo.tmp" + +if [ "$1" == "-6" ]; then + Q_PREFIX='!6' +else + Q_PREFIX='!g' +fi + +read_aslist () { + local asn + + while read asn + do + echo ${asn} >&3 + echo ${Q_PREFIX}${asn} + done +} + +output_filter () { + local a + local b + + while read -u 3 a && read -d '' b + do + printf '%-12s\t%s\n' "$a" "$b" + done +} + + +mkfifo "$PIPE_NAME" || exit 1 +grep -Eoi 'AS[0-9]+' | + read_aslist 3> "$PIPE_NAME" | + open_db | + extract_answer | + output_filter \ + 3< "$PIPE_NAME" & +rm "$PIPE_NAME" + +wait diff --git a/query-rpki-prefixes b/query-rpki-prefixes new file mode 100755 index 0000000..34c365a --- /dev/null +++ b/query-rpki-prefixes @@ -0,0 +1,28 @@ +#!/bin/bash +. common.sh + +read_prefixes () { + grep -Eoi '[a-f0-9.:]+/[0-9]{1,3}' | while read l + do + echo '!r'"$l" + done +} + +process_result () { + local r + + while read -d '' r + do + if echo "$r" | grep -Eiqs '^rpki-ov-state:(\s+)?valid'; then + echo "$r" | + grep -Eoi 'route6?:(\s+)?[a-f0-9.:]+/[0-9]{1,3}' | + sed -E 's/^route6?:(\s+)?//' | + sort | + uniq | + head -n1 + fi + done +} + + +read_prefixes | open_db | extract_answer | process_result |