aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--COPYING18
-rw-r--r--Makefile53
-rw-r--r--README.skel.md73
-rw-r--r--bangbang1
-rw-r--r--common.sh28
-rwxr-xr-xget-kisa-as-list46
-rw-r--r--image.pngbin0 -> 103789 bytes
-rw-r--r--image.webpbin0 -> 19436 bytes
-rw-r--r--image2.webpbin0 -> 55616 bytes
-rwxr-xr-xproduce-readme19
-rwxr-xr-xproduce-table74
-rwxr-xr-xquery-prefixes42
-rwxr-xr-xquery-rpki-prefixes28
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
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..bb56477
--- /dev/null
+++ b/COPYING
@@ -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>
+
+
+![RPKI by Country: Korea, Japan, United States, Australia](image.webp)
+
+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
new file mode 100644
index 0000000..3a4f9ec
--- /dev/null
+++ b/image.png
Binary files differ
diff --git a/image.webp b/image.webp
new file mode 100644
index 0000000..f272789
--- /dev/null
+++ b/image.webp
Binary files differ
diff --git a/image2.webp b/image2.webp
new file mode 100644
index 0000000..c3a0de5
--- /dev/null
+++ b/image2.webp
Binary files differ
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