Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
be75ab83f2
|
|||
|
ccd54a93f0
|
|||
|
81a7426e51
|
|||
|
ee40371272
|
|||
|
838f9e970a
|
|||
|
b1b0757e0d
|
|||
|
4a062a4dae
|
|||
|
857c39fdbb
|
|||
|
0326c60f90
|
|||
|
f8a00dec1b
|
|||
|
ef975d1b98
|
|||
|
d51d43d8a7
|
|||
|
b69e68b04b
|
|||
|
cfdf9083ef
|
|||
|
8c6c0e6aa2
|
|||
|
8440ba4cf8
|
|||
|
28f91eeecb
|
|||
|
61652d1e85
|
|||
|
4218f8b5bf
|
|||
|
1aae89ed08
|
|||
|
dae670f2b5
|
|||
|
413a4746cd
|
|||
|
11cb80cf7c
|
|||
|
f984f6d47c
|
|||
|
d2f4907fcd
|
|||
|
c1ec450c33
|
+3
-1
@@ -25,9 +25,11 @@ build:
|
||||
- "apk add sqlite-static"
|
||||
- "shards install"
|
||||
- "crystal build --release --static src/access_log.cr"
|
||||
- "strip --strip-all access_log"
|
||||
- "crystal build --release --static src/ip2asn_server.cr"
|
||||
- "strip --strip-all access_log ip2asn_server"
|
||||
- "install -Dm 644 LICENSE ${RELEASE_DIRECTORY}/LICENSE"
|
||||
- "install -Dm 755 access_log ${RELEASE_DIRECTORY}/access_log"
|
||||
- "install -Dm 755 ip2asn_server ${RELEASE_DIRECTORY}/ip2asn_server"
|
||||
- "tar -cf ${RELEASE_TARBALL} ${RELEASE_DIRECTORY}"
|
||||
artifacts:
|
||||
paths:
|
||||
|
||||
@@ -195,6 +195,18 @@ Then run the program with the required flags enabled:
|
||||
access_log --swd --device-country
|
||||
```
|
||||
|
||||
## ASN database
|
||||
|
||||
If you want to keep track of ASN for each visitor, for instance for
|
||||
grouping possible attacks or IA crawls, create a database based on
|
||||
<https://iptoasn.com/>:
|
||||
|
||||
```bash
|
||||
./contrib/asn_database.sh
|
||||
```
|
||||
|
||||
And start the server with the `--asn-database=` flag.
|
||||
|
||||
## Crawler user agents
|
||||
|
||||
Download the [crawler user agents
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
db="${1:-$PWD/asn.sqlite3}"
|
||||
sqlite3 "$db" "CREATE TABLE IF NOT EXISTS asn (range_start INTEGER, range_end INTEGER, id INTEGER, country STRING, description TEXT);"
|
||||
sqlite3 "$db" "CREATE INDEX IF NOT EXISTS asn_id_idx ON asn (id);"
|
||||
sqlite3 "$db" "CREATE TABLE IF NOT EXISTS status (id INTEGER PRIMARY KEY, blocked BOOLEAN DEFAULT FALSE);"
|
||||
sqlite3 "$db" "DELETE from asn;"
|
||||
wget https://iptoasn.com/data/ip2asn-v4-u32.tsv.gz -O - | gunzip | sqlite3 -tabs "$db" ".import '|cat -' asn"
|
||||
|
||||
echo "ASN database created/updated at $db"
|
||||
+4
-1
@@ -53,7 +53,10 @@ CREATE TABLE IF NOT EXISTS "access_logs" (
|
||||
"network_co2" float DEFAULT NULL,
|
||||
"consumer_device_co2" float DEFAULT NULL,
|
||||
"production_co2" float DEFAULT NULL,
|
||||
"total_co2" float DEFAULT NULL
|
||||
"total_co2" float DEFAULT NULL,
|
||||
"node" varchar DEFAULT NULL,
|
||||
"asn" integer DEFAULT NULL,
|
||||
"http3" varchar(2) DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "index_access_logs_on_host" ON "access_logs" ("host");
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
keyval_zone_redis zone=ip2asn hostname=::1;
|
||||
keyval "$remote_addr:id" $asn zone=ip2asn;
|
||||
keyval "$remote_addr:blocked" $asn_blocked zone=ip2asn;
|
||||
|
||||
limit_conn_zone $asn zone=asn_conn:10m;
|
||||
limit_req_zone $asn zone=asn_req:10m rate=10r/s;
|
||||
|
||||
server {
|
||||
server_name _;
|
||||
|
||||
listen 80 default_server;
|
||||
|
||||
if ($asn_blocked) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
limit_conn asn_conn 1;
|
||||
limit_req zone=asn_req nodelay;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "access_log"
|
||||
version: "0.5.9"
|
||||
version: "0.6.1"
|
||||
authors:
|
||||
- "f <f@sutty.nl>"
|
||||
targets:
|
||||
|
||||
+5
-5
@@ -111,31 +111,31 @@ describe SWD do
|
||||
|
||||
describe "#consumer_device_co2" do
|
||||
it "should extract the consumer device co2 emissions" do
|
||||
swd.consumer_device_co2.should(eq 0.6343432152938272)
|
||||
swd.consumer_device_co2.should(eq 0.6854924454444445)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#production_co2" do
|
||||
it "should extract the production co2 emissions" do
|
||||
swd.production_co2.should(eq 0.2317792517419753)
|
||||
swd.production_co2.should(eq 0.2504683935277778)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#network_co2" do
|
||||
it "should extract the network co2 emissions" do
|
||||
swd.network_co2.should(eq 0.1707847118098766)
|
||||
swd.network_co2.should(eq 0.1845556583888889)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#datacenter_co2" do
|
||||
it "should extract the datacenter co2 emissions" do
|
||||
swd.datacenter_co2.should(eq 0.1829836197962963)
|
||||
swd.datacenter_co2.should(eq 0.19773820541666665)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#total_co2" do
|
||||
it "should extract the total co2 emissions" do
|
||||
swd.total_co2.should(eq 1.2198907986419754)
|
||||
swd.total_co2.should(eq 1.3182547027777778)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+37
-11
@@ -10,8 +10,9 @@ require "system"
|
||||
require "./models/access_log"
|
||||
require "./models/crawler"
|
||||
require "./swd"
|
||||
require "./asn"
|
||||
|
||||
VERSION = "0.5.8"
|
||||
VERSION = "0.6.1"
|
||||
|
||||
Log.setup_from_env
|
||||
|
||||
@@ -19,13 +20,15 @@ Log.setup_from_env
|
||||
socket = "/tmp/access_log.socket"
|
||||
# The default database URI
|
||||
database = "sqlite3://./development.sqlite3"
|
||||
# ASN database
|
||||
asn_database = ""
|
||||
# Detect web crawlers
|
||||
crawler = false
|
||||
# Parse the crawlers repository
|
||||
crawlers = [] of Crawler
|
||||
# Fields in database
|
||||
# TODO: Obtain them from AccessLog
|
||||
fields = %w[id remote_user host msec server_protocol request_method request_completion uri request_uri query_string status sent_http_content_type sent_http_content_encoding sent_http_etag sent_http_last_modified http_accept http_accept_encoding http_accept_language http_pragma http_cache_control http_if_none_match http_dnt http_user_agent http_origin http_referer request_time bytes_sent body_bytes_sent request_length http_connection pipe connection_requests geoip2_data_country_name geoip2_data_city_name ssl_server_name ssl_protocol ssl_early_data ssl_session_reused ssl_curves ssl_ciphers ssl_cipher sent_http_x_xss_protection sent_http_x_frame_options sent_http_x_content_type_options sent_http_strict_transport_security nginx_version pid crawler datacenter_co2 network_co2 consumer_device_co2 production_co2 total_co2 node]
|
||||
fields = %w[id remote_user host msec server_protocol request_method request_completion uri request_uri query_string status sent_http_content_type sent_http_content_encoding sent_http_etag sent_http_last_modified http_accept http_accept_encoding http_accept_language http_pragma http_cache_control http_if_none_match http_dnt http_user_agent http_origin http_referer request_time bytes_sent body_bytes_sent request_length http_connection pipe connection_requests geoip2_data_country_name geoip2_data_city_name ssl_server_name ssl_protocol ssl_early_data ssl_session_reused ssl_curves ssl_ciphers ssl_cipher sent_http_x_xss_protection sent_http_x_frame_options sent_http_x_content_type_options sent_http_strict_transport_security nginx_version pid crawler datacenter_co2 network_co2 consumer_device_co2 production_co2 total_co2 node asn http3]
|
||||
# Params for the query
|
||||
params = [] of String
|
||||
# SWD
|
||||
@@ -56,6 +59,10 @@ OptionParser.parse do |p|
|
||||
database = d
|
||||
end
|
||||
|
||||
p.on "-I DATABASE", "--asn-database=DATABASE", "ASN Database URI" do |d|
|
||||
asn_database = d
|
||||
end
|
||||
|
||||
p.on "-c crawler-user-agents.json", "--crawlers crawler-user-agents.json", "Crawlers repository" do |c|
|
||||
crawler = true
|
||||
crawlers = Array(Crawler).from_json File.read(c)
|
||||
@@ -110,26 +117,41 @@ if crawler
|
||||
crawler_re = Regex.union(crawlers.map { |c| c.pattern })
|
||||
end
|
||||
|
||||
def remove_socket!(socket)
|
||||
FileUtils.rm(socket) if File.exists? socket
|
||||
end
|
||||
|
||||
remove_socket!(socket)
|
||||
|
||||
asn = ASN.new(asn_database) unless asn_database.empty?
|
||||
server = Socket.unix(Socket::Type::DGRAM)
|
||||
server.bind Socket::UNIXAddress.new(socket)
|
||||
|
||||
def close_server!(server, asn)
|
||||
server.try(&.close)
|
||||
asn.try(&.close)
|
||||
end
|
||||
|
||||
Signal::INT.trap do
|
||||
server.close
|
||||
FileUtils.rm(socket) if File.exists? socket
|
||||
remove_socket!(socket)
|
||||
close_server!(server, asn)
|
||||
exit
|
||||
end
|
||||
|
||||
Signal::KILL.trap do
|
||||
server.close
|
||||
FileUtils.rm(socket) if File.exists? socket
|
||||
remove_socket!(socket)
|
||||
close_server!(server, asn)
|
||||
exit
|
||||
end
|
||||
|
||||
MAX_SIZE = 1024 * 64 # 64K
|
||||
|
||||
# Open the database and wait for JSONL input.
|
||||
db = DB.open database do |db|
|
||||
DB.open database do |db|
|
||||
while true
|
||||
begin
|
||||
msg, _ = server.receive(1024 * 64) # 64K
|
||||
msg, _ = server.receive(MAX_SIZE)
|
||||
# TODO: is this the best method?
|
||||
_, json = msg.split("{", 2)
|
||||
json = "{#{json}"
|
||||
|
||||
@@ -137,6 +159,8 @@ db = DB.open database do |db|
|
||||
access_log = AccessLog.from_json(json || "{}")
|
||||
d = device_country ? access_log.geoip2_data_country_iso_code : nil
|
||||
s = SWD.new(access_log.bytes_sent, renewable, datacenter, d, intensity) if swd
|
||||
i = access_log.remote_addr
|
||||
a = asn.try(&.query(i)) if i
|
||||
|
||||
# Execute query, detect crawler if enabled.
|
||||
# TODO: Does Crystal support splats? It does but we need to cast
|
||||
@@ -195,14 +219,16 @@ db = DB.open database do |db|
|
||||
(swd ? s.try(&.consumer_device_co2) : nil),
|
||||
(swd ? s.try(&.production_co2) : nil),
|
||||
(swd ? s.try(&.total_co2) : nil),
|
||||
(node ? System.hostname : nil)
|
||||
(node ? System.hostname : nil),
|
||||
a.try(&.id),
|
||||
access_log.http3
|
||||
|
||||
# Ignore parsing errors
|
||||
rescue e : JSON::ParseException
|
||||
Log.warn &.emit("Parse exception", error: e.message)
|
||||
rescue IO::Error
|
||||
server.close
|
||||
FileUtils.rm(socket) if File.exists? socket
|
||||
remove_socket!(socket)
|
||||
close_server!(server, asn)
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
require "db"
|
||||
require "sqlite3"
|
||||
|
||||
class AsnResult
|
||||
include DB::Serializable
|
||||
|
||||
property id : Int64?
|
||||
property country : String?
|
||||
property description : String?
|
||||
property blocked : Bool?
|
||||
end
|
||||
|
||||
class ASN
|
||||
QUERY = "select id, country, description from asn where ? between range_start and range_end limit 1;"
|
||||
STATUS = "select blocked from status where id = ? limit 1;"
|
||||
|
||||
property db : DB::Database
|
||||
|
||||
def initialize(database : String)
|
||||
@db = DB.open(database)
|
||||
@cache = Hash(UInt32, AsnResult).new
|
||||
end
|
||||
|
||||
# Binary representation of an IPv4 address
|
||||
def to_binary(address : String) : UInt32
|
||||
(address.split(".").map(&.to_u32).reduce(0.to_u32) { |t, v| (t << 8) + v })
|
||||
end
|
||||
|
||||
# Lookups an address and returns the ASN for it
|
||||
def query(address : String)
|
||||
query(to_binary(address))
|
||||
end
|
||||
|
||||
def query(binary_address : UInt32) : AsnResult?
|
||||
(@cache[binary_address] ||= @db.query_one(QUERY, binary_address.to_i64, &.read(AsnResult))).tap do |result|
|
||||
result.blocked = @db.query_one(STATUS, result.id, &.read(Bool))
|
||||
end
|
||||
rescue DB::NoResultsError
|
||||
nil
|
||||
end
|
||||
|
||||
def close
|
||||
@db.close
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
# This is a very minimal Redis server that works with Nginx keyval
|
||||
# module. When set like this:
|
||||
#
|
||||
# keyval_zone_redis zone=redis hostname=::1;
|
||||
# keyval "$remote_addr:id" $asn_id zone=redis;
|
||||
#
|
||||
# We can query the ASN id from a database and use it on Nginx config to
|
||||
# rate limit entire subnets.
|
||||
require "log"
|
||||
require "option_parser"
|
||||
require "./asn"
|
||||
require "./redis_server"
|
||||
|
||||
Log.setup_from_env
|
||||
|
||||
VERSION = "0.2.0"
|
||||
ASN_ID = "id"
|
||||
ASN_DESCRIPTION = "description"
|
||||
ASN_COUNTRY = "country"
|
||||
ASN_BLOCKED = "blocked"
|
||||
|
||||
asn_database = "sqlite3://./asn.sqlite3"
|
||||
interface = "localhost"
|
||||
port = 6379
|
||||
|
||||
OptionParser.parse do |p|
|
||||
p.banner = "IP to ASN Redis server"
|
||||
|
||||
p.on "-v", "--version", "Show version" do
|
||||
puts VERSION
|
||||
exit
|
||||
end
|
||||
|
||||
p.on "-h", "--help", "Show help" do
|
||||
puts p
|
||||
exit
|
||||
end
|
||||
|
||||
p.on "-d #{asn_database}", "--asn-database=#{asn_database}", "ASN Database URI" do |d|
|
||||
asn_database = d
|
||||
end
|
||||
|
||||
p.on "-i #{interface}", "--interface=#{interface}", "Listening interface" do |i|
|
||||
interface = i
|
||||
end
|
||||
|
||||
p.on "-p #{port}", "--port=#{port}", "Listening port" do |p|
|
||||
port = p.to_i
|
||||
end
|
||||
end
|
||||
|
||||
asn = ASN.new(asn_database)
|
||||
|
||||
# The client can be a long lived connection sending commands. We only
|
||||
# answer to GET commands.
|
||||
RedisServer.new(interface, port) do |client, _, key|
|
||||
begin
|
||||
_, ip, field = key.split(":", 3)
|
||||
rescue
|
||||
Log.warn &.emit("Error parsing key", key: key)
|
||||
next
|
||||
end
|
||||
|
||||
# This is cached in memory
|
||||
result = asn.query(ip)
|
||||
|
||||
next if result.nil?
|
||||
|
||||
# XXX: Are we going to use the other fields?
|
||||
response =
|
||||
case field
|
||||
when ASN_ID then result.try(&.id).try(&.to_s)
|
||||
when ASN_COUNTRY then result.try(&.country)
|
||||
when ASN_DESCRIPTION then result.try(&.description)
|
||||
when ASN_BLOCKED then
|
||||
if result.try(&.blocked)
|
||||
"1"
|
||||
else
|
||||
"0"
|
||||
end
|
||||
else nil
|
||||
end
|
||||
|
||||
next if response.nil?
|
||||
|
||||
# Reply
|
||||
RedisServer.bulk_string(client, response)
|
||||
end
|
||||
|
||||
asn.close
|
||||
@@ -58,4 +58,6 @@ class AccessLog
|
||||
property sent_http_strict_transport_security : String
|
||||
property nginx_version : String
|
||||
property pid : String
|
||||
property remote_addr : String?
|
||||
property http3 : String?
|
||||
end
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
require "log"
|
||||
require "socket"
|
||||
|
||||
class RedisServer
|
||||
BULK_STRING = "$"
|
||||
CRLF = "\r\n"
|
||||
NOT_IMPLEMENTED = "-Not implemented#{CRLF}"
|
||||
QUIT_COMMAND = "quit"
|
||||
GET_COMMAND = "get"
|
||||
|
||||
property server : TCPServer
|
||||
|
||||
def initialize(interface : String = "localhost", port : Int = 6379, &block : TCPSocket, String, String -> _)
|
||||
@server = TCPServer.new(interface, port)
|
||||
|
||||
while (client = server.accept?)
|
||||
spawn handle_client(client, block)
|
||||
end
|
||||
ensure
|
||||
server.close
|
||||
end
|
||||
|
||||
def handle_client(client : TCPSocket, block : Proc)
|
||||
next_is_key = false
|
||||
last_command = ""
|
||||
|
||||
while (line = client.gets)
|
||||
# We're not a compliant RESP
|
||||
next if line.starts_with?("*")
|
||||
next if line.starts_with?("$")
|
||||
|
||||
# Execute the query when we get the key
|
||||
if next_is_key
|
||||
next_is_key = false
|
||||
|
||||
# Do something with the key
|
||||
block.call(client, last_command, line)
|
||||
# First we get the Redis command and we only support a small subset.
|
||||
else
|
||||
case (last_command = line.downcase)
|
||||
when QUIT_COMMAND then client.close
|
||||
when GET_COMMAND then next_is_key = true
|
||||
else
|
||||
client.print NOT_IMPLEMENTED
|
||||
|
||||
raise last_command
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue ex
|
||||
Log.warn &.emit("Error", exception: ex.class.name, error: ex.message)
|
||||
ensure
|
||||
client.close
|
||||
end
|
||||
|
||||
def self.bulk_string(client : TCPSocket, string : String)
|
||||
client.print(BULK_STRING, string.size, CRLF, string, CRLF)
|
||||
end
|
||||
end
|
||||
+1
-1
@@ -27,7 +27,7 @@ class SWD
|
||||
NETWORK_ENERGY = 0.14
|
||||
DATACENTER_ENERGY = 0.15
|
||||
PRODUCTION_ENERGY = 0.19
|
||||
GLOBAL_GRID_INTENSITY = 437.66
|
||||
GLOBAL_GRID_INTENSITY = 472.95
|
||||
RENEWABLES_GRID_INTENSITY = 50.0
|
||||
|
||||
@transfered_bytes_to_gb : Float32? | Float64? = nil
|
||||
|
||||
+192
-192
@@ -2,215 +2,215 @@ class SWD
|
||||
module IntensityData
|
||||
# @see {co2.js/data/output/average-intensities.json}
|
||||
AVERAGE_INTENSITY_BY_ISO_CODE = {
|
||||
"AE": 492.7,
|
||||
"AF": 132.53,
|
||||
"AE": 467.51,
|
||||
"AF": 123.71,
|
||||
"AG": 611.11,
|
||||
"AL": 24.29,
|
||||
"AM": 264.54,
|
||||
"AO": 174.73,
|
||||
"AR": 353.96,
|
||||
"AS": 611.11,
|
||||
"AT": 110.78,
|
||||
"AU": 556.3,
|
||||
"AW": 561.22,
|
||||
"AZ": 671.39,
|
||||
"BA": 600.94,
|
||||
"BB": 605.51,
|
||||
"BD": 691.41,
|
||||
"BE": 138.11,
|
||||
"BF": 467.53,
|
||||
"BG": 335.33,
|
||||
"BH": 904.62,
|
||||
"BI": 250.0,
|
||||
"BJ": 584.07,
|
||||
"BN": 893.91,
|
||||
"BO": 531.69,
|
||||
"BR": 96.4,
|
||||
"BS": 660.1,
|
||||
"BT": 23.33,
|
||||
"BW": 847.91,
|
||||
"BY": 441.74,
|
||||
"BZ": 225.81,
|
||||
"CA": 165.15,
|
||||
"CD": 24.46,
|
||||
"AL": 24.42,
|
||||
"AM": 243.52,
|
||||
"AO": 167.22,
|
||||
"AR": 344.83,
|
||||
"AS": 647.06,
|
||||
"AT": 102.62,
|
||||
"AU": 553.76,
|
||||
"AW": 550.0,
|
||||
"AZ": 632.89,
|
||||
"BA": 638.05,
|
||||
"BB": 600.0,
|
||||
"BD": 694.63,
|
||||
"BE": 117.58,
|
||||
"BF": 554.91,
|
||||
"BG": 264.21,
|
||||
"BH": 902.41,
|
||||
"BI": 230.77,
|
||||
"BJ": 590.0,
|
||||
"BN": 892.67,
|
||||
"BO": 468.02,
|
||||
"BR": 106.06,
|
||||
"BS": 653.66,
|
||||
"BT": 24.19,
|
||||
"BW": 849.42,
|
||||
"BY": 323.63,
|
||||
"BZ": 155.56,
|
||||
"CA": 184.99,
|
||||
"CD": 27.04,
|
||||
"CF": 0.0,
|
||||
"CG": 700.0,
|
||||
"CH": 34.7,
|
||||
"CI": 393.89,
|
||||
"CG": 713.73,
|
||||
"CH": 36.72,
|
||||
"CI": 393.53,
|
||||
"CK": 250.0,
|
||||
"CL": 291.11,
|
||||
"CM": 305.42,
|
||||
"CN": 583.61,
|
||||
"CO": 259.51,
|
||||
"CR": 53.38,
|
||||
"CU": 637.61,
|
||||
"CV": 558.14,
|
||||
"CY": 526.02,
|
||||
"CZ": 449.72,
|
||||
"DE": 381.41,
|
||||
"DJ": 692.31,
|
||||
"DK": 151.65,
|
||||
"DM": 529.41,
|
||||
"DO": 580.78,
|
||||
"DZ": 634.61,
|
||||
"EC": 166.91,
|
||||
"EE": 416.67,
|
||||
"EG": 574.04,
|
||||
"ER": 631.58,
|
||||
"ES": 174.05,
|
||||
"ET": 24.64,
|
||||
"FI": 79.12,
|
||||
"FJ": 288.46,
|
||||
"FK": 500.0,
|
||||
"FO": 404.76,
|
||||
"FR": 56.02,
|
||||
"GA": 491.6,
|
||||
"GB": 228.25,
|
||||
"GD": 640.0,
|
||||
"GE": 167.59,
|
||||
"GF": 217.82,
|
||||
"GH": 484.0,
|
||||
"GL": 178.57,
|
||||
"CL": 265.52,
|
||||
"CM": 285.71,
|
||||
"CN": 557.5,
|
||||
"CO": 285.8,
|
||||
"CR": 63.01,
|
||||
"CU": 638.98,
|
||||
"CV": 480.0,
|
||||
"CY": 512.24,
|
||||
"CZ": 413.86,
|
||||
"DE": 342.06,
|
||||
"DJ": 450.0,
|
||||
"DK": 143.3,
|
||||
"DM": 600.0,
|
||||
"DO": 565.97,
|
||||
"DZ": 633.65,
|
||||
"EC": 209.7,
|
||||
"EE": 341.02,
|
||||
"EG": 574.5,
|
||||
"ER": 590.91,
|
||||
"ES": 146.15,
|
||||
"ET": 23.55,
|
||||
"FI": 72.25,
|
||||
"FJ": 278.26,
|
||||
"FK": 1000.0,
|
||||
"FO": 354.17,
|
||||
"FR": 44.18,
|
||||
"GA": 429.47,
|
||||
"GB": 215.79,
|
||||
"GD": 666.67,
|
||||
"GE": 143.06,
|
||||
"GF": 204.08,
|
||||
"GH": 452.86,
|
||||
"GL": 111.11,
|
||||
"GM": 666.67,
|
||||
"GN": 236.84,
|
||||
"GP": 500.0,
|
||||
"GQ": 591.84,
|
||||
"GR": 336.57,
|
||||
"GT": 328.27,
|
||||
"GU": 622.86,
|
||||
"GN": 182.72,
|
||||
"GP": 493.9,
|
||||
"GQ": 605.1,
|
||||
"GR": 319.76,
|
||||
"GT": 272.66,
|
||||
"GU": 611.11,
|
||||
"GW": 625.0,
|
||||
"GY": 640.35,
|
||||
"HK": 699.5,
|
||||
"HN": 282.27,
|
||||
"HR": 204.96,
|
||||
"HT": 567.31,
|
||||
"HU": 204.01,
|
||||
"GY": 634.33,
|
||||
"HK": 681.99,
|
||||
"HN": 289.5,
|
||||
"HR": 174.48,
|
||||
"HT": 534.65,
|
||||
"HU": 182.82,
|
||||
"ID": 682.43,
|
||||
"IE": 282.98,
|
||||
"IL": 582.93,
|
||||
"IN": 713.01,
|
||||
"IQ": 688.81,
|
||||
"IR": 641.73,
|
||||
"IS": 27.68,
|
||||
"IT": 330.72,
|
||||
"JM": 555.56,
|
||||
"JO": 540.92,
|
||||
"JP": 493.59,
|
||||
"KE": 71.2,
|
||||
"KG": 147.29,
|
||||
"KH": 417.71,
|
||||
"KI": 666.67,
|
||||
"IE": 279.79,
|
||||
"IL": 567.26,
|
||||
"IN": 708.96,
|
||||
"IQ": 689.4,
|
||||
"IR": 648.68,
|
||||
"IS": 28.33,
|
||||
"IT": 287.75,
|
||||
"JM": 561.25,
|
||||
"JO": 539.21,
|
||||
"JP": 483.73,
|
||||
"KE": 84.83,
|
||||
"KG": 152.65,
|
||||
"KH": 497.46,
|
||||
"KI": 500.0,
|
||||
"KM": 642.86,
|
||||
"KN": 636.36,
|
||||
"KP": 389.59,
|
||||
"KR": 432.11,
|
||||
"KW": 649.16,
|
||||
"KP": 344.26,
|
||||
"KR": 415.65,
|
||||
"KW": 637.24,
|
||||
"KY": 642.86,
|
||||
"KZ": 821.9,
|
||||
"LA": 265.51,
|
||||
"LB": 599.01,
|
||||
"LC": 666.67,
|
||||
"KZ": 801.79,
|
||||
"LA": 232.12,
|
||||
"LB": 369.47,
|
||||
"LC": 650.0,
|
||||
"LK": 509.78,
|
||||
"LR": 227.85,
|
||||
"LS": 20.0,
|
||||
"LT": 160.07,
|
||||
"LU": 105.26,
|
||||
"LV": 123.99,
|
||||
"LY": 818.69,
|
||||
"MA": 624.45,
|
||||
"MD": 643.46,
|
||||
"ME": 418.09,
|
||||
"MG": 436.44,
|
||||
"MK": 539.55,
|
||||
"ML": 408.0,
|
||||
"MM": 436.92,
|
||||
"MN": 775.31,
|
||||
"LR": 435.9,
|
||||
"LS": 20.83,
|
||||
"LT": 139.34,
|
||||
"LU": 134.62,
|
||||
"LV": 136.22,
|
||||
"LY": 830.53,
|
||||
"MA": 577.65,
|
||||
"MD": 629.56,
|
||||
"ME": 413.51,
|
||||
"MG": 477.27,
|
||||
"MK": 568.97,
|
||||
"ML": 394.5,
|
||||
"MM": 569.69,
|
||||
"MN": 784.01,
|
||||
"MO": 448.98,
|
||||
"MQ": 523.18,
|
||||
"MR": 464.71,
|
||||
"MQ": 516.78,
|
||||
"MR": 481.71,
|
||||
"MS": 1000.0,
|
||||
"MT": 444.03,
|
||||
"MU": 632.48,
|
||||
"MT": 484.16,
|
||||
"MU": 633.03,
|
||||
"MV": 611.77,
|
||||
"MW": 66.67,
|
||||
"MX": 492.34,
|
||||
"MY": 607.88,
|
||||
"MZ": 135.65,
|
||||
"NA": 59.26,
|
||||
"NC": 660.58,
|
||||
"NE": 670.89,
|
||||
"NG": 523.25,
|
||||
"NI": 265.12,
|
||||
"NL": 268.48,
|
||||
"NO": 30.05,
|
||||
"NP": 24.44,
|
||||
"MW": 54.65,
|
||||
"MX": 483.14,
|
||||
"MY": 604.43,
|
||||
"MZ": 127.81,
|
||||
"NA": 47.62,
|
||||
"NC": 585.76,
|
||||
"NE": 687.5,
|
||||
"NG": 507.85,
|
||||
"NI": 288.33,
|
||||
"NL": 252.7,
|
||||
"NO": 30.75,
|
||||
"NP": 23.36,
|
||||
"NR": 750.0,
|
||||
"NZ": 112.76,
|
||||
"OM": 564.55,
|
||||
"PA": 161.68,
|
||||
"PE": 266.48,
|
||||
"PF": 442.86,
|
||||
"PG": 507.25,
|
||||
"PH": 610.74,
|
||||
"PK": 440.61,
|
||||
"PL": 661.93,
|
||||
"NZ": 120.11,
|
||||
"OM": 545.33,
|
||||
"PA": 258.74,
|
||||
"PE": 263.27,
|
||||
"PF": 436.62,
|
||||
"PG": 513.74,
|
||||
"PH": 613.38,
|
||||
"PK": 398.61,
|
||||
"PL": 614.98,
|
||||
"PM": 600.0,
|
||||
"PR": 677.96,
|
||||
"PS": 516.13,
|
||||
"PT": 165.55,
|
||||
"PY": 24.31,
|
||||
"QA": 602.5,
|
||||
"RE": 572.82,
|
||||
"RO": 240.58,
|
||||
"RS": 648.2,
|
||||
"RU": 445.02,
|
||||
"RW": 316.33,
|
||||
"SA": 696.31,
|
||||
"SB": 700.0,
|
||||
"SC": 564.52,
|
||||
"SD": 263.16,
|
||||
"SE": 40.7,
|
||||
"SG": 470.78,
|
||||
"SI": 231.28,
|
||||
"SK": 116.77,
|
||||
"SL": 50.0,
|
||||
"SN": 511.6,
|
||||
"SO": 578.95,
|
||||
"SR": 349.28,
|
||||
"SS": 629.03,
|
||||
"ST": 642.86,
|
||||
"SV": 224.76,
|
||||
"SY": 701.66,
|
||||
"SZ": 172.41,
|
||||
"PR": 664.53,
|
||||
"PS": 460.78,
|
||||
"PT": 111.8,
|
||||
"PY": 24.86,
|
||||
"QA": 602.83,
|
||||
"RE": 525.22,
|
||||
"RO": 245.55,
|
||||
"RS": 670.8,
|
||||
"RU": 446.17,
|
||||
"RW": 301.89,
|
||||
"SA": 691.95,
|
||||
"SB": 636.36,
|
||||
"SC": 571.43,
|
||||
"SD": 214.33,
|
||||
"SE": 35.89,
|
||||
"SG": 498.74,
|
||||
"SI": 227.11,
|
||||
"SK": 96.49,
|
||||
"SL": 47.62,
|
||||
"SN": 535.4,
|
||||
"SO": 523.81,
|
||||
"SR": 383.18,
|
||||
"SS": 610.17,
|
||||
"ST": 555.56,
|
||||
"SV": 99.29,
|
||||
"SY": 682.27,
|
||||
"SZ": 142.86,
|
||||
"TC": 653.85,
|
||||
"TD": 628.57,
|
||||
"TG": 443.18,
|
||||
"TH": 549.85,
|
||||
"TJ": 116.86,
|
||||
"TM": 1306.03,
|
||||
"TN": 563.96,
|
||||
"TO": 625.0,
|
||||
"TR": 464.59,
|
||||
"TT": 681.53,
|
||||
"TW": 644.36,
|
||||
"TZ": 339.25,
|
||||
"UA": 256.21,
|
||||
"UG": 44.53,
|
||||
"US": 369.53,
|
||||
"UY": 128.79,
|
||||
"UZ": 1167.6,
|
||||
"VC": 529.41,
|
||||
"VE": 185.8,
|
||||
"TD": 615.39,
|
||||
"TG": 478.26,
|
||||
"TH": 555.43,
|
||||
"TJ": 98.7,
|
||||
"TM": 1306.3,
|
||||
"TN": 560.25,
|
||||
"TO": 571.43,
|
||||
"TR": 469.7,
|
||||
"TT": 682.11,
|
||||
"TW": 635.15,
|
||||
"TZ": 371.59,
|
||||
"UA": 250.47,
|
||||
"UG": 57.39,
|
||||
"US": 383.55,
|
||||
"UY": 96.7,
|
||||
"UZ": 1121.18,
|
||||
"VC": 600.0,
|
||||
"VE": 180.25,
|
||||
"VG": 647.06,
|
||||
"VI": 632.35,
|
||||
"VN": 472.47,
|
||||
"VU": 571.43,
|
||||
"WS": 473.68,
|
||||
"XK": 894.65,
|
||||
"YE": 566.1,
|
||||
"ZA": 709.69,
|
||||
"ZM": 111.97,
|
||||
"ZW": 297.87
|
||||
"VI": 641.79,
|
||||
"VN": 486.13,
|
||||
"VU": 500.0,
|
||||
"WS": 400.0,
|
||||
"XK": 958.72,
|
||||
"YE": 586.32,
|
||||
"ZA": 713.9,
|
||||
"ZM": 111.0,
|
||||
"ZW": 298.44
|
||||
}
|
||||
|
||||
# @see {co2.js/data/output/marginal-intensities-2021.json}
|
||||
|
||||
Reference in New Issue
Block a user