├── .cargo-ok ├── src ├── js │ ├── 404.js │ ├── vendor │ │ └── sortable.min.js │ └── index.js ├── 404.html ├── css │ ├── colors.css │ └── index.css └── index.html ├── .gitignore ├── workers-site ├── .gitignore ├── webpack.config.js ├── package.json ├── index.js └── package-lock.json ├── design ├── icons.sketch ├── server-icon.svg ├── laptop-icon.svg └── cloud-icon.svg ├── resources ├── favicon.ico ├── open-graph.mp4 └── open-graph.png ├── wrangler.toml ├── test └── operators.test.js ├── .github └── workflows │ ├── ci.yml │ └── main.yml ├── LICENSE ├── README.md ├── webpack.config.js ├── package.json ├── CONTRIBUTING.md └── data ├── twitter.csv └── operators.csv /.cargo-ok: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/404.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | public/ 3 | node_modules/ 4 | .idea/ 5 | -------------------------------------------------------------------------------- /workers-site/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | worker 4 | -------------------------------------------------------------------------------- /design/icons.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adaptive/isbgpsafeyet.com/master/design/icons.sketch -------------------------------------------------------------------------------- /resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adaptive/isbgpsafeyet.com/master/resources/favicon.ico -------------------------------------------------------------------------------- /resources/open-graph.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adaptive/isbgpsafeyet.com/master/resources/open-graph.mp4 -------------------------------------------------------------------------------- /resources/open-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adaptive/isbgpsafeyet.com/master/resources/open-graph.png -------------------------------------------------------------------------------- /workers-site/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | target: "webworker", 3 | entry: "./index.js", 4 | context: __dirname, 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.csv$/i, 9 | use: 'raw-loader', 10 | }, 11 | ], 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /design/server-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "isbgpsafeyet" 2 | type = "webpack" 3 | account_id = "04cb62d57d02b212c133288d8e42f84c" 4 | workers_dev = false 5 | route = "isbgpsafeyet.com/*" 6 | zone_id = "72a2ccd3f2b1c26c23d134d8d1a70a5d" 7 | webpack_config = "workers-site/webpack.config.js" 8 | 9 | [site] 10 | bucket = "./public" 11 | -------------------------------------------------------------------------------- /workers-site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "1.0.0", 4 | "description": "A template for kick starting a Cloudflare Workers project", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@cloudflare/kv-asset-handler": "^0.0.9", 9 | "csv-parse": "^4.8.8", 10 | "raw-loader": "^4.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /design/laptop-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/operators.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const parse = require('csv-parse/lib/sync'); 3 | const path = require('path'); 4 | 5 | describe('Operators', function() { 6 | test('Has duplicate ASN returns false', () => { 7 | const OPERATORS_STRING = fs.readFileSync(path.join(__dirname, '..', 'data', 'operators.csv'), {encoding: 'utf-8'}); 8 | const OPERATORS = parse(OPERATORS_STRING, {columns: true}); 9 | 10 | let seen = new Set(); 11 | 12 | const HAS_DUPLICATES = OPERATORS.some(function(currentObject) { 13 | return seen.size === seen.add(currentObject.asn).size; 14 | }); 15 | 16 | expect(HAS_DUPLICATES).toBe(false); 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /design/cloud-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Trigger workflow on push and pull requests. 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Setup Node 10.x 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: '10.x' 15 | - uses: actions/cache@v2 16 | with: 17 | path: ~/.npm 18 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 19 | restore-keys: | 20 | ${{ runner.os }}-node- 21 | - name: Install dependencies with a clean slate 22 | run: npm ci 23 | - name: Run tests 24 | run: npm run test 25 | -------------------------------------------------------------------------------- /src/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Page not found · IsBGPSafeYet.com 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Not found

18 | 19 |

Unfortunately, the page you requested cannot be found.

20 | 21 |
22 | 23 |

24 | Go home 25 |

26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: '10.x' 16 | - uses: actions/cache@v2 17 | with: 18 | path: ~/.npm 19 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 20 | restore-keys: | 21 | ${{ runner.os }}-node- 22 | - name: Install deps 23 | run: npm install 24 | - name: Build site 25 | run: npm run build 26 | - name: Build Workers script 27 | run: npx wrangler build 28 | - name: Wrangler auth check 29 | run: npx wrangler whoami 30 | env: 31 | CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} 32 | CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} 33 | - name: Upload assets 34 | run: npx wrangler publish 35 | env: 36 | CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} 37 | CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cloudflare Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isbgpsafeyet.com 2 | 3 | **Is BGP safe yet?** is an initiative by Cloudflare to make BGP more secure by deploying RPKI. 4 | 5 | A list of major operators tracks the status of their RPKI deployments into three categories: 6 | safe, partially safe and unsafe. Contributions are welcome. 7 | 8 | [Contribute to the list →](https://github.com/cloudflare/isbgpsafeyet.com/tree/master/data) 9 | 10 | The website also provides a simple test to determine if your operator is filtering RPKI invalid prefixes. 11 | 12 | [Visit isbgpsafeyet.com →](https://isbgpsafeyet.com/) 13 | 14 | ## Running 15 | 16 | IsBGPSafeYet is built with Webpack and deployed with Workers Sites. 17 | 18 | To run locally: 19 | 20 | - `npm install` 21 | - `npm run start` 22 | 23 | If you have never used Wrangler before you will need to [Configure Wrangler](https://developers.cloudflare.com/workers/quickstart#configure). 24 | 25 | You will also have to update the wrangler.toml file. 26 | 27 | If you have a zone on Cloudflare to test with: 28 | 29 | - Update the `account_id`, `zone_id`, and `route` values 30 | 31 | If you do not have a zone on Cloudflare to test with: 32 | 33 | - Update the `account_id`, remove the `zone_id` and `route` fields, and add `workers_dev = true` 34 | 35 | To produce a build: 36 | 37 | - `npm run build` 38 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const CopyPlugin = require('copy-webpack-plugin'); 7 | const TerserPlugin = require('terser-webpack-plugin'); 8 | 9 | module.exports = { 10 | entry: [ 11 | './src/js/index.js', 12 | './src/css/index.css', 13 | ], 14 | plugins: [ 15 | new MiniCssExtractPlugin({ 16 | filename: '[name].[contenthash].css', 17 | path: path.resolve(__dirname, 'public'), 18 | }), 19 | new CleanWebpackPlugin(), 20 | new HtmlWebpackPlugin({ 21 | template: 'src/index.html', 22 | }), 23 | new HtmlWebpackPlugin({ 24 | template: 'src/404.html', 25 | filename: '404.html', 26 | }), 27 | new CopyPlugin([ 28 | 'resources/*', 29 | ]), 30 | ], 31 | output: { 32 | filename: '[name].[contenthash].js', 33 | path: path.resolve(__dirname, 'public'), 34 | }, 35 | module: { 36 | rules: [ 37 | { 38 | test: /\.css$/i, 39 | use: [MiniCssExtractPlugin.loader, 'css-loader'], 40 | }, 41 | ], 42 | }, 43 | optimization: { 44 | minimizer: [ 45 | new TerserPlugin(), 46 | ], 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isbgpsafeyet.com", 3 | "version": "1.0.0", 4 | "description": "**Is BGP safe yet?** is an initiative by Cloudflare to make BGP more secure by deploying RPKI.", 5 | "main": "public/index.js", 6 | "scripts": { 7 | "build": "webpack-cli", 8 | "webpack-dev": "webpack-cli -w ", 9 | "wrangler-dev": "wrangler dev", 10 | "start": "concurrently --kill-others-on-fail --names 'webpack ,wrangler' --prefix name --prefix-colors 'bgCyan.black,bgYellow.black' \"npm run webpack-dev\" \"npm run wrangler-dev\" \"while ! nc -z localhost 8787; do\nsleep 1\ndone; open 'http://localhost:8787'\"", 11 | "test": "jest" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/cloudflare/isbgpsafeyet.com.git" 16 | }, 17 | "author": "Adam Schwartz", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/cloudflare/isbgpsafeyet.com/issues" 21 | }, 22 | "homepage": "https://github.com/cloudflare/isbgpsafeyet.com#readme", 23 | "devDependencies": { 24 | "@cloudflare/wrangler": "^1.10.2", 25 | "clean-webpack-plugin": "^3.0.0", 26 | "concurrently": "^5.1.0", 27 | "copy-webpack-plugin": "^5.1.1", 28 | "css-loader": "^3.5.2", 29 | "csv-parse": "^4.8.9", 30 | "focus-visible-polyfill": "^0.1.0", 31 | "html-webpack-plugin": "^4.2.0", 32 | "jest": "^25.4.0", 33 | "lodash.pickby": "^4.6.0", 34 | "mini-css-extract-plugin": "^0.9.0", 35 | "optimize-css-assets-webpack-plugin": "^5.0.3", 36 | "terser-webpack-plugin": "^2.3.5", 37 | "webpack": "^4.43.0", 38 | "webpack-cli": "^3.3.11" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to isbgpsafeyet.com 2 | 3 | Thank you for helping to keep the list up to date! We welcome contributions as well. 4 | 5 | This project is governed by Cloudflare's [code of conduct](https://github.com/cloudflare/.github/blob/master/CODE_OF_CONDUCT.md) and 6 | [guidelines](https://github.com/cloudflare/.github/blob/master/CONTRIBUTING.md). 7 | 8 | ## I have a question on RPKI or the website 9 | 10 | First, have a look at the FAQ on the website. 11 | Please raise an [issue](https://github.com/cloudflare/isbgpsafeyet.com/issues/new/choose) 12 | if you haven't found the answer. 13 | 14 | ## Adding networks and Twitter accounts 15 | 16 | * [operators.csv](https://github.com/cloudflare/isbgpsafeyet.com/blob/master/data/operators.csv) [[edit it online](https://github.com/cloudflare/isbgpsafeyet.com/edit/master/data/operators.csv)] 17 | * [twitter.csv](https://github.com/cloudflare/isbgpsafeyet.com/blob/master/data/twitter.csv) [[edit it online](https://github.com/cloudflare/isbgpsafeyet.com/edit/master/data/twitter.csv)] 18 | 19 | Follow the steps below: 20 | * when adding a new network: 21 | * the columns are: 22 | * name: most common name of a network (eg: AT&T) 23 | * type: ISP, cloud or transit 24 | * details: indicates the progression of an RPKI deployment (eg: signed + filtering/filtering peers only) 25 | * status: the RPKI state: 26 | _unsafe_ (nothing deployed, only signed or not filtering), 27 | _partially safe_ (not filtering all the peers), 28 | _safe_ (fully signed and fully filtering) 29 | * asn: autonomous system number (eg: 7018 for AT&T) 30 | * rank: you can look them up using the ASN: https://asrank.caida.org/asns/3356 31 | * sort the row by the rank 32 | * when adding a Twitter account, sort them by ASN 33 | 34 | Create a pull-request. 35 | We will review it and merge it if it's valid. 36 | Try to include proof if you're indicating a filtering ISP (mailing-list message, website, tweet, RIPE Atlas test). 37 | 38 | If you wish to publish a new major deployment as part of the *latest updates*: edit the following: 39 | [index.html](https://github.com/cloudflare/isbgpsafeyet.com/blob/master/src/index.html) [[edit it online](https://github.com/cloudflare/isbgpsafeyet.com/edit/master/src/index.html)] 40 | Make sure you provide enough information to validate the statement. 41 | -------------------------------------------------------------------------------- /src/js/vendor/sortable.min.js: -------------------------------------------------------------------------------- 1 | /* Downloaded 2020-04-07 from https://github.com/HubSpot/sortable/archive/v0.8.0.zip and slightly modified */ 2 | (function(){var a,b,c,d,e,f,g;a="table[data-sortable]",d=/^-?[£$¤]?[\d,.]+%?$/,g=/^\s+|\s+$/g,c=["click"],f="ontouchstart"in document.documentElement,f,b=function(a,b,c){return null!=a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},e={init:function(b){var c,d,f,g,h;for(null==b&&(b={}),null==b.selector&&(b.selector=a),d=document.querySelectorAll(b.selector),h=[],f=0,g=d.length;g>f;f++)c=d[f],h.push(e.initTable(c));return h},initTable:function(a){var b,c,d,f,g,h;if(1===(null!=(h=a.tHead)?h.rows.length:void 0)&&"true"!==a.getAttribute("data-sortable-initialized")){for(a.setAttribute("data-sortable-initialized","true"),d=a.querySelectorAll("th"),b=f=0,g=d.length;g>f;b=++f)c=d[b],"false"!==c.getAttribute("data-sortable")&&e.setupClickableTH(a,c,b);return a}},setupClickableTH:function(a,d,f){var g,h,i,j,k,l;for(i=e.getColumnType(a,f),h=function(b){var c,g,h,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D;if(b.handled===!0)return!1;for(b.handled=!0,m="true"===this.getAttribute("data-sorted"),n=this.getAttribute("data-sorted-direction"),h=m?"ascending"===n?"descending":"ascending":i.defaultSortDirection,p=this.parentNode.querySelectorAll("th"),s=0,w=p.length;w>s;s++)d=p[s],d.setAttribute("data-sorted","false"),d.removeAttribute("data-sorted-direction");if(this.setAttribute("data-sorted","true"),this.setAttribute("data-sorted-direction",h),o=a.tBodies[0],l=[],m){for(D=o.rows,v=0,z=D.length;z>v;v++)g=D[v],l.push(g);for(l.reverse(),B=0,A=l.length;A>B;B++)k=l[B],o.appendChild(k)}else{for(r=null!=i.compare?i.compare:function(a,b){return b-a},c=function(a,b){return a[0]===b[0]?a[2]-b[2]:i.reverse?r(b[0],a[0]):r(a[0],b[0])},C=o.rows,j=t=0,x=C.length;x>t;j=++t)k=C[j],q=e.getNodeValue(k.cells[f]),null!=i.comparator&&(q=i.comparator(q)),l.push([q,k,j]);for(l.sort(c),u=0,y=l.length;y>u;u++)k=l[u],o.appendChild(k[1])}return"function"==typeof window.CustomEvent&&"function"==typeof a.dispatchEvent?a.dispatchEvent(new CustomEvent("Sortable.sorted",{bubbles:!0})):void 0},l=[],j=0,k=c.length;k>j;j++)g=c[j],l.push(b(d,g,h));return l},getColumnType:function(a,b){var c,d,f,g,h,i,j,k,l,m,n;if(d=null!=(l=a.querySelectorAll("th")[b])?l.getAttribute("data-sortable-type"):void 0,null!=d)return e.typesObject[d];for(m=a.tBodies[0].rows,h=0,j=m.length;j>h;h++)for(c=m[h],f=e.getNodeValue(c.cells[b]),n=e.types,i=0,k=n.length;k>i;i++)if(g=n[i],g.match(f))return g;return e.typesObject.alpha},getNodeValue:function(a){var b;return a?(b=a.getAttribute("data-value"),null!==b?b:"undefined"!=typeof a.innerText?a.innerText.replace(g,""):a.textContent.replace(g,"")):""},setupTypes:function(a){var b,c,d,f;for(e.types=a,e.typesObject={},f=[],c=0,d=a.length;d>c;c++)b=a[c],f.push(e.typesObject[b.name]=b);return f}},e.setupTypes([{name:"numeric",defaultSortDirection:"descending",match:function(a){return a.match(d)},comparator:function(a){return parseFloat(a.replace(/[^0-9.-]/g,""),10)||0}},{name:"date",defaultSortDirection:"ascending",reverse:!0,match:function(a){return!isNaN(Date.parse(a))},comparator:function(a){return Date.parse(a)||0}},{name:"alpha",defaultSortDirection:"ascending",match:function(){return!0},compare:function(a,b){return a.localeCompare(b)}}]),setTimeout(e.init,0),"function"==typeof define&&define.amd?define(function(){return e}):"undefined"!=typeof exports?module.exports=e:window.Sortable=e}).call(this); 3 | -------------------------------------------------------------------------------- /data/twitter.csv: -------------------------------------------------------------------------------- 1 | asn,handle 2 | 1,CenturyLink 3 | 2,UDelaware 4 | 3,mit 5 | 4,usc 6 | 6,atos 7 | 9,carnegiemellon 8 | 14,columbia 9 | 17,lifeatpurdue 10 | 18,utaustin 11 | 19,leidosinc 12 | 20,UofR 13 | 174,CogentCo 14 | 513,CERN 15 | 559,SWITCH_ch 16 | 577,Bell 17 | 701,VERIZON 18 | 786,Jisc 19 | 812,Rogers 20 | 852,TELUS 21 | 1136,KPN 22 | 1221,Telstra 23 | 1267,WIND_Telecomunicazioni 24 | 1399,megacable 25 | 1741,CSCfi 26 | 1759,TeliaFinland 27 | 1764,next_layer 28 | 1853,ziduniwien 29 | 2018,Telenet 30 | 2047,roche 31 | 2611,belnet_be 32 | 2612,Sitasys 33 | 2852,CESNET_cz 34 | 2856,bt_uk 35 | 3209,vodafone_de 36 | 3215,Orange_France 37 | 3238,Alcom_ax 38 | 3243,MEOpt 39 | 3265,xs4all 40 | 3269,tim_official 41 | 3303,Swisscom 42 | 3304,Scarlet 43 | 3320,deutschetelekom 44 | 3352,Telefonica 45 | 4637,Telstra 46 | 4739,iiNet 47 | 4764,Aussie_BB 48 | 4922,Shentel 49 | 5089,virginmedia 50 | 5198,jazzpk 51 | 5378,VodafoneUK 52 | 5410,bouyguestelecom 53 | 5432,proximus 54 | 5466,eir 55 | 5483,telekomHU 56 | 5488,proximus 57 | 5607,SkyUK 58 | 5610,O2_CZ 59 | 5645,TekSavvyNetwork 60 | 5769,videotron 61 | 6128,Optimum 62 | 6181,CincyBell 63 | 6327,ShawInfo 64 | 6667,ElisaOyj 65 | 6677,siminn 66 | 6697,BeltelecomBy 67 | 6730,sunrise_de 68 | 6799,otenet_gr 69 | 6830,libertyglobal 70 | 6848,Telenet 71 | 6866,cytacyprus 72 | 7029,Windstream 73 | 7203,Leaseweb 74 | 7545,TPG_Telecom 75 | 7642,Dhiraagu 76 | 7922,Comcast 77 | 7992,cogeco 78 | 8100,QuadraNet 79 | 8151,Telmex 80 | 8167,oi_oficial 81 | 8220,Colt_Technology 82 | 8236,DNA_fi 83 | 8282,coloclue 84 | 8412,magentatelekom 85 | 8422,NetCologneNEWS 86 | 8447,A1Telekom 87 | 8455,as8455 88 | 8559,KabelplusDK 89 | 8640,Zurich 90 | 8758,iwayag 91 | 8803,migros 92 | 8881,versatel 93 | 9009,WeAreM247 94 | 9031,edpnet 95 | 9121,TurkTelekom 96 | 9208,win_ICTpartner 97 | 9299,PLDT_Cares 98 | 9318,SK_Bworld 99 | 9829,BSNLCorporate 100 | 10139,LiveSmart 101 | 10139,SMARTCares 102 | 10838,getspectrum 103 | 11427,GetSpectrum 104 | 11831,eSecureData 105 | 12271,getspectrum 106 | 12322,free 107 | 12330,MPYPalvelut 108 | 12337,norisnetwork 109 | 12350,VTX_Telecom 110 | 12353,VodafonePT 111 | 12392,VOOetvous 112 | 12430,vodafone_es 113 | 12511,swisspost 114 | 12566,SFR 115 | 12576,ee 116 | 12582,TeliaFinland 117 | 12626,SFR 118 | 12735,TurkNet 119 | 12859,bitnl 120 | 12874,FASTWEB 121 | 12876,as12876 122 | 13030,init7 123 | 13036,TMobile_CZE 124 | 13156,nowoportugal 125 | 13170,KaisanetOy 126 | 13213,UK2 127 | 13283,juliusbaer 128 | 14061,DigitalOcean 129 | 14537,Continent8 130 | 15424,ElisaOyj 131 | 15496,AaltoUniversity 132 | 15517,netstream 133 | 15547,bliblablo_ch 134 | 15557,SFR 135 | 15585,kanton_bern 136 | 15623,cyberlinkag_noc 137 | 15796,SaltMobile_Care 138 | 16019,Vodafone_CZ 139 | 16086,DNA_fi 140 | 16135,Turkcell 141 | 16302,SuomiCom 142 | 16591,GoogleFiber 143 | 17090,dbdesignllc 144 | 18209,ACTFibernet 145 | 19148,Leaseweb 146 | 20001,getspectrum 147 | 20115,getspectrum 148 | 20868,ABBgroupnews 149 | 20904,Netplaza 150 | 20931,ElisaOyj 151 | 21013,eww_Gruppe 152 | 21221,infopact 153 | 21334,Vodafone_HU 154 | 21366,ElisaOyj 155 | 21502,Numericable 156 | 21928,TMobile 157 | 22394,CELLCOATT 158 | 22773,CoxComm 159 | 23889,telecom_mu 160 | 23944,SKYserves 161 | 24713,CGI_FI 162 | 24751,JNTPSP 163 | 24904,knetfr 164 | 24940,Hetzner_Online 165 | 24952,firmenich 166 | 25021,staat_freiburg 167 | 25106,by_mts 168 | 25178,PCCWGlobal 169 | 25255,dreioesterreich 170 | 27796,Ovnicom 171 | 28329,G8Networks 172 | 28403,RadiomovilDIPSA 173 | 28573,NEToficial 174 | 28686,AVECTRIS_IT 175 | 28704,proximus 176 | 29132,imageworldFIN 177 | 29222,infomaniak 178 | 29372,SFR 179 | 29422,TeliaFinland 180 | 29485,A1Hrvatska 181 | 29695,Altibox 182 | 29854,WestHost 183 | 30633,Leaseweb 184 | 30722,VodafoneIT 185 | 30736,ASERGOGroup 186 | 30798,TNNetOy 187 | 30900,webworldireland 188 | 31423,PROMAX 189 | 31615,tmobile_webcare 190 | 31639,planeettanet 191 | 32489,AmanahTech 192 | 33083,Axcelx 193 | 33182,hostdime 194 | 33801,EdzcomOfficial 195 | 33915,ZiggoWebcare 196 | 34781,citycable 197 | 34803,BroadbandGibraltarLtd. 198 | 35228,O2 199 | 35280,Acorus Networks/Volterra 200 | 35382,Capnova 201 | 35612,eolo_it 202 | 36077,dynamichosting 203 | 36351,IBMcloud 204 | 36352,ColoCrossing 205 | 36850,unc 206 | 37611,Afrihost 207 | 37705,Topnet_FSI 208 | 39651,ComHemAB 209 | 39699,LouneaOy 210 | 39857,AaltoUniversity 211 | 40676,PsychzNetworks 212 | 41164,Get_Telia_Norway 213 | 41998,NetComBW 214 | 42541,FiberBy 215 | 42772,a1belarus 216 | 43289,trabia_network 217 | 43350,nforce_bv 218 | 44944,Telenet 219 | 45011,A3Sverige 220 | 45595,PTCLOfficial 221 | 46375,sonic 222 | 46562,livetss 223 | 46664,volumedrive 224 | 47147,_ANEXIA 225 | 47377,OrangeBeFR 226 | 47524,turksat 227 | 47800,lanetua 228 | 48403,planeettanet 229 | 49071,srgssr 230 | 49422,DNA_fi 231 | 49505,selectel 232 | 49762,ElisaOyj 233 | 49826,LouneaOy 234 | 50266,tmobile_webcare 235 | 50290,Valtori_ 236 | 50340,selectel 237 | 50377,ITaitoOy 238 | 51164,CybercomFinland 239 | 51207,Free Mobile 240 | 51728,SeltimilHuolto 241 | 51820,Swisscom 242 | 51935,BLCkonserni 243 | 55536,pacswitch 244 | 56478,HyperopticCS 245 | 56655,TerraHost 246 | 57066,Yleisradio 247 | 57370,Swisscom 248 | 57866,fusixnetworks 249 | 57933,IRISnet_team 250 | 58003,planeettanet 251 | 58065,packetexchange1 252 | 58820,as58820 253 | 60068,CDN77com 254 | 60633,Swisscom 255 | 61098,Exoscale 256 | 62044,Zscaler_Inc_EMEA 257 | 63023,GTHostCOM 258 | 132199,talk2GLOBE 259 | 132335,leapswitch 260 | 133480,intergridanz 261 | 134067,unitiwireless 262 | 135478,di_cbn 263 | 139879,galaxy_net_pk 264 | 196655,NivosOy 265 | 197595,Obenetwork 266 | 198024,IstekkiOy 267 | 199524,gcorelabs 268 | 200698,GlobalHost_bh 269 | 202053,UpCloud 270 | 203609,Erillisverkot 271 | 205668,macminihost 272 | 206067,ThreeUK 273 | 206067,ThreeUKSupport 274 | 206567,tamedia 275 | 206927,Bittium 276 | 207960,AS_207960 277 | 209507,DashfloNet 278 | 262287,maxihost 279 | 263812,ipxonnetworks 280 | 264649,nuthost 281 | 265656,AnacondaWeb 282 | 393886,Leaseweb 283 | 394256,Tech_Futures 284 | 394380,Leaseweb 285 | 395954,Leaseweb 286 | 396190,Leaseweb 287 | 396362,Leaseweb 288 | 397143,neptunenets 289 | 397373,h4ytec 290 | -------------------------------------------------------------------------------- /src/css/colors.css: -------------------------------------------------------------------------------- 1 | html { 2 | --gray-9-rgb: 246, 249, 252; --gray-9: rgb(var(--gray-9-rgb)); 3 | --gray-8-rgb: 230, 235, 241; --gray-8: rgb(var(--gray-8-rgb)); 4 | --gray-7-rgb: 207, 215, 223; --gray-7: rgb(var(--gray-7-rgb)); 5 | --gray-6-rgb: 171, 183, 196; --gray-6: rgb(var(--gray-6-rgb)); 6 | --gray-5-rgb: 137, 152, 169; --gray-5: rgb(var(--gray-5-rgb)); 7 | --gray-4-rgb: 108, 125, 146; --gray-4: rgb(var(--gray-4-rgb)); 8 | --gray-3-rgb: 83, 96, 126; --gray-3: rgb(var(--gray-3-rgb)); 9 | --gray-2-rgb: 67, 72, 111; --gray-2: rgb(var(--gray-2-rgb)); 10 | --gray-1-rgb: 50, 51, 92; --gray-1: rgb(var(--gray-1-rgb)); 11 | --gray-0-rgb: 20, 21, 51; --gray-0: rgb(var(--gray-0-rgb)); 12 | 13 | --red-9-rgb: 255, 247, 239; --red-9: rgb(var(--red-9-rgb)); 14 | --red-8-rgb: 255, 231, 205; --red-8: rgb(var(--red-8-rgb)); 15 | --red-7-rgb: 254, 204, 168; --red-7: rgb(var(--red-7-rgb)); 16 | --red-6-rgb: 253, 162, 127; --red-6: rgb(var(--red-6-rgb)); 17 | --red-5-rgb: 248, 118, 95; --red-5: rgb(var(--red-5-rgb)); 18 | --red-4-rgb: 224, 90, 84; --red-4: rgb(var(--red-4-rgb)); 19 | --red-3-rgb: 192, 63, 78; --red-3: rgb(var(--red-3-rgb)); 20 | --red-2-rgb: 156, 36, 71; --red-2: rgb(var(--red-2-rgb)); 21 | --red-1-rgb: 123, 18, 63; --red-1: rgb(var(--red-1-rgb)); 22 | 23 | --orange-9-rgb: 254, 250, 234; --orange-9: rgb(var(--orange-9-rgb)); 24 | --orange-8-rgb: 253, 238, 193; --orange-8: rgb(var(--orange-8-rgb)); 25 | --orange-7-rgb: 252, 217, 152; --orange-7: rgb(var(--orange-7-rgb)); 26 | --orange-6-rgb: 252, 188, 120; --orange-6: rgb(var(--orange-6-rgb)); 27 | --orange-5-rgb: 245, 154, 96; --orange-5: rgb(var(--orange-5-rgb)); 28 | --orange-4-rgb: 225, 124, 82; --orange-4: rgb(var(--orange-4-rgb)); 29 | --orange-3-rgb: 198, 94, 71; --orange-3: rgb(var(--orange-3-rgb)); 30 | --orange-2-rgb: 168, 61, 59; --orange-2: rgb(var(--orange-2-rgb)); 31 | --orange-1-rgb: 140, 35, 48; --orange-1: rgb(var(--orange-1-rgb)); 32 | 33 | --yellow-9-rgb: 254, 253, 230; --yellow-9: rgb(var(--yellow-9-rgb)); 34 | --yellow-8-rgb: 252, 243, 182; --yellow-8: rgb(var(--yellow-8-rgb)); 35 | --yellow-7-rgb: 252, 231, 138; --yellow-7: rgb(var(--yellow-7-rgb)); 36 | --yellow-6-rgb: 251, 213, 113; --yellow-6: rgb(var(--yellow-6-rgb)); 37 | --yellow-5-rgb: 244, 189, 97; --yellow-5: rgb(var(--yellow-5-rgb)); 38 | --yellow-4-rgb: 226, 159, 80; --yellow-4: rgb(var(--yellow-4-rgb)); 39 | --yellow-3-rgb: 205, 124, 65; --yellow-3: rgb(var(--yellow-3-rgb)); 40 | --yellow-2-rgb: 179, 86, 49; --yellow-2: rgb(var(--yellow-2-rgb)); 41 | --yellow-1-rgb: 156, 53, 36; --yellow-1: rgb(var(--yellow-1-rgb)); 42 | 43 | --green-9-rgb: 242, 254, 240; --green-9: rgb(var(--green-9-rgb)); 44 | --green-8-rgb: 215, 249, 209; --green-8: rgb(var(--green-8-rgb)); 45 | --green-7-rgb: 177, 240, 184; --green-7: rgb(var(--green-7-rgb)); 46 | --green-6-rgb: 121, 227, 164; --green-6: rgb(var(--green-6-rgb)); 47 | --green-5-rgb: 71, 206, 144; --green-5: rgb(var(--green-5-rgb)); 48 | --green-4-rgb: 47, 179, 128; --green-4: rgb(var(--green-4-rgb)); 49 | --green-3-rgb: 32, 148, 113; --green-3: rgb(var(--green-3-rgb)); 50 | --green-2-rgb: 20, 114, 97; --green-2: rgb(var(--green-2-rgb)); 51 | --green-1-rgb: 9, 84, 80; --green-1: rgb(var(--green-1-rgb)); 52 | 53 | --blue-9-rgb: 232, 255, 255; --blue-9: rgb(var(--blue-9-rgb)); 54 | --blue-8-rgb: 186, 253, 254; --blue-8: rgb(var(--blue-8-rgb)); 55 | --blue-7-rgb: 147, 239, 250; --blue-7: rgb(var(--blue-7-rgb)); 56 | --blue-6-rgb: 109, 213, 246; --blue-6: rgb(var(--blue-6-rgb)); 57 | --blue-5-rgb: 75, 179, 230; --blue-5: rgb(var(--blue-5-rgb)); 58 | --blue-4-rgb: 57, 152, 209; --blue-4: rgb(var(--blue-4-rgb)); 59 | --blue-3-rgb: 40, 123, 181; --blue-3: rgb(var(--blue-3-rgb)); 60 | --blue-2-rgb: 24, 93, 150; --blue-2: rgb(var(--blue-2-rgb)); 61 | --blue-1-rgb: 12, 68, 120; --blue-1: rgb(var(--blue-1-rgb)); 62 | 63 | --indigo-9-rgb: 237, 250, 255; --indigo-9: rgb(var(--indigo-9-rgb)); 64 | --indigo-8-rgb: 198, 240, 254; --indigo-8: rgb(var(--indigo-8-rgb)); 65 | --indigo-7-rgb: 159, 220, 254; --indigo-7: rgb(var(--indigo-7-rgb)); 66 | --indigo-6-rgb: 137, 188, 251; --indigo-6: rgb(var(--indigo-6-rgb)); 67 | --indigo-5-rgb: 121, 152, 245; --indigo-5: rgb(var(--indigo-5-rgb)); 68 | --indigo-4-rgb: 104, 118, 226; --indigo-4: rgb(var(--indigo-4-rgb)); 69 | --indigo-3-rgb: 86, 93, 189; --indigo-3: rgb(var(--indigo-3-rgb)); 70 | --indigo-2-rgb: 68, 71, 137; --indigo-2: rgb(var(--indigo-2-rgb)); 71 | --indigo-1-rgb: 50, 51, 92; --indigo-1: rgb(var(--indigo-1-rgb)); 72 | 73 | --violet-9-rgb: 245, 248, 254; --violet-9: rgb(var(--violet-9-rgb)); 74 | --violet-8-rgb: 225, 233, 250; --violet-8: rgb(var(--violet-8-rgb)); 75 | --violet-7-rgb: 206, 210, 246; --violet-7: rgb(var(--violet-7-rgb)); 76 | --violet-6-rgb: 190, 178, 242; --violet-6: rgb(var(--violet-6-rgb)); 77 | --violet-5-rgb: 167, 143, 231; --violet-5: rgb(var(--violet-5-rgb)); 78 | --violet-4-rgb: 143, 113, 211; --violet-4: rgb(var(--violet-4-rgb)); 79 | --violet-3-rgb: 115, 89, 180; --violet-3: rgb(var(--violet-3-rgb)); 80 | --violet-2-rgb: 87, 65, 140; --violet-2: rgb(var(--violet-2-rgb)); 81 | --violet-1-rgb: 62, 45, 105; --violet-1: rgb(var(--violet-1-rgb)); 82 | 83 | --lilac-9-rgb: 255, 245, 252; --lilac-9: rgb(var(--lilac-9-rgb)); 84 | --lilac-8-rgb: 255, 225, 245; --lilac-8: rgb(var(--lilac-8-rgb)); 85 | --lilac-7-rgb: 254, 200, 237; --lilac-7: rgb(var(--lilac-7-rgb)); 86 | --lilac-6-rgb: 245, 166, 234; --lilac-6: rgb(var(--lilac-6-rgb)); 87 | --lilac-5-rgb: 214, 133, 215; --lilac-5: rgb(var(--lilac-5-rgb)); 88 | --lilac-4-rgb: 182, 109, 194; --lilac-4: rgb(var(--lilac-4-rgb)); 89 | --lilac-3-rgb: 145, 84, 170; --lilac-3: rgb(var(--lilac-3-rgb)); 90 | --lilac-2-rgb: 107, 59, 143; --lilac-2: rgb(var(--lilac-2-rgb)); 91 | --lilac-1-rgb: 75, 39, 117; --lilac-1: rgb(var(--lilac-1-rgb)); 92 | } 93 | 94 | html { 95 | --status-red-fg: #a93e2c; 96 | --status-red-bg: #ffe0e3; 97 | --status-yellow-fg: #8c6200; 98 | --status-yellow-bg: #fdeec1; 99 | --status-green-fg: #057554; 100 | --status-green-bg: #bff4c2; 101 | } 102 | -------------------------------------------------------------------------------- /workers-site/index.js: -------------------------------------------------------------------------------- 1 | import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler' 2 | import OPERATORS_STRING from '../data/operators.csv' 3 | import ISP_TWITTER_STRING from '../data/twitter.csv' 4 | 5 | import parse from 'csv-parse/lib/sync' 6 | import pickBy from 'lodash.pickby' 7 | 8 | /** 9 | * The DEBUG flag will do two things that help during development: 10 | * 1. we will skip caching on the edge, which makes it easier to 11 | * debug. 12 | * 2. we will return an error message on exception in your Response rather 13 | * than the default 404.html page. 14 | */ 15 | const DEBUG = false 16 | 17 | const IS_BGP_SAFE_YET = false // TODO - update when safe ;) 18 | 19 | const OPERATORS = parse(OPERATORS_STRING, {columns: true}) 20 | const ISP_TWITTER = parse(ISP_TWITTER_STRING, {columns: true}) 21 | 22 | function statusSortIndex(status) { 23 | return [, 'safe', 'partially safe', 'unsafe'].indexOf(status) 24 | } 25 | 26 | // Yay for stable sorting in ES2019! 27 | OPERATORS.sort(function(a, b) { 28 | return +a.rank - +b.rank 29 | }) 30 | 31 | OPERATORS.sort(function(a, b) { 32 | return statusSortIndex(a.status) - statusSortIndex(b.status) 33 | }) 34 | 35 | const majorCloudASNs = [ 36 | '13335', // Cloudflare 37 | '14907', // Wikimedia 38 | '15169', // Google 39 | '16509', // Amazon 40 | '12876', // Scaleway 41 | '9009', // M247 42 | ] 43 | 44 | let MAJOR_OPERATORS_COUNT = 0 45 | 46 | for (let i = 0; i < OPERATORS.length; i += 1) { 47 | const operator = OPERATORS[i] 48 | const rank = +operator.rank 49 | 50 | const major = (1 <= rank && rank <= 24) || majorCloudASNs.includes(operator.asn) 51 | if (major) MAJOR_OPERATORS_COUNT += 1 52 | 53 | operator.major = major 54 | } 55 | 56 | addEventListener('fetch', event => { 57 | try { 58 | event.respondWith(handleEvent(event)) 59 | } catch (e) { 60 | if (DEBUG) { 61 | return event.respondWith( 62 | new Response(e.message || e.toString(), { 63 | status: 500, 64 | }), 65 | ) 66 | } 67 | event.respondWith(new Response('Internal Error', { status: 500 })) 68 | } 69 | }) 70 | 71 | async function handleEvent(event) { 72 | const url = new URL(event.request.url) 73 | let options = {} 74 | 75 | try { 76 | if (DEBUG) { 77 | options.cacheControl = { 78 | bypassCache: true, 79 | } 80 | } 81 | 82 | const page = await getAssetFromKV(event, options) 83 | 84 | // Allow headers to be altered 85 | const response = new Response(page.body, page) 86 | 87 | // Manually adding utf8 charset for now until https://git.io/Jf1aQ is added to release 88 | const contentType = response.headers.get('content-type') 89 | if (contentType.startsWith('text')) { 90 | response.headers.set('content-type', contentType + '; charset=utf8') 91 | } 92 | 93 | if (url.pathname === '/' || url.pathname === '/index.html') { 94 | response.headers.set('Cache-Control', 'public; max-age=60') 95 | response.headers.set('Content-Security-Policy', "default-src 'none'; script-src 'self' data: 'unsafe-inline'; object-src 'none'; style-src 'self' ui.components.workers.dev; img-src 'self'; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'self' invalid.rpki.cloudflare.com valid.rpki.cloudflare.com") 96 | response.headers.set('X-XSS-Protection', '1; mode=block') 97 | response.headers.set('X-Frame-Options', 'DENY') 98 | response.headers.set('Referrer-Policy', 'unsafe-url') 99 | response.headers.set('Feature-Policy', 'none') 100 | 101 | return new HTMLRewriter() 102 | .on('head', new VarInjector('ISP_TWITTER', ISP_TWITTER)) 103 | .on('[data-is-bgp-safe-yet]', new StringInjector(IS_BGP_SAFE_YET ? 'Yes.' : 'No.')) 104 | .on('[data-major-operators-count]', new StringInjector(MAJOR_OPERATORS_COUNT)) 105 | .on('table[data-js-table]', new OperatorsTableBuilder(OPERATORS)) 106 | .transform(response) 107 | } else { 108 | response.headers.set('Cache-Control', 'public; max-age=86400') 109 | 110 | return response 111 | } 112 | } catch (e) { 113 | // if an error is thrown try to serve the asset at 404.html 114 | if (!DEBUG) { 115 | try { 116 | let notFoundResponse = await getAssetFromKV(event, { 117 | mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req), 118 | }) 119 | 120 | return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 }) 121 | } catch (e) {} 122 | } 123 | 124 | return new Response(e.message || e.toString(), { status: 500 }) 125 | } 126 | } 127 | 128 | class VarInjector { 129 | constructor(name, body) { 130 | this.name = name 131 | this.body = body 132 | } 133 | 134 | element(element) { 135 | element.prepend(` 136 | 139 | `, { 140 | html: true 141 | }) 142 | } 143 | } 144 | 145 | class StringInjector { 146 | constructor(string) { 147 | this.string = string 148 | } 149 | 150 | element(element) { 151 | element.setInnerContent(this.string) 152 | } 153 | } 154 | 155 | const safeAttr = (str) => { 156 | return str.replace(/"/g, '"') 157 | } 158 | 159 | function template(rows) { 160 | const columns = ['name', 'type', 'details', 'status', 'asn'] 161 | 162 | function each(value, func) { 163 | let out = '' 164 | for (let key in value) { 165 | out += func(value[key], key) 166 | } 167 | return out 168 | } 169 | 170 | function tbody(v) { 171 | return ` 172 | 173 | ${ each(v, row) } 174 | 175 | ` 176 | } 177 | 178 | function row(r) { 179 | const major = r.major 180 | r = pickBy(r, (v, k) => columns.indexOf(k) !== -1) 181 | 182 | return ` 183 | 184 | ${ each(r, cell) } 185 | 186 | ` 187 | } 188 | 189 | function cell(val, key) { 190 | return ` 191 | ${ val } 192 | ` 193 | } 194 | 195 | function sortKey(key, val) { 196 | if (key === 'status') 197 | return statusSortIndex(val) 198 | else 199 | return val 200 | } 201 | 202 | return tbody(rows) 203 | } 204 | 205 | class OperatorsTableBuilder { 206 | constructor(operators) { 207 | this.operators = operators 208 | } 209 | 210 | element(element) { 211 | element.append(template(this.operators), { 212 | html: true 213 | }) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /workers-site/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "lockfileVersion": 1, 4 | "requires": true, 5 | "dependencies": { 6 | "@cloudflare/kv-asset-handler": { 7 | "version": "0.0.9", 8 | "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.0.9.tgz", 9 | "integrity": "sha512-OP+uwr+5VShB9ktzJvkwy1AibGaTV7EoHs/YMemUY56fgnqI+IlV++e4T6cTUfHDAfE/pdXMx7q4zMf90dU00Q==", 10 | "requires": { 11 | "@types/mime": "^2.0.1", 12 | "mime": "^2.4.4" 13 | } 14 | }, 15 | "@types/mime": { 16 | "version": "2.0.1", 17 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", 18 | "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" 19 | }, 20 | "ajv": { 21 | "version": "6.12.0", 22 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", 23 | "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", 24 | "requires": { 25 | "fast-deep-equal": "^3.1.1", 26 | "fast-json-stable-stringify": "^2.0.0", 27 | "json-schema-traverse": "^0.4.1", 28 | "uri-js": "^4.2.2" 29 | } 30 | }, 31 | "ajv-keywords": { 32 | "version": "3.4.1", 33 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", 34 | "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" 35 | }, 36 | "big.js": { 37 | "version": "5.2.2", 38 | "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", 39 | "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" 40 | }, 41 | "commander": { 42 | "version": "2.20.3", 43 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 44 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 45 | "optional": true 46 | }, 47 | "csv-parse": { 48 | "version": "4.8.8", 49 | "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.8.8.tgz", 50 | "integrity": "sha512-Kv3Ilz2GV58dOoHBXRCTF8ijxlLjl80bG3d67XPI6DNqffb3AnbPbKr/WvCUMJq5V0AZYi6sukOaOQAVpfuVbg==" 51 | }, 52 | "emojis-list": { 53 | "version": "3.0.0", 54 | "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", 55 | "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" 56 | }, 57 | "fast-deep-equal": { 58 | "version": "3.1.1", 59 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", 60 | "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" 61 | }, 62 | "fast-json-stable-stringify": { 63 | "version": "2.1.0", 64 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 65 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 66 | }, 67 | "json-schema-traverse": { 68 | "version": "0.4.1", 69 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 70 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 71 | }, 72 | "json5": { 73 | "version": "1.0.1", 74 | "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", 75 | "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", 76 | "requires": { 77 | "minimist": "^1.2.0" 78 | } 79 | }, 80 | "loader-utils": { 81 | "version": "1.4.0", 82 | "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", 83 | "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", 84 | "requires": { 85 | "big.js": "^5.2.2", 86 | "emojis-list": "^3.0.0", 87 | "json5": "^1.0.1" 88 | } 89 | }, 90 | "mime": { 91 | "version": "2.4.4", 92 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", 93 | "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" 94 | }, 95 | "minimist": { 96 | "version": "1.2.5", 97 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 98 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 99 | }, 100 | "neo-async": { 101 | "version": "2.6.1", 102 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", 103 | "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" 104 | }, 105 | "punycode": { 106 | "version": "2.1.1", 107 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 108 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 109 | }, 110 | "raw-loader": { 111 | "version": "4.0.0", 112 | "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.0.tgz", 113 | "integrity": "sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==", 114 | "requires": { 115 | "loader-utils": "^1.2.3", 116 | "schema-utils": "^2.5.0" 117 | } 118 | }, 119 | "schema-utils": { 120 | "version": "2.6.5", 121 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", 122 | "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", 123 | "requires": { 124 | "ajv": "^6.12.0", 125 | "ajv-keywords": "^3.4.1" 126 | } 127 | }, 128 | "source-map": { 129 | "version": "0.6.1", 130 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 131 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 132 | }, 133 | "uglify-js": { 134 | "version": "3.8.1", 135 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", 136 | "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", 137 | "optional": true, 138 | "requires": { 139 | "commander": "~2.20.3", 140 | "source-map": "~0.6.1" 141 | } 142 | }, 143 | "uri-js": { 144 | "version": "4.2.2", 145 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 146 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 147 | "requires": { 148 | "punycode": "^2.1.0" 149 | } 150 | }, 151 | "wordwrap": { 152 | "version": "1.0.0", 153 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 154 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import './vendor/sortable.min.js' 2 | import 'focus-visible-polyfill' 3 | 4 | const svgCheck = '' 5 | const svgTimes = '' 6 | 7 | const successMessageDetails = `${ '' 8 | }fetch https://valid.rpki.cloudflare.com 9 | ${ svgCheck }correctly accepted valid prefixes 10 | 11 | fetch https://invalid.rpki.cloudflare.com 12 | ${ svgCheck }correctly rejected invalid prefixes` 13 | 14 | const failureMessageDetails = `${ '' 15 | }fetch https://valid.rpki.cloudflare.com 16 | ${ svgCheck }correctly accepted valid prefixes 17 | 18 | fetch https://invalid.rpki.cloudflare.com 19 | ${ svgTimes }incorrectly accepted invalid prefixes` 20 | 21 | const setupShowAllRowsToggle = () => { 22 | const table = document.querySelector('[data-js-table]') 23 | const button = document.querySelector('[data-js-toggle-show-all-rows]') 24 | 25 | if (!table || !button) return 26 | 27 | const tableSummary = document.querySelector('[data-js-table-summary]') 28 | const majorCount = table.querySelectorAll('tr[data-is-major="true"]').length 29 | 30 | button.addEventListener('click', () => { 31 | if (table.getAttribute('data-show-all-rows') === 'false') { 32 | table.setAttribute('data-show-all-rows', 'true') 33 | button.textContent = '- Show fewer' 34 | tableSummary.textContent = 'Displaying all operators' 35 | } else { 36 | table.setAttribute('data-show-all-rows', 'false') 37 | button.textContent = '+ Show all' 38 | tableSummary.textContent = `Displaying ${ majorCount } major operators` 39 | } 40 | }) 41 | } 42 | 43 | const setupASNColumnToggle = () => { 44 | const table = document.querySelector('[data-js-table]') 45 | const button = document.querySelector('[data-js-toggle-asn-column]') 46 | 47 | if (!button) return 48 | 49 | button.addEventListener('click', () => { 50 | if (table.getAttribute('data-hide-asn-column') === 'true') { 51 | table.setAttribute('data-hide-asn-column', 'false') 52 | button.textContent = '- Hide ASN column' 53 | } else { 54 | table.setAttribute('data-hide-asn-column', 'true') 55 | button.textContent = '+ Show ASN column' 56 | } 57 | }) 58 | } 59 | 60 | const initTesting = () => { 61 | const button = document.querySelector('[data-js-test]') 62 | const el = document.querySelector('[data-js-test-results]') 63 | 64 | const render = ({ type, message, tweet }) => { 65 | if (type === 'running') { 66 | button.setAttribute('disabled', '') 67 | } else { 68 | button.removeAttribute('disabled', '') 69 | } 70 | 71 | el.setAttribute('data-type', type) 72 | el.innerHTML = '' 73 | 74 | const messageEl = document.createElement('span') 75 | messageEl.textContent = message 76 | el.appendChild(messageEl) 77 | 78 | if (tweet) { 79 | messageEl.textContent += ' ' 80 | const tweetWrapper = document.createElement('span') 81 | tweetWrapper.className = 'Markdown' 82 | const tweetLink = document.createElement('a') 83 | tweetLink.href = `https://twitter.com/intent/tweet/?via=Cloudflare&text=${ encodeURIComponent(tweet) }` 84 | tweetLink.innerHTML = 'Tweet this →' 85 | tweetWrapper.appendChild(tweetLink) 86 | el.appendChild(tweetWrapper) 87 | } 88 | 89 | if (type === 'success' || type === 'failure') { 90 | const details = document.createElement('details') 91 | const summary = document.createElement('summary') 92 | const pre = document.createElement('pre') 93 | 94 | details.appendChild(summary) 95 | details.appendChild(pre) 96 | 97 | summary.textContent = 'Details' 98 | 99 | if (type === 'success') { 100 | pre.innerHTML = successMessageDetails 101 | } else { 102 | pre.innerHTML = failureMessageDetails 103 | } 104 | 105 | el.appendChild(details) 106 | } 107 | } 108 | 109 | const getISPInfo = (data, forTweet) => { 110 | if (!data || data.asn === 0) return ' ' 111 | 112 | if (data.name && data.name !== '') { 113 | if (forTweet) { 114 | for (var i=0; i < ISP_TWITTER.length; i++){ 115 | if (ISP_TWITTER[i].asn === data.asn.toString()){ 116 | const twitterUsername = `@${ ISP_TWITTER[i].handle }` 117 | return `, ${ twitterUsername } (AS${ data.asn }), ` 118 | } 119 | } 120 | } 121 | 122 | return ` (${ data.name }, AS${ data.asn }) ` 123 | } 124 | 125 | return ` (AS${ data.asn }) ` 126 | } 127 | 128 | const renderSuccessWARP = () => { 129 | render({ 130 | type: 'success', 131 | message: 'You are using Cloudflare WARP, which implements BGP safely.' 132 | }) 133 | } 134 | 135 | const renderSuccess = data => { 136 | if (data.blackholed) { 137 | renderFailure(data) 138 | } else { 139 | render({ 140 | type: 'success', 141 | message: `Your ISP${ getISPInfo(data) }implements BGP safely. It correctly drops invalid prefixes.`, 142 | tweet: `My Internet provider${ getISPInfo(data, true) }implements BGP safely! Check out https://isbgpsafeyet.com to see if your ISP implements BGP in a safe way or if it leaves the Internet vulnerable to malicious route hijacks.` 143 | }) 144 | } 145 | } 146 | 147 | const renderFailure = data => { 148 | render({ 149 | type: 'failure', 150 | message: `Your ISP${ getISPInfo(data) }does not implement BGP safely. It should be using RPKI to protect the Internet from BGP hijacks.`, 151 | tweet: `Unfortunately, my Internet provider${ getISPInfo(data, true) }does NOT implement BGP safely. Check out https://isbgpsafeyet.com to see if your ISP implements BGP in a safe way or if it leaves the Internet vulnerable to malicious route hijacks.` 152 | }) 153 | } 154 | 155 | const renderError = () => { 156 | render({ 157 | type: 'error', 158 | message: 'An error occured trying to conduct the test. Please try again.' 159 | }) 160 | } 161 | 162 | const runTest = () => { 163 | const now = performance.now() 164 | 165 | const uid = Math.floor((Math.random() * 10e10)).toString(16) 166 | 167 | const warpFetch = fetch('https://valid.rpki.cloudflare.com/cdn-cgi/trace') 168 | const validFetch = fetch(`https://valid.rpki.cloudflare.com/${ uid }`) 169 | const invalidFetch = fetch(`https://invalid.rpki.cloudflare.com/${ uid }`) 170 | 171 | render({ 172 | type: 'running', 173 | message: 'Running test...' 174 | }) 175 | 176 | warpFetch 177 | .then(response => response.text()) 178 | .then(response => { 179 | if (!response.includes('warp=off')) { 180 | return renderSuccessWARP() 181 | } 182 | 183 | validFetch 184 | .then(response => response.json()) 185 | .then(data => { 186 | let timedOut = false 187 | let completed = false 188 | 189 | setTimeout(() => { 190 | timedOut = true 191 | if (completed) return 192 | renderSuccess(data) 193 | }, 2 * 1000) 194 | 195 | invalidFetch 196 | .then(() => { 197 | completed = true 198 | if (timedOut) return 199 | renderFailure(data) 200 | }) 201 | .catch(err => renderSuccess(data)) 202 | }) 203 | .catch(err => { 204 | renderError() 205 | }) 206 | }) 207 | .catch(err => { 208 | renderError() 209 | }) 210 | } 211 | 212 | if (button) 213 | button.addEventListener('click', () => runTest()) 214 | } 215 | 216 | const initDiagrams = () => { 217 | document.querySelectorAll('[data-js-diagram]').forEach(el => { 218 | const name = el.getAttribute('data-js-diagram') 219 | const header = el.querySelector('[data-js-diagram-header]') 220 | const button = document.querySelector(`[data-js-diagram-toggle-for="${ name }"]`) 221 | 222 | if (!button) 223 | return 224 | 225 | const copy = { 226 | unsafe: { 227 | header: { 228 | happy: 'Normal request', 229 | sad: 'Hijacked' 230 | }, 231 | button: { 232 | happy: 'Hijack the request', 233 | sad: 'Undo BGP hijack' 234 | } 235 | }, 236 | safe: { 237 | header: { 238 | happy: 'BGP with RPKI', 239 | sad: 'BGP with RPKI' 240 | }, 241 | button: { 242 | happy: 'Attempt to hijack', 243 | sad: 'Undo hijack attempt' 244 | } 245 | } 246 | } 247 | 248 | button.addEventListener('click', () => { 249 | if (el.getAttribute('path') === 'happy') { 250 | el.setAttribute('path', 'sad') 251 | button.className = 'Button Button-is-bordered Button-has-depth' 252 | } else { 253 | el.setAttribute('path', 'happy') 254 | button.className = 'Button Button-is-primary Button-is-elevated' 255 | } 256 | 257 | const path = el.getAttribute('path') 258 | header.textContent = copy[name].header[path] 259 | button.textContent = copy[name].button[path] 260 | }) 261 | }) 262 | } 263 | 264 | const openPossibleTargetFAQItem = () => { 265 | const target = document.querySelector(':target') 266 | if (!target || target.tagName.toLowerCase() !== 'details') return 267 | 268 | target.setAttribute('open', '') 269 | target.scrollIntoView() 270 | } 271 | 272 | const init = () => { 273 | setupShowAllRowsToggle() 274 | setupASNColumnToggle() 275 | initTesting() 276 | initDiagrams() 277 | 278 | requestAnimationFrame(() => { 279 | openPossibleTargetFAQItem() 280 | }) 281 | } 282 | 283 | init() 284 | -------------------------------------------------------------------------------- /data/operators.csv: -------------------------------------------------------------------------------- 1 | name,type,details,status,asn,rank 2 | Level3/CenturyLink,transit,,unsafe,3356,1 3 | Telia,transit,signed + filtering,safe,1299,2 4 | Cogent,transit,signed + filtering,safe,174,3 5 | GTT,transit,signed + filtering,safe,3257,4 6 | NTT,transit,signed + filtering,safe,2914,5 7 | Sparkle,transit,started,unsafe,6762,6 8 | Hurricane Electric,transit,signed + filtering,safe,6939,7 9 | TATA,transit,filtering peers only,partially safe,6453,8 10 | PCCW,transit,filtering peers only,partially safe,3491,9 11 | Zayo,transit,,unsafe,6461,10 12 | Vodafone,transit,,unsafe,1273,11 13 | RETN,transit,,unsafe,9002,13 14 | Orange,transit,started,unsafe,5511,14 15 | Telstra Global,transit,signed,partially safe,4637,15 16 | Telefonica/Telxius,transit,,unsafe,12956,16 17 | SingTel,transit,,unsafe,7473,17 18 | PJSC RosTelecom,transit,,unsafe,12389,19 19 | Deutsche Telekom,ISP,started,unsafe,3320,20 20 | Verizon,ISP,,unsafe,701,21 21 | AT&T,ISP,signed + filtering peers only,partially safe,7018,22 22 | Comcast,ISP,,unsafe,7922,23 23 | TransTelecom,transit,,unsafe,20485,24 24 | Algar Telecom,transit,,unsafe,16735,26 25 | Liberty Global,transit,signed,partially safe,6830,29 26 | Globenet,transit,,unsafe,52320,32 27 | Sprint,transit,,unsafe,1239,34 28 | KPN,transit,signed + filtering,safe,286,36 29 | Telefonica Vivo,transit,,unsafe,10429,39 30 | Internexa,transit,,unsafe,262589,40 31 | Angola Cables,transit,,unsafe,37468,42 32 | China Telecom,transit,,unsafe,4809,43 33 | Oi,ISP,,unsafe,7738,45 34 | Core-Backbone,transit,signed + filtering,safe,33891,46 35 | Vivo GVT,ISP,,unsafe,18881,54 36 | Embratel,transit,,unsafe,4230,58 37 | Telekom Hungary,ISP,signed,unsafe,5483,59 38 | Eletronet,transit,,unsafe,267613,63 39 | Windstream Communications,ISP,,unsafe,7029,64 40 | TIM Brasil,ISP,,unsafe,26615,67 41 | Swisscom,ISP,,unsafe,3303,69 42 | MOB Telecom,transit,,unsafe,28598,74 43 | Telstra Australia,transit,signed,partially safe,1221,76 44 | Cox Communications, ISP,,unsafe,22773,77 45 | G8,transit,signed + filtering,safe,28329,81 46 | Seabras,transit,,unsafe,13786,83 47 | SK Broadband,ISP,,unsafe,9318,85 48 | TPG,transit,,unsafe,7545,87 49 | Durand,transit,,unsafe,22356,88 50 | Bell Canada,ISP,,unsafe,577,90 51 | Optimum,ISP,,unsafe,6128,97 52 | RCS&RDS,ISP,,unsafe,8708,102 53 | GEANT,ISP,signed + filtering,safe,20965,103 54 | Commcorp,transit,,unsafe,14840,122 55 | Next Layer GmbH,transit,signed + filtering,safe,1764,126 56 | TurkTelekom,ISP,,unsafe,9121,127 57 | Shaw Communications,ISP,,unsafe,6327,128 58 | M247,cloud,,unsafe,9009,133 59 | A1 Telekom Austria,ISP,,unsafe,8447,135 60 | Wave Broadband,ISP,,unsafe,11404,136 61 | W I X NET DO BRASIL,cloud,,unsafe,53013,137 62 | Init7 (Schweiz) AG,ISP,started,unsafe,13030,139 63 | Fastweb,ISP,,unsafe,12874,151 64 | American Tower Brasil,transit,,unsafe,23106,154 65 | Vogel,transit,,unsafe,25933,166 66 | OpenX,transit,signed + filtering,safe,263444,173 67 | TIM,ISP,,unsafe,3269,176 68 | TELY,transit,,unsafe,53087,188 69 | Rogers,ISP,,unsafe,812,198 70 | British Telecommunications,ISP,,unsafe,2856,199 71 | Vodafone España,ISP,,unsafe,12430,201 72 | Sunrise Communications AG,ISP,,unsafe,6730,203 73 | SIA Tet,ISP,,unsafe,12578,205 74 | TDC,ISP,,unsafe,3292,222 75 | Jaguar Network,ISP,signed + filtering,safe,30781,226 76 | PLDT,ISP,,unsafe,9299,230 77 | VNPT,cloud,,unsafe,45899,239 78 | Forte Telecom,transit,,unsafe,263009,246 79 | HiNet,ISP,signed + filtering,safe,3462,249 80 | ITS Telecom,transit,,unsafe,28186,255 81 | Alta Rede,transit,,unsafe,28260,259 82 | Vodafone DE,ISP,,unsafe,3209,276 83 | Acorus Networks,ISP,signed + filtering,safe,35280,277 84 | Virgin Media UK,ISP,,unsafe,5089,284 85 | Ensite Telecom,transit,signed + filtering,safe,28263,287 86 | Telenor,ISP,signed + filtering,safe,2119,288 87 | Nianet A/S,ISP,signed,unsafe,31027,302 88 | Vivacom,ISP,signed,partially safe,8866,304 89 | Globe Telecom,ISP,,unsafe,4775,305 90 | HKBN,ISP,,unsafe,9269,308 91 | ANEXIA Internetdienstleistungs GmbH,transit,signed + filtering,safe,47147,323 92 | Biznet Networks,ISP,signed + filtering,safe,17451,324 93 | Copel Telecom,transit,,unsafe,14868,333 94 | Vocus Group NZ,ISP,,unsafe,9790,370 95 | ACONET,transit,started,unsafe,1853,384 96 | Wirelink,transit,,unsafe,28368,391 97 | SFR,ISP,,unsafe,15557,394 98 | RCN,ISP,signed + filtering,safe,6079,396 99 | TASCOM,transit,,unsafe,52871,415 100 | Devoli,ISP,signed + filtering,safe,45177,422 101 | NTS Workspace AG,ISP,signed + filtering,safe,15576,432 102 | MNET,ISP,signed + filtering,safe,8767,440 103 | Hutchison Drei Austria,ISP,,unsafe,25255,451 104 | K2 Telecom,transit,,unsafe,53181,453 105 | NFOrce,cloud,signed,unsafe,43350,459 106 | Psychz Networks,cloud,,unsafe,40676,463 107 | SuddenLink,ISP,,unsafe,19108,470 108 | Delta Telecom,cloud,,unsafe,29049,471 109 | Kyivstar,ISP,,unsafe,15895,477 110 | Cogeco,ISP,,unsafe,7992,497 111 | DNA Oyj,ISP,,unsafe,16086,523 112 | NIB India,ISP,,unsafe,9829,576 113 | Elisa Finland,ISP,,unsafe,719,577 114 | Reliance Jio,ISP,,unsafe,55836,584 115 | KPN-Netco,ISP,signed,partially safe,1136,593 116 | Volia,cloud,,unsafe,25229,595 117 | Spectrum,ISP,,unsafe,12271,607 118 | Taiwan Fixed Network,ISP,signed,unsafe,9924,613 119 | Beltelecom,ISP,,unsafe,6697,658 120 | ViewQwest,ISP,signed + filtering,safe,18106,675 121 | QuadraNet,cloud,,unsafe,8100,686 122 | Brisanet,ISP,,unsafe,28126,710 123 | Hetzner Online,cloud,signed,unsafe,24940,717 124 | eww ag,transit,,unsafe,21013,722 125 | Videotron,ISP,,unsafe,5769,727 126 | ASAP Telecom,transit,,unsafe,264144,749 127 | G-Core Labs,cloud,,unsafe,199524,759 128 | CYTA,ISP,signed + filtering,safe,6866,763 129 | Janet,ISP,,unsafe,786,781 130 | CDN77,cloud,,unsafe,60068,782 131 | Blix Solutions AS,cloud,,unsafe,50304,792 132 | Telenet,ISP,,unsafe,6848,804 133 | NOS Portugal,ISP,,unsafe,2860,805 134 | Obenetwork,ISP,signed + filtering,safe,197595,833 135 | 2degrees,ISP,,unsafe,23655,856 136 | Altibox,ISP,,unsafe,29695,872 137 | NetCologne,ISP,,unsafe,8422,873 138 | Bredband2,ISP,signed + filtering,safe,29518,879 139 | Vodafone IT,ISP,,unsafe,30722,890 140 | Shentel,ISP,,unsafe,4922,897 141 | Proximus,ISP,,unsafe,5432,911 142 | FasterNET,ISP,,unsafe,28580,924 143 | MásMóvil,ISP,,unsafe,15704,938 144 | Turknet,ISP,,unsafe,12735,954 145 | iiNet Limited,ISP,,unsafe,4739,975 146 | Siminn,ISP,,unsafe,6677,982 147 | Ziggo,ISP,signed,unsafe,33915,1028 148 | UltraWave Telecom,ISP,signed + filtering,safe,262659,1049 149 | IBM Cloud,cloud,,unsafe,36351,1062 150 | Selectel Ltd,cloud,,unsafe,49505,1080 151 | Total Server Solutions,cloud,,unsafe,46562,1139 152 | noris network AG,ISP,signed + filtering,safe,12337,1156 153 | IP Converge Data Services Inc.,cloud,,unsafe,23930,1193 154 | Cablenet Cyprus,ISP,signed + filtering,safe,35432,1224 155 | xneelo,cloud,,unsafe,37153,1232 156 | HotNet Internet Services,ISP,,unsafe,12849,1257 157 | Pakistan Telecom Company Limited,ISP,,unsafe,45595,1259 158 | Radore Veri Merkezi Hizmetleri,cloud,,unsafe,42926,1326 159 | ColoCrossing,cloud,filtering,partially safe,36352,1365 160 | Mobicom,transit,filtering,safe,55805,1367 161 | Terrahost,cloud,signed + filtering,safe,56655,1416 162 | A1 Belarus,ISP,,unsafe,42772,1496 163 | Maxihost,cloud,,unsafe,262287,1499 164 | Selectel MSK,cloud,,unsafe,50340,1504 165 | NetCom BW,ISP,,unsafe,41998,1508 166 | Continent 8 LLC,cloud,,unsafe,14537,1513 167 | Synapsecom Telecoms,cloud,,unsafe,8280,1522 168 | SpaceNet,ISP,signed + filtering,safe,5539,1584 169 | CESNET,ISP,signed + filtering,safe,2852,1638 170 | A3 Sverige,ISP,,unsafe,45011,1644 171 | Deutsche Glasfaser,ISP,,unsafe,60294,1681 172 | Google,cloud,,unsafe,15169,1714 173 | Vodafone Portugal,ISP,,unsafe,12353,1725 174 | TekSavvy,ISP,,unsafe,5645,1727 175 | SkyCable,ISP,,unsafe,23944,1746 176 | A2B Internet,ISP,signed + filtering,safe,51088,1755 177 | Cloudflare,cloud,signed + filtering,safe,13335,1819 178 | HostDime.com Inc,cloud,,unsafe,33182,1841 179 | xs4all,cloud,signed + filtering,safe,3265,1937 180 | Worldstream,ISP,signed,partially safe,49981,2000 181 | CSL IDC,cloud,,unsafe,9891,2007 182 | Telefonica Peru,ISP,,unsafe,6147,2099 183 | Digital Energy Technologies Limited (Global),cloud,,unsafe,61317,2105 184 | MTS Belarus,ISP,,unsafe,25106,2127 185 | TheGigabit,cloud,,unsafe,55720,2147 186 | Netwerkvereniging ColoClue,ISP,signed + filtering,safe,8283,2152 187 | Microsoft,cloud,,unsafe,8075,2262 188 | ST-BGP,cloud,,unsafe,46844,2295 189 | Aussie Broadband,ISP,started,unsafe,4764,2319 190 | Dhiraagu,ISP,signed + filtering,safe,7642,2357 191 | MEO Portugal,ISP,,unsafe,3243,2490 192 | UK-2 Limited,cloud,,unsafe,13213,2517 193 | SKY Brasil,ISP,,unsafe,11338,2599 194 | Ovnicom,cloud,,unsafe,27796,2663 195 | Locaweb,cloud,,unsafe,27715,2752 196 | ARTNET,cloud,,unsafe,197155,2836 197 | K-NET,ISP,,unsafe,24904,2851 198 | APIK Media,cloud,signed + filtering,safe,58820,2982 199 | Free SAS,ISP,signed,unsafe,12322,3007 200 | Bouygues Telecom,ISP,,unsafe,5410,3011 201 | Triolan,ISP,filtering,partially safe,13188,3056 202 | EdgeUno,cloud,,unsafe,7195,3160 203 | Oy Creanova Hosting Solutions Ltd,cloud,,unsafe,51765,3209 204 | GSL Networks,cloud,,unsafe,137409,3277 205 | Amazon,cloud,signed,partially safe,16509,3444 206 | Digi,ISP,,unsafe,20845,3473 207 | EOLO,ISP,signed + filtering,safe,35612,3478 208 | O2 Broadband,ISP,,unsafe,35228,3501 209 | Vodafone Hungary,ISP,,unsafe,21334,3508 210 | Networx Bulgaria,ISP,,unsafe,34569,3603 211 | FishNet,cloud,,unsafe,43317,3679 212 | ArgonHost,cloud,,unsafe,58477,3907 213 | Gis Telecom,ISP,signed + filtering,safe,264130,3924 214 | OVH,cloud,,unsafe,16276,3982 215 | ComHemAB,ISP,started,unsafe,39651,3997 216 | Kingston Communications PLC,ISP,,unsafe,12390,4029 217 | WestHost,cloud,,unsafe,29854,4032 218 | Magenta (T-Mobile) Austria,ISP,,unsafe,8412,4043 219 | Atria Convergence,ISP,signed + filtering,safe,24309,4053 220 | ALMOUROLTEC SERVICOS DE INFORMATICA E INTERNET LDA,cloud,,unsafe,24768,4105 221 | Global IP Exchange,cloud,,unsafe,47536,4117 222 | trabia network,cloud,signed,unsafe,43289,4133 223 | Packetexchange,cloud,,unsafe,58065,4136 224 | Alands Telekommunikation Ab,ISP,,unsafe,3238,4149 225 | LeapSwitch Networks,cloud,filtering,partially safe,132335,4214 226 | Amanah,cloud,,unsafe,32489,4279 227 | UNMETERED,cloud,,unsafe,54133,4340 228 | Via Radio Dourados,transit,signed + filtering,safe,61785,4697 229 | T-Mobile,ISP,,unsafe,21928,4793 230 | Vodafone UK,ISP,,unsafe,5378,4810 231 | ACT Fibernet,ISP,signed + filtering,safe,18209,4821 232 | Numericable,ISP,,unsafe,21502,4825 233 | Get (Telia Norway),ISP,signed + filtering,safe,41164,4847 234 | H4Y,cloud,signed,unsafe,397373,4857 235 | MEO Portugal - Serviços de Comunicações e Multimédia,ISP,,unsafe,42863,4858 236 | Intergrid,cloud,,unsafe,133480,4965 237 | Mobilink,ISP,,unsafe,45669,5166 238 | INTERSPACE-MK,cloud,,unsafe,200899,5200 239 | Monkeybrains,ISP,,unsafe,32329,5215 240 | BroadbandGibraltarLtd.,ISP,,unsafe,34803,5285 241 | AltusHost,cloud,,unsafe,51430,5306 242 | Stadtnetz Bamberg,ISP,,unsafe,198570,5457 243 | DigitalOcean,cloud,,unsafe,14061,6381 244 | Vodafone India,ISP,,unsafe,38266,6428 245 | Afrihost,ISP,,unsafe,37611,6457 246 | EBOX,ISP,signed + filtering,safe,1403,6501 247 | tzulo,cloud,,unsafe,11878,6586 248 | Istanbuldc Veri Merkezi,cloud,,unsafe,197328,6633 249 | Aura Fiber,ISP,,unsafe,204274,6735 250 | Kaisanet Oy,ISP,,unsafe,13170,6773 251 | Phase Layer Global Networks,cloud,,unsafe,51852,6959 252 | eSecureData,cloud,signed,unsafe,11831,7324 253 | Axcelx,cloud,,unsafe,33083,7453 254 | VoiceHost,ISP,signed + filtering,safe,31472,7527 255 | Neptune Networks,cloud,signed + filtering,safe,397143,7597 256 | Gigabit DK,ISP,signed + filtering,safe,60876,7680 257 | Iver Norge AS,ISP,,safe,49409,7878 258 | GTHost,cloud,filtering,partially safe,63023,7915 259 | Siamdata Communication,cloud,,unsafe,56309,7934 260 | Clearfly Communications,ISP,signed + filtering,safe,27400,7943 261 | Tech Futures,ISP,signed + filtering,safe,394256,8414 262 | Wikimedia Foundation,cloud,signed + filtering,safe,14907,8716 263 | ProveNET,ISP,,unsafe,263945,8791 264 | Cloud9,cloud,,unsafe,57814,8983 265 | Claro Brasil,ISP,,unsafe,28573,10205 266 | EE,ISP,,unsafe,12576,10206 267 | Plusnet,ISP,,unsafe,6871,10237 268 | TurkCell,ISP,,unsafe,16135,10254 269 | Free Mobile,ISP,,unsafe,51207,10266 270 | Hi3G,ISP,,unsafe,44034,10272 271 | T-Mobile Netherlands,ISP,,unsafe,31615,10273 272 | Taiwan Mobile,ISP,signed,unsafe,24158,10284 273 | Leaseweb USA-LAX-11,cloud,,unsafe,395954,10306 274 | TOPNET,ISP,,unsafe,37705,10329 275 | B2 Net Solutions,cloud,,unsafe,55286,10338 276 | Scaleway,cloud,signed + filtering,safe,12876,10343 277 | Webpass,ISP,,unsafe,19165,10352 278 | Turksat,ISP,,unsafe,47524,10355 279 | T-Mobile Thuis,ISP,signed,unsafe,50266,10362 280 | Globe Telecom,ISP,,unsafe,132199,10444 281 | Three UK,ISP,,unsafe,206067,10502 282 | University of North Carolina at Chapel Hill,ISP,,unsafe,36850,10541 283 | Leaseweb USA-SFO-12,cloud,,unsafe,7203,10618 284 | Smart Communications,ISP,,unsafe,10139,10625 285 | Leaseweb USA-SEA-10,cloud,,unsafe,396190,10900 286 | Leaseweb USA-WDC-01,cloud,,unsafe,30633,10901 287 | Millenicom,ISP,,unsafe,34296,10945 288 | Trustpower,ISP,started,unsafe,55850,11135 289 | NetCup,cloud,,unsafe,197540,11196 290 | Leaseweb USA-NYC-11,cloud,,unsafe,396362,12489 291 | Leaseweb USA-PHX-11,cloud,,unsafe,19148,12777 292 | A1 Hrvatska,ISP,,unsafe,29485,12837 293 | Wave G, ISP,,unsafe,54858,13010 294 | PROMAX,ISP,,unsafe,31423,13078 295 | Leaseweb USA-DAL-10,cloud,,unsafe,394380,13079 296 | CBN Broadband,ISP,started,unsafe,135478,13267 297 | Lanet Network,ISP,,unsafe,47800,13295 298 | EHOSTIDC,cloud,,unsafe,45382,13385 299 | Coextro,ISP,,unsafe,36445,13475 300 | Aktsiaselts WaveCom,cloud,,unsafe,34702,13703 301 | ThorDC,cloud,,unsafe,50613,13743 302 | ASERGO,cloud,signed + filtering,safe,30736,14022 303 | Leaseweb USA-MIA-11,cloud,,unsafe,393886,14245 304 | KemiNet,cloud,,unsafe,197706,14332 305 | Datapark,ISP,,unsafe,21040,14383 306 | Informacines sistemos ir technologijos UAB,cloud,,unsafe,61272,14606 307 | Web World Ireland,cloud,,unsafe,30900,14691 308 | Database By Design LLC,cloud,,unsafe,17090,15018 309 | Serverfield,cloud,,unsafe,134094,15153 310 | ELSERVER S.R.L,cloud,,unsafe,52270,15568 311 | volumedrive,cloud,filtering,partially safe,46664,15704 312 | MadeIT,cloud,filtering,partially safe,54455,16842 313 | Redder,ISP,signed + filtering,safe,33986,17220 314 | nobistech,cloud,,unsafe,15003,17342 315 | ENAHOST s.r.o.,cloud,,unsafe,201924,17977 316 | Dynamic Hosting,cloud,,unsafe,36077,18778 317 | Avative Fiber,ISP,,unsafe,394752,19128 318 | Green Mini host,cloud,signed + filtering,safe,205668,19229 319 | Globalhost d.o.o.,cloud,,unsafe,200698,19722 320 | FlokiNET,cloud,,unsafe,200651,20940 321 | Kviknet DK,ISP,signed + filtering,safe,204151,21707 322 | HQserv,cloud,,unsafe,42994,22744 323 | TL Group,cloud,,unsafe,263812,24357 324 | Pacswitch,ISP,filtering,partially safe,55536,27546 325 | AnacondaWeb,ISP,signed + filtering,safe,265656,32585 326 | Asimia Damaskou,cloud,,unsafe,205053,36355 327 | iServer-AS,cloud,,unsafe,57127,36355 328 | NUT HOST SRL,cloud,,unsafe,264649,36355 329 | SIA Bighost.lv,cloud,,unsafe,200709,42875 330 | Estoxy,cloud,,unsafe,208673,48965 331 | WhiteHat,ISP,signed + filtering,safe,51999,52250 332 | NETSTYLE A. LTD,cloud,,unsafe,43945,59342 333 | Galaxy Broadband,ISP,started,unsafe,139879,59342 334 | -------------------------------------------------------------------------------- /src/css/index.css: -------------------------------------------------------------------------------- 1 | @import "colors.css"; 2 | 3 | * { 4 | box-sizing: inherit; 5 | } 6 | 7 | html { 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | box-sizing: border-box; 11 | --border-radius: .3125em; 12 | --font-size: 18px; 13 | --line-height: 1.7; 14 | --color-rgb: var(--gray-1-rgb); 15 | --accent-color-rgb: var(--indigo-3-rgb); 16 | --column-width: 42rem; 17 | --column-inline-padding: 1rem; 18 | } 19 | 20 | @media (max-width: 1280px) { 21 | html { 22 | --font-size: 17px; 23 | } 24 | } 25 | 26 | @media (max-width: 768px) { 27 | html { 28 | --font-size: 16px; 29 | --line-height: 1.5; 30 | } 31 | } 32 | 33 | body { 34 | margin: 0; 35 | } 36 | 37 | 38 | [data-tooltip] { 39 | position: relative; 40 | } 41 | 42 | [data-tooltip]:hover::after { 43 | display: block; 44 | content: attr(title); 45 | position: absolute; 46 | left: 50%; 47 | bottom: 100%; 48 | padding: .3em 1em; 49 | transform: translate3d(-50%, 0, 0); 50 | margin-bottom: .5em; 51 | background: var(--gray-0); 52 | color: #fff; 53 | max-width: 60vw; 54 | white-space: nowrap; 55 | font-weight: normal; 56 | font-style: normal; 57 | text-shadow: none; 58 | text-transform: none; 59 | letter-spacing: normal; 60 | pointer-events: none; 61 | border-radius: var(--border-radius); 62 | z-index: 10; 63 | } 64 | 65 | 66 | summary { 67 | cursor: pointer; 68 | box-shadow: 0 0 0 var(--focus-size) var(--focus-color); 69 | transition: box-shadow .3s ease; 70 | } 71 | 72 | [js-focus-visible-polyfill-available] summary:focus { 73 | outline: none; 74 | } 75 | 76 | summary:not([is-focus-visible]) { 77 | --focus-size: 0em; 78 | } 79 | 80 | 81 | .Hero { 82 | background: var(--gray-9); 83 | } 84 | 85 | @media (min-width: 769px) { 86 | .Hero--actions { 87 | font-size: 1.1em; 88 | } 89 | } 90 | 91 | 92 | .Column { 93 | width: var(--column-width); 94 | max-width: 100%; 95 | margin-left: auto; 96 | margin-right: auto; 97 | padding-left: var(--column-inline-padding); 98 | padding-right: var(--column-inline-padding); 99 | } 100 | 101 | 102 | .Spacer { 103 | height: 3em; 104 | } 105 | 106 | @media (max-width: 414px) { 107 | .Spacer { 108 | height: 1.75em; 109 | } 110 | } 111 | 112 | 113 | .Stack { 114 | --gap: 2rem; 115 | } 116 | 117 | @media (max-width: 768px) { 118 | .Stack { 119 | --gap: 1.25rem; 120 | } 121 | } 122 | 123 | 124 | h1 { 125 | font-size: 3em; 126 | line-height: 1; 127 | margin: 0 0 .75em; 128 | text-align: center; 129 | } 130 | 131 | h1 em { 132 | color: var(--gray-4); 133 | } 134 | 135 | @media (max-width: 768px) { 136 | h1 { 137 | font-size: 6.5vw; 138 | } 139 | } 140 | 141 | @media (max-width: 576px) { 142 | h1 { 143 | font-size: 8vw; 144 | } 145 | } 146 | 147 | h1, h2 { 148 | color: var(--gray-0); 149 | } 150 | 151 | 152 | .Button { 153 | display: inline-flex; 154 | justify-content: center; 155 | align-items: center; 156 | padding-left: 1.1875em; 157 | padding-right: 1.1875em; 158 | } 159 | 160 | .Button-is-bordered { 161 | --color-rgb: var(--indigo-2-rgb); 162 | } 163 | 164 | .Button-is-gray { 165 | background: var(--gray-8); 166 | } 167 | 168 | .Button-is-question-mark { 169 | font-size: .9em; 170 | margin-left: .125em; 171 | padding: 0 .6em; 172 | border-radius: 999em; 173 | font-weight: 700; 174 | } 175 | 176 | .Button-is-slim { 177 | padding: .125em .5em; 178 | } 179 | 180 | 181 | .Test { 182 | position: relative; 183 | font-size: 1.1em; 184 | background: var(--gray-8); 185 | padding: .9em 1.2em; 186 | margin-top: 1em; 187 | } 188 | 189 | @media (max-width: 768px) { 190 | .Test { 191 | margin-left: -1rem; 192 | margin-right: -1rem; 193 | padding: .8em 1em; 194 | } 195 | } 196 | 197 | .Test:empty { 198 | display: none; 199 | } 200 | 201 | .Test::before { 202 | content: attr(data-type); 203 | display: block; 204 | font-size: .8em; 205 | font-weight: 600; 206 | text-transform: uppercase; 207 | letter-spacing: .02em; 208 | color: var(--gray-4); 209 | margin-bottom: .2em; 210 | } 211 | 212 | .Test[data-type="running"] { 213 | color: var(--gray-4); 214 | text-align: center; 215 | padding: 2.31em 1em; 216 | } 217 | 218 | .Test[data-type="running"]::before { 219 | display: none; 220 | } 221 | 222 | .Test[data-type="running"]::after { 223 | content: ""; 224 | position: absolute; 225 | top: 0; 226 | bottom: 0; 227 | margin-top: auto; 228 | margin-bottom: auto; 229 | left: calc(50% - 5em); 230 | width: .8em; 231 | height: .8em; 232 | border-radius: .5em; 233 | border: 2px solid transparent; 234 | border-top-color: currentColor; 235 | border-left-color: currentColor; 236 | animation: test-running .6s linear infinite; 237 | } 238 | 239 | @keyframes test-running { 240 | to { transform: rotate(360deg) } 241 | } 242 | 243 | .Test[data-type="success"] { 244 | border-left: 1em solid var(--green-5); 245 | } 246 | 247 | .Test[data-type="error"], 248 | .Test[data-type="failure"] { 249 | border-left: 1em solid var(--red-5); 250 | } 251 | 252 | .Test summary { 253 | font-size: .8em; 254 | margin: .4em 0 0; 255 | text-indent: .125em; 256 | } 257 | 258 | .Test pre { 259 | margin: .4em 0 0; 260 | font-size: .7em; 261 | line-height: 1.7; 262 | font-family: var(--monospace-font-family); 263 | padding: .8em 1em .6em; 264 | background: var(--gray-9); 265 | overflow-x: auto; 266 | } 267 | 268 | .Test pre::-webkit-scrollbar { 269 | height: 1em; 270 | } 271 | 272 | .Test pre::-webkit-scrollbar-track { 273 | background: none; 274 | border: none; 275 | } 276 | 277 | .Test pre::-webkit-scrollbar-thumb { 278 | min-width: 3.5em; 279 | background-color: var(--gray-6); 280 | background-clip: padding-box; 281 | border: .25em solid transparent; 282 | border-radius: .5em; 283 | box-shadow: none; 284 | } 285 | 286 | .Test pre::-webkit-scrollbar-thumb:active { 287 | background-color: var(--gray-4); 288 | } 289 | 290 | .Test pre > i { 291 | display: inline-block; 292 | font-style: normal; 293 | vertical-align: baseline; 294 | line-height: 1.3; 295 | margin-right: .5em; 296 | } 297 | 298 | .Test pre > i > i { 299 | display: inline-block; 300 | height: 1.3125em; 301 | width: 1.3125em; 302 | vertical-align: bottom; 303 | border-radius: .25em; 304 | } 305 | 306 | .Test pre > i[pass] > i { 307 | color: var(--green-4); 308 | } 309 | 310 | .Test pre > i[fail] > i { 311 | color: var(--red-4); 312 | } 313 | 314 | .Test pre > i > i svg { 315 | stroke: currentColor; 316 | stroke-width: 2.5px; 317 | fill: none; 318 | } 319 | 320 | 321 | .Markdown h2 { 322 | font-size: 2.5em; 323 | } 324 | 325 | @media (max-width: 768px) { 326 | .Markdown h2 { 327 | font-size: 2em; 328 | } 329 | } 330 | 331 | @media (max-width: 374px) { 332 | .Markdown h2 { 333 | font-size: 1.75em; 334 | } 335 | } 336 | 337 | .Markdown h2[id]::before, 338 | .Markdown details[id]::before { 339 | content: ""; 340 | display: block; 341 | padding-top: 1.5em; 342 | margin-top: -1.5em; 343 | visibility: hidden; 344 | pointer-events: none; 345 | } 346 | 347 | .Markdown h2.Markdown--h2-as-h3 { 348 | font-size: 1.6em; 349 | } 350 | 351 | .Markdown h2.Markdown--h2-as-h3:not(:last-child) { 352 | margin-bottom: .25em; 353 | } 354 | 355 | 356 | .InlineSafeHighlight { 357 | color: var(--status-green-fg); 358 | background: var(--status-green-bg); 359 | padding-left: .2em; 360 | padding-right: .2em; 361 | } 362 | 363 | @media (min-width: 769px) { 364 | .InlineSafeHighlight { 365 | border-radius: .15em; 366 | } 367 | } 368 | 369 | 370 | .TableHeader { 371 | padding-bottom: .125em; 372 | border-bottom: 1px solid var(--gray-7); 373 | margin-bottom: 1.25em; 374 | } 375 | 376 | .TableHeader--subheader { 377 | display: flex; 378 | } 379 | 380 | @media (max-width: 374px) { 381 | .TableHeader--info { 382 | font-size: .9em; 383 | } 384 | } 385 | 386 | .TableHeader--actions { 387 | font-size: .85em; 388 | margin-left: auto; 389 | } 390 | 391 | .TableHeader--actions > * + * { 392 | margin-left: .5em; 393 | } 394 | 395 | @media (max-width: 960px) { 396 | .TableHeader--actions [large-desktop-only] { 397 | display: none; 398 | } 399 | } 400 | 401 | @media (max-width: 319px) { 402 | .TableHeader--actions { 403 | display: none; 404 | } 405 | } 406 | 407 | .BGPSafetyTable { 408 | --cell-horizontal-padding: 1.1rem; 409 | --column-table-width: calc(var(--column-width) - 2 * var(--column-inline-padding) + 2 * var(--cell-horizontal-padding)); 410 | 411 | table-layout: fixed; 412 | width: var(--column-table-width); 413 | --max-width: var(--column-table-width); 414 | margin-left: auto; 415 | margin-right: auto; 416 | border-collapse: collapse; 417 | border-spacing: 0; 418 | border: 0; 419 | 420 | --type-column-width: 6rem; 421 | --details-column-width: 14rem; 422 | --status-column-width: 8rem; 423 | --asn-column-width: 7rem; 424 | 425 | --non-name-columns-width-sum: calc( 426 | var(--type-column-width) + 427 | var(--details-column-width) + 428 | var(--status-column-width) + 429 | var(--asn-column-width) 430 | ); 431 | } 432 | 433 | .BGPSafetyTable[data-show-all-rows="true"] { 434 | width: auto; 435 | max-width: calc(100vw - 5em); 436 | min-width: var(--column-table-width); 437 | } 438 | 439 | .BGPSafetyTable [data-column="type"] { width: var(--type-column-width) } 440 | .BGPSafetyTable [data-column="details"] { width: var(--details-column-width) } 441 | .BGPSafetyTable [data-column="status"] { width: var(--status-column-width) } 442 | .BGPSafetyTable [data-column="asn"] { width: var(--asn-column-width) } 443 | 444 | @media (min-width: 961px) { 445 | .BGPSafetyTable[data-show-all-rows="true"] { 446 | --max-width: 100vw; 447 | } 448 | } 449 | 450 | @media (max-width: 960px) { 451 | .BGPSafetyTable { 452 | --max-width: 100vw; 453 | } 454 | } 455 | 456 | @media (max-width: 768px) { 457 | .BGPSafetyTable { 458 | --cell-horizontal-padding: 1rem; 459 | } 460 | } 461 | 462 | @media (max-width: 672px) { /* var(--column-width) in px */ 463 | .BGPSafetyTable { 464 | --column-table-width: 100vw; 465 | } 466 | } 467 | 468 | .BGPSafetyTable[data-hide-asn-column="true"] [data-column="asn"] { 469 | display: none; 470 | } 471 | 472 | @media (min-width: 961px) { 473 | .BGPSafetyTable[data-hide-asn-column="true"] { 474 | --asn-column-width: 0rem; 475 | } 476 | 477 | .BGPSafetyTable[data-show-all-rows="false"][data-hide-asn-column="false"] { 478 | width: calc(var(--column-table-width) + var(--asn-column-width)); 479 | --max-width: calc(var(--column-table-width) + var(--asn-column-width)); 480 | } 481 | } 482 | 483 | .BGPSafetyTable th { 484 | font-size: .8em; 485 | font-weight: 600; 486 | text-transform: uppercase; 487 | letter-spacing: .02em; 488 | color: var(--gray-3); 489 | vertical-align: bottom; 490 | white-space: nowrap; 491 | } 492 | 493 | @supports ((-webkit-backdrop-filter: blur(1em)) or (backdrop-filter: blur(1em))) { 494 | .BGPSafetyTable th { 495 | position: sticky; 496 | top: 0; 497 | background: rgba(var(--background-color-rgb), .8); 498 | -webkit-backdrop-filter: saturate(200%) blur(1.25em); 499 | backdrop-filter: saturate(200%) blur(1.25em); 500 | z-index: 1; 501 | } 502 | 503 | .BGPSafetyTable th:first-child { 504 | box-shadow: -1em 0 var(--background-color); 505 | } 506 | } 507 | 508 | .BGPSafetyTable th:not([data-sortable="false"]) { 509 | -webkit-user-select: none; 510 | user-select: none; 511 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 512 | -webkit-touch-callout: none; 513 | touch-action: manipulation; 514 | cursor: pointer; 515 | } 516 | 517 | .BGPSafetyTable th:not([data-sortable="false"]) > span { 518 | display: block; 519 | position: relative; 520 | padding-right: 1.75em; 521 | } 522 | 523 | .BGPSafetyTable th:not([data-sortable="false"]) > span::after { 524 | content: ""; 525 | position: absolute; 526 | right: 0; 527 | top: 0; 528 | height: 0; 529 | width: 0; 530 | border-width: 7px; 531 | border-style: solid; 532 | border-color: transparent; 533 | margin-left: 10px; 534 | visibility: hidden; 535 | } 536 | 537 | .BGPSafetyTable th[data-sorted="true"] > span::after { 538 | visibility: visible; 539 | } 540 | 541 | .BGPSafetyTable th[data-sorted-direction="descending"] > span::after { 542 | border-top-color: currentColor; 543 | top: .55em; 544 | } 545 | 546 | .BGPSafetyTable th[data-sorted-direction="ascending"] > span::after { 547 | border-bottom-color: currentColor; 548 | top: .1em; 549 | } 550 | 551 | @media (max-width: 768px) { 552 | .BGPSafetyTable th[data-sorted-direction="descending"] > span::after { 553 | top: .45em; 554 | } 555 | 556 | .BGPSafetyTable th[data-sorted-direction="ascending"] > span::after { 557 | top: 0; 558 | } 559 | } 560 | 561 | .BGPSafetyTable th, 562 | .BGPSafetyTable td { 563 | padding: .2rem var(--cell-horizontal-padding); 564 | text-align: left; 565 | } 566 | 567 | .BGPSafetyTable td { 568 | vertical-align: top; 569 | white-space: nowrap; 570 | } 571 | 572 | .BGPSafetyTable [data-column="asn"] { 573 | text-align: right; 574 | } 575 | 576 | .BGPSafetyTable tr { 577 | white-space: nowrap; 578 | } 579 | 580 | .BGPSafetyTable[data-show-all-rows="false"] tr[data-is-major="false"] { 581 | display: none; 582 | } 583 | 584 | .BGPSafetyTable tbody { 585 | letter-spacing: -.01em; 586 | } 587 | 588 | .BGPSafetyTable[data-show-all-rows="true"] tbody tr:nth-child(even) { 589 | background: rgba(var(--gray-9-rgb), .9); 590 | } 591 | 592 | .BGPSafetyTable tbody tr:not(:last-child) td { 593 | border-bottom: 1px solid var(--gray-8); 594 | } 595 | 596 | .BGPSafetyTable tbody tr[data-status="safe"]:not(:last-child) td[data-column="status"] { 597 | border-bottom-color: rgba(var(--green-2-rgb), .08); 598 | } 599 | 600 | .BGPSafetyTable tbody tr[data-status="partially-safe"]:not(:last-child) td[data-column="status"] { 601 | border-bottom-color: rgba(var(--yellow-2-rgb), .08); 602 | } 603 | 604 | .BGPSafetyTable tbody tr[data-status="unsafe"]:not(:last-child) td[data-column="status"] { 605 | border-bottom-color: rgba(var(--red-2-rgb), .08); 606 | } 607 | 608 | .BGPSafetyTable td[data-column="name"] { 609 | letter-spacing: -.03em; 610 | } 611 | 612 | .BGPSafetyTable td[data-column="name"] > span { 613 | display: block; 614 | -webkit-mask-image: linear-gradient(to right, #000, #000 calc(100% - 1em), transparent); 615 | mask-image: linear-gradient(to right, #000, #000 calc(100% - 1em), transparent); 616 | max-width: calc(var(--max-width) - 2 * var(--cell-horizontal-padding) - var(--non-name-columns-width-sum)); 617 | padding-right: 1em; 618 | margin-right: -1em; 619 | } 620 | 621 | .BGPSafetyTable td[data-value="signed + filtering peers only"] { 622 | font-size: .95em; 623 | letter-spacing: -.03em; 624 | } 625 | 626 | .BGPSafetyTable td[data-column="status"] { 627 | font-weight: 500; 628 | letter-spacing: -.02em; 629 | } 630 | 631 | .BGPSafetyTable [data-status="safe"] td:first-child { 632 | box-shadow: -5px 0 var(--green-4); 633 | } 634 | 635 | .BGPSafetyTable [data-status="partially-safe"] td:first-child { 636 | box-shadow: -5px 0 var(--yellow-4); 637 | } 638 | 639 | .BGPSafetyTable [data-status="unsafe"] td:first-child { 640 | box-shadow: -5px 0 var(--red-4); 641 | } 642 | 643 | .BGPSafetyTable [data-status="safe"] td[data-column="status"] { 644 | color: var(--status-green-fg); 645 | background: var(--status-green-bg); 646 | } 647 | 648 | .BGPSafetyTable [data-status="partially-safe"] td[data-column="status"] { 649 | color: var(--status-yellow-fg); 650 | background: var(--status-yellow-bg); 651 | } 652 | 653 | .BGPSafetyTable [data-status="unsafe"] td[data-column="status"] { 654 | color: var(--status-red-fg); 655 | background: var(--status-red-bg); 656 | } 657 | 658 | @media (max-width: 960px) { 659 | .BGPSafetyTable { 660 | --asn-column-width: 0rem; 661 | } 662 | 663 | .BGPSafetyTable [data-column="asn"] { 664 | display: none; 665 | } 666 | } 667 | 668 | @media (max-width: 768px) { 669 | .BGPSafetyTable { 670 | --details-column-width: 0rem; 671 | } 672 | 673 | .BGPSafetyTable [data-column="details"] { 674 | display: none; 675 | } 676 | } 677 | 678 | @media (max-width: 576px) { 679 | .BGPSafetyTable { 680 | --type-column-width: 0rem; 681 | } 682 | 683 | .BGPSafetyTable [data-column="type"] { 684 | display: none; 685 | } 686 | } 687 | 688 | .BGPSafetyTable---footer { 689 | display: flex; 690 | font-size: .85em; 691 | padding-top: 1em; 692 | opacity: .8; 693 | } 694 | 695 | 696 | @media (max-width: 960px) { 697 | .BodyWithDiagrams { 698 | display: none; 699 | } 700 | } 701 | 702 | 703 | .Diagram { 704 | position: relative; 705 | --grid-line-color: rgba(var(--gray-8-rgb), .8); 706 | --grid-line-thickness: 1px; 707 | --grid-size: 2rem; 708 | --grid-height: 20rem; 709 | --grid-width: 60rem; 710 | --grid-positions-count: 5; 711 | --grid-positions-unit: calc(var(--grid-width) / (var(--grid-positions-count) + 1)); 712 | height: calc(var(--grid-height) + var(--grid-line-thickness)); 713 | margin: 2em auto; 714 | width: 100%; 715 | overflow: hidden; 716 | user-select: none; 717 | } 718 | 719 | .Diagram--surface { 720 | position: absolute; 721 | top: 0; 722 | bottom: 0; 723 | left: calc(50vw - var(--grid-width) / 2); 724 | width: calc(var(--grid-width) + var(--grid-line-thickness)); 725 | background-image: 726 | linear-gradient( 727 | to bottom, 728 | var(--grid-line-color) var(--grid-line-thickness), 729 | transparent var(--grid-line-thickness) 730 | ), 731 | linear-gradient( 732 | to right, 733 | var(--grid-line-color) var(--grid-line-thickness), 734 | transparent var(--grid-line-thickness) 735 | ); 736 | background-size: var(--grid-size) var(--grid-size); 737 | } 738 | 739 | .Diagram--header { 740 | position: absolute; 741 | left: var(--grid-positions-unit); 742 | top: var(--grid-size); 743 | font-size: 1.25em; 744 | line-height: var(--grid-size); 745 | margin: 0 0 0 .25em; 746 | } 747 | 748 | .Diagram--header [unsafe], 749 | .Diagram--header [safe] { 750 | padding-left: .2em; 751 | padding-right: .2em; 752 | border-radius: .15em; 753 | } 754 | 755 | .Diagram--header [unsafe] { 756 | color: var(--status-red-fg); 757 | background: var(--status-red-bg); 758 | } 759 | 760 | .Diagram--header [safe] { 761 | color: var(--status-green-fg); 762 | background: var(--status-green-bg); 763 | } 764 | 765 | .Diagram [position="1"] { left: calc(var(--grid-positions-unit) * 1); } 766 | .Diagram [position="2"] { left: calc(var(--grid-positions-unit) * 2); } 767 | .Diagram [position="3"] { left: calc(var(--grid-positions-unit) * 3); } 768 | .Diagram [position="4"] { left: calc(var(--grid-positions-unit) * 4); } 769 | .Diagram [position="5"] { left: calc(var(--grid-positions-unit) * 5); } 770 | 771 | .Diagram--node { 772 | position: absolute; 773 | top: calc(var(--grid-height) / 2); 774 | --background: var(--gray-4); 775 | transition: opacity .65s ease, transform .65s ease; 776 | } 777 | 778 | .Diagram[data-js-diagram="unsafe"] .Diagram--node[path="sad"] { 779 | --background: var(--red-4); 780 | } 781 | 782 | .Diagram[path="sad"] .Diagram--node[path="sad"] { 783 | transform: translate3d(0, calc(2 * var(--grid-size)), 0); 784 | } 785 | 786 | .Diagram[path="sad"] .Diagram--node[path="happy"] { 787 | transform: translate3d(0, calc(-2 * var(--grid-size)), 0); 788 | } 789 | 790 | .Diagram--node-circle { 791 | position: absolute; 792 | --size: 1.5em; 793 | width: var(--size); 794 | height: var(--size); 795 | left: calc(-1 * var(--size) / 2); 796 | top: calc(-1 * var(--size) / 2); 797 | background: var(--background); 798 | border: .25em solid #fff; 799 | border-radius: 100%; 800 | } 801 | 802 | .Diagram[path="sad"] .Diagram--node[path="happy"] { 803 | transform: translate3d(0, calc(-2 * var(--grid-size)), 0); 804 | } 805 | 806 | .Diagram--node-icon { 807 | position: absolute; 808 | top: -68px; 809 | width: 73px; 810 | height: 50px; 811 | margin-left: -36px; 812 | --color: var(--gray-4); 813 | --background: #fff; 814 | transition: opacity .65s ease; 815 | } 816 | 817 | .Diagram--node-label { 818 | position: absolute; 819 | top: calc(var(--grid-size) / 2); 820 | left: calc(-1 * var(--grid-positions-unit) / 2); 821 | right: calc(-1 * var(--grid-positions-unit) / 2); 822 | text-align: center; 823 | line-height: var(--grid-size); 824 | text-shadow: 0 0 .5em #fff, 0 0 .5em #fff, 0 0 .25em #fff; 825 | transition: opacity .65s ease; 826 | } 827 | 828 | .Diagram[path="happy"] .Diagram--node[path="sad"] .Diagram--node-icon, 829 | .Diagram[path="happy"] .Diagram--node[path="sad"] .Diagram--node-label { 830 | opacity: 0; 831 | } 832 | 833 | .Diagram[data-js-diagram="unsafe"][path="sad"] .Diagram--node[path="happy"] .Diagram--node-icon, 834 | .Diagram[data-js-diagram="unsafe"][path="sad"] .Diagram--node[path="happy"] .Diagram--node-label, 835 | .Diagram[data-js-diagram="unsafe"][path="sad"] .Diagram--node[path="both"][position="5"] .Diagram--node-icon, 836 | .Diagram[data-js-diagram="unsafe"][path="sad"] .Diagram--node[path="both"][position="5"] .Diagram--node-label { 837 | opacity: .4; 838 | } 839 | 840 | .Diagram--edge { 841 | position: absolute; 842 | top: calc(var(--grid-height) / 2); 843 | --background: var(--gray-7); 844 | --line-thickness: 6px; 845 | --delay: 0s; 846 | transition: opacity .65s ease, transform .65s ease; 847 | } 848 | 849 | .Diagram [position="2"] { --delay: 0.75s } 850 | .Diagram [position="3"] { --delay: 1.50s } 851 | .Diagram [position="4"] { --delay: 2.25s } 852 | .Diagram [position="5"] { --delay: 3.00s } 853 | 854 | .Diagram--edge-line { 855 | position: absolute; 856 | top: 50%; 857 | width: calc(2 * var(--line-thickness) + var(--grid-positions-unit)); 858 | height: var(--line-thickness); 859 | left: calc(-1 * var(--line-thickness)); 860 | top: calc(-1 * var(--line-thickness) / 2); 861 | border-radius: 99em; 862 | background: var(--background); 863 | } 864 | 865 | .Diagram[data-js-diagram="unsafe"] .Diagram--edge[path="sad"], 866 | .Diagram[data-js-diagram="unsafe"] .Diagram--edge[with-status-color] { 867 | --background: #f5ced2; 868 | } 869 | 870 | .Diagram[data-js-diagram="safe"] .Diagram--edge[with-status-color] { 871 | --background: #a3e0a6; 872 | } 873 | 874 | .Diagram[path="happy"] .Diagram--edge[path="switch"][with-status-color], 875 | .Diagram[path="sad"] .Diagram--edge[path="switch"]:not([with-status-color]) { 876 | opacity: 0; 877 | } 878 | 879 | .Diagram[data-js-diagram="unsafe"][path="sad"] .Diagram--edge[path="switch"] { 880 | transform: translate3d(0, 0, 0) rotate(22deg); 881 | } 882 | 883 | .Diagram[data-js-diagram="safe"][path="sad"] .Diagram--edge[path="switch"] { 884 | transform: translate3d(0, 0, 0) rotate(-22deg); 885 | } 886 | 887 | .Diagram[path="sad"] .Diagram--edge[path="sad"] { 888 | transform: translate3d(0, calc(2 * var(--grid-size)), 0); 889 | } 890 | 891 | .Diagram[path="sad"] .Diagram--edge[path="happy"] { 892 | transform: translate3d(0, calc(-2 * var(--grid-size)), 0); 893 | } 894 | 895 | .Diagram[path="sad"] .Diagram--edge[path="happy"][position="4"] { 896 | transform: translate3d(0, calc(-2 * var(--grid-size)), 0) rotate(22deg); 897 | } 898 | 899 | .Diagram--edge-data { 900 | position: absolute; 901 | height: 10px; 902 | width: 20px; 903 | background: var(--gray-0); 904 | border-radius: 2px; 905 | margin-left: -10px; 906 | top: -5px; 907 | left: 0; 908 | animation: diagram-edge-data-pulse 5s var(--delay) linear infinite; 909 | } 910 | 911 | .Diagram[data-js-diagram="unsafe"][path="happy"] .Diagram--edge[path="sad"] .Diagram--edge-data, 912 | .Diagram[data-js-diagram="unsafe"][path="sad"] .Diagram--edge[path="happy"] .Diagram--edge-data, 913 | .Diagram[data-js-diagram="safe"] .Diagram--edge[path="sad"] .Diagram--edge-data { 914 | visibility: hidden; 915 | } 916 | 917 | @keyframes diagram-edge-data-pulse { 918 | 0% { 919 | opacity: 0; 920 | transform: translate3d(0, 0, 0) scale(.5); 921 | } 922 | 923 | 3% { 924 | opacity: 1; 925 | transform: translate3d(calc(var(--grid-positions-unit) * .2), 0, 0) scale(1); 926 | } 927 | 928 | 12.5% { 929 | opacity: 1; 930 | transform: translate3d(calc(var(--grid-positions-unit) * .8), 0, 0) scale(1); 931 | } 932 | 933 | 16%, 100% { 934 | opacity: 0; 935 | transform: translate3d(var(--grid-positions-unit), 0, 0) scale(.5); 936 | } 937 | } 938 | 939 | .Diagram---footer { 940 | text-align: center; 941 | } 942 | 943 | 944 | .FAQItem summary { 945 | font-size: 1.5em; 946 | padding: 0 .6em; 947 | line-height: 1.75; 948 | background: var(--gray-8); 949 | color: var(--gray-4); 950 | border-radius: var(--border-radius); 951 | } 952 | 953 | .FAQItem summary span { 954 | font-weight: 600; 955 | color: var(--gray-0); 956 | } 957 | 958 | .FAQItem { 959 | margin-bottom: 1.5em; 960 | } 961 | 962 | .FAQItem p { 963 | padding-left: 2.7em; 964 | } 965 | 966 | @media (max-width: 413px) { 967 | .FAQItem summary { 968 | font-size: 1.2em; 969 | } 970 | 971 | .FAQItem p { 972 | padding-left: 0; 973 | } 974 | } 975 | 976 | .FAQItem[open] { 977 | margin-bottom: 2.5em; 978 | } 979 | 980 | .FAQItem[open] summary { 981 | margin-bottom: .5em; 982 | } 983 | 984 | 985 | .Footer { 986 | background: var(--gray-9); 987 | } 988 | 989 | .Footer--content { 990 | display: flex; 991 | flex-direction: column; 992 | align-items: center; 993 | justify-content: center; 994 | } 995 | 996 | .Footer--logo-link { 997 | display: block; 998 | height: 2.4em; 999 | width: 4.8em; 1000 | margin-bottom: .75em; 1001 | } 1002 | 1003 | .Footer--logo { 1004 | display: block; 1005 | height: 100%; 1006 | width: 100%; 1007 | } 1008 | 1009 | .Footer--legal { 1010 | text-align: center; 1011 | font-size: .8em; 1012 | margin-bottom: 1em; 1013 | } 1014 | 1015 | 1016 | .PageNotFound { 1017 | display: flex; 1018 | align-items: center; 1019 | justify-content: center; 1020 | flex-direction: column; 1021 | height: 100%; 1022 | width: 100%; 1023 | padding: 1em 3em 5em; 1024 | text-align: center; 1025 | } 1026 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Is BGP safe yet? · Cloudflare 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 | 41 |
42 |

Is BGP safe yet?

43 | 44 |
45 |
46 |

Border Gateway Protocol (BGP) is the postal service of the Internet. It’s responsible for looking at all of the available paths that data could travel and picking the best route.

47 | 48 |

Unfortunately, it isn’t secure, and there have been some major Internet disruptions as a result. But fortunately there is a way to make it secure.

49 | 50 |

ISPs and other major Internet players (Comcast, Sprint, Verizon, and others) would need to implement a certification system, called RPKI.

51 |
52 | 53 |
54 |
55 | 56 | Read FAQ 57 |
58 |
59 | 60 |
61 |
62 |
63 | 64 |
65 |
66 | 67 |
68 |
69 | 70 |
71 |

Latest updates

72 | 73 | 95 |
96 | 97 |
98 | 99 |
100 |
101 |

Status

102 |
103 | 104 |
105 |
106 |
107 |

Displaying major operators

108 |
109 |
110 | 111 |
112 | 113 | 114 |
115 |
116 |
117 |
118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
NameTypeDetailsStatusASN ?
130 | 131 |
132 | 137 | 138 |
139 |
140 | 141 |
142 |
143 | 144 |
145 |
146 |

What’s a BGP hijack?

147 | 148 |

To better understand why BGP’s lack of security is so problematic, let’s look at a simplified model of how BGP is used to route Internet packets.

149 | 150 |

The Internet is not run by just one company. It’s made up of thousands of autonomous systems with nodes located all around the world, connected to each other in a massive graph.

151 | 152 |

In essence, the way BGP works is that each node must determine how to route packets using only what it knows from the nodes it connects with directly.

153 | 154 |

For example, in the simple network A–B–C–D–E, the node A only knows how to reach E based on information it received from B. The node B knows about the network from A and C. And so forth.

155 | 156 |

A BGP hijack occurs when a malicious node deceives another node, lying about what the routes are for its neighbors. Without any security protocols, this misinformation can propagate from node to node, until a large number of nodes now know about, and attempt to use these incorrect, nonexistent, or malicious routes.

157 | 158 |

Click “Hijack the request” to visualize how packets are re-routed:

159 |
160 |
161 | 162 |
163 |
164 |

Unsafe BGP: Normal request

165 | 166 |
167 |
168 |
169 |
170 | 171 |
172 |
173 |
174 |
175 | 176 |
177 |
178 |
179 |
180 | 181 |
182 |
183 |
184 |
185 | 186 |
187 |
188 |
189 |
190 | 191 |
192 |
193 |
194 |
195 | 196 |
197 |
198 |
199 |
Laptop
200 |
201 | 202 |
203 |
204 |
205 |
ISP
206 |
207 | 208 |
209 |
210 |
Hijacker
211 |
212 | 213 |
214 |
215 |
216 |
Transit
217 |
218 | 219 |
220 |
221 |
Malicious website
222 |
223 | 224 |
225 |
226 |
227 |
Cloud
228 |
229 | 230 |
231 |
232 |
233 |
Web resource
234 |
235 |
236 |
237 | 238 | 241 | 242 |
243 | 244 |
245 |
246 |

In order to make BGP safe, we need some way of preventing the spread of this misinformation. Since the Internet is so open and distributed, we can’t prevent malicious nodes from attempting to deceive other nodes in the first place. So instead we need to give nodes the ability to validate the information they receive, so they can reject these undesired routes on their own.

247 | 248 |

Enter Resource Public Key Infrastructure (RPKI), a security framework method that associates a route with an autonomous system. It gets a little technical, but the basic idea is that RPKI uses cryptography to provide nodes with a way of doing this validation.

249 | 250 |

With RPKI enabled, let’s see what happens to packets after an attemped BGP hijack. Click “Attempt to hijack” to visualize how RPKI allows the network to protect itself by invalidating the malicious routes:

251 |
252 |
253 | 254 |
255 |
256 |

Safe BGP with RPKI

257 | 258 |
259 |
260 |
261 |
262 | 263 |
264 |
265 |
266 |
267 | 268 |
269 |
270 |
271 |
272 | 273 |
274 |
275 |
276 |
277 | 278 |
279 |
280 |
281 |
282 | 283 |
284 |
285 |
286 |
287 | 288 |
289 |
290 |
291 |
Laptop
292 |
293 | 294 |
295 |
296 |
297 |
ISP
298 |
299 | 300 |
301 |
302 |
Hijacker
303 |
304 | 305 |
306 |
307 |
308 |
Transit
309 |
310 | 311 |
312 |
313 |
Malicious website
314 |
315 | 316 |
317 |
318 |
319 |
Cloud
320 |
321 | 322 |
323 |
324 |
325 |
Web resource
326 |
327 |
328 |
329 | 330 | 333 | 334 |
335 |
336 |
337 | 338 |
339 |
340 |
341 |

FAQ

342 | 343 |
344 | What is BGP? 345 |

Border Gateway Protocol (BGP) is the postal service of the Internet. When someone drops a letter into a mailbox, the postal service processes that piece of mail and chooses a fast, efficient route to deliver that letter to its recipient. Similarly, when someone submits data across the Internet, BGP is responsible for looking at all of the available paths that data could travel and picking the best route, which usually means hopping between autonomous systems. Learn more →

346 |
347 | 348 |
349 | Why is BGP unsafe? 350 |

By default, BGP does not embed any security protocols. It is up to every autonomous system to implement filtering of “wrong routes”. Leaking routes can break parts of the Internet by making them unreachable. It is commonly the result of misconfigurations. Although, it is not always accidental. A practice called BGP hijack consists of redirecting traffic to another autonomous system to steal information (via phishing, or passive listening for instance).

351 |

BGP can be made safe if all autonomous systems (AS) only announce legitimate routes. A route is defined as legitimate when the owner of the resource allows its announcement. Filters need to be built in order to make sure only legitimate routes are accepted. There are a few approaches for BGP route validation which vary in degrees of trustability and efficiency. A mature implementation is RPKI.

352 |
353 | 354 |
355 | What is RPKI? 356 |

With 800k+ routes on the Internet, it is impossible to check them manually. Resource Public Key Infrastructure (RPKI) is a security framework method that associates a route with an autonomous system. It uses cryptography in order to validate the information before being passed onto the routers. You can read more about RPKI on the Cloudflare blog.

357 |

On May 14th, Job Snijders from NTT will present a free RPKI 101 webinar.

358 |
359 | 360 |
361 | How does the test work? 362 |

In order to test if your ISP is implementing BGP safely, we announce a legitimate route but we make sure the announcement is invalid. If you can load the website we host on that route, that means the invalid route was accepted by your ISP. A leaked or a hijacked route would likely be accepted too.

363 |
364 | 365 |
366 | Can even more be done? 367 |

Over the years, network operators and developers started working groups to design and deploy standards to overcome unsafe routing protocols. Cloudflare recently joined a global initiative called Mutually Agreed Norms for Routing Security (MANRS). It’s a community of security-minded organizations committed to making routing infrastructure more robust and secure, and members agree to implement filtering mechanisms. New voices are always appreciated.

368 |
369 | 370 |
371 | What can you do? 372 |

Share this page. For BGP to be safe, all of the major ISPs will need to embrace RPKI. Sharing this page will increase awareness of the problem which can ultimately pressure ISPs into implementing RPKI for the good of themselves and the general public. You can also reach out to your service provider or hosting company directly and ask them to deploy RPKI and join MANRS. When the Internet is safe, everybody wins.

373 |
374 |
375 | 376 |
377 | 380 |
381 |
382 |
383 | 384 |
385 |
386 | 387 | 408 | 409 | 410 | --------------------------------------------------------------------------------