Compare commits

..

37 Commits

Author SHA1 Message Date
R4SAS
a7a882582f [gha] test again with 20.04
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 20:42:32 +03:00
R4SAS
250696a7b5 [gha] revert to 18.04, change static build options
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 20:34:44 +03:00
R4SAS
af7905744e [gha] update ubuntu build base to 20.04
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 19:12:01 +03:00
R4SAS
db2364e9aa [gha] add ubuntu static build
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 19:03:49 +03:00
orignal
067fb45a25 exclude router from tunnel build for 2.5 minutes if doesn't reply too often 2023-01-21 19:40:43 -05:00
R4SAS
ac287a896c [websonsole] use a function to format the amount of tunnel traffic
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-20 23:03:00 +00:00
R4SAS
8baf62eb2c [websonsole] fix int concatenation with char strings
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-21 01:55:43 +03:00
R4SAS
e1ec79daf2 [webconsole] format transit tunnels with table
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-21 00:51:07 +03:00
orignal
d4426118c5 exclude router from tunnel build for 2.5 minutes if declined 2023-01-20 15:34:40 -05:00
orignal
64fe56aa07 Merge pull request #1854 from Vort/server_forwards_style
use correct style for Server Forwards section
2023-01-20 11:58:10 -05:00
Vort
47eb49c34e use correct style for Server Forwards section 2023-01-20 18:52:56 +02:00
orignal
cd6d86c8c3 make sure that async CreateStream complete 2023-01-19 13:40:12 -05:00
R4SAS
84d4e074ce add loglevel checker, fix fields passing to translated string formatter
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 08:33:03 +03:00
R4SAS
a0e71c4173 [i18n] update strings and translation file
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 07:46:54 +03:00
R4SAS
533c8a8a55 [i18n] set decimal point based on language
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 06:40:32 +03:00
R4SAS
a57ae4dc56 [i18n] add sweedish translation
Translation author: corona@mail.i2p

Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 05:53:29 +03:00
R4SAS
88dfe3ca4e [i18n] fix build on macos
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-18 18:56:21 +03:00
R4SAS
d68c7f8ea7 [i18n] fix build on macos
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-18 08:19:45 +03:00
R4SAS
e8ace998ba [i18n] add support of string formatting
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-18 07:21:44 +03:00
orignal
e8be39af17 memrory pool for SSU2IncompleteMessage 2023-01-17 21:32:36 -05:00
orignal
7196db09d6 Merge pull request #1852 from freeacetone/openssl
HTTPProxy message stream correcting and comments
2023-01-17 07:48:05 -05:00
acetone
b290ee1aa0 Cfg example: verbose comments for Web Console auth and addresshelper for public proxy 2023-01-17 09:00:11 +03:00
acetone
d105ab11af Joining two strings to one and correct comments 2023-01-17 08:45:18 +03:00
orignal
bc888167a7 use linked list for out of sequence fragments 2023-01-16 21:40:23 -05:00
R4SAS
6ca6591c43 [make] set PREFIX from DESTDIR if it present
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-17 01:30:31 +03:00
orignal
36cb707e47 Merge pull request #1849 from freeacetone/openssl
Addresshelper updating: referer check
2023-01-16 08:33:07 -05:00
acetone
013d5ff74f Addresshelper request validation by Refer-header 2023-01-16 16:16:16 +03:00
acetone
9af5a90757 Copyright year updated 2023-01-16 13:57:15 +03:00
acetone
d8b6f5438c log typo 2023-01-16 13:56:36 +03:00
acetone
10030a4e0d Addresshelper updating: referer check 2023-01-16 13:31:13 +03:00
orignal
993dc72ce6 use separate pointer to second fragment of incomplete message 2023-01-15 22:50:54 -05:00
orignal
324ace103b memoery pool for fragments 2023-01-14 17:05:09 -05:00
orignal
d530269e4f try to insert received msgid instead lookup 2023-01-13 19:23:26 -05:00
orignal
7146a4dbae check if session socket was closed before tunnels were built 2023-01-12 15:41:57 -05:00
R4SAS
f79900653b Update README.md 2023-01-12 15:49:02 +03:00
orignal
f172f44f32 Merge pull request #1833 from TomasGlgg/feature
Использование скользящего среднего для рассчета tunnel creation success rate
2023-01-11 15:36:37 -05:00
Tomas Globis
66f82cb43f Use moving average to calculate tunnel creation success rate 2023-01-03 03:07:07 +03:00
40 changed files with 1318 additions and 855 deletions

View File

@@ -5,7 +5,7 @@ on: [push, pull_request]
jobs:
build-make:
name: Make with USE_UPNP=${{ matrix.with_upnp }}
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
strategy:
fail-fast: true
matrix:
@@ -14,14 +14,13 @@ jobs:
- uses: actions/checkout@v2
- name: install packages
run: |
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get update
sudo apt-get install build-essential libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev
sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
- name: build application
run: make USE_UPNP=${{ matrix.with_upnp }} -j3
build-cmake:
name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }}
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
strategy:
fail-fast: true
matrix:
@@ -30,11 +29,26 @@ jobs:
- uses: actions/checkout@v2
- name: install packages
run: |
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get update
sudo apt-get install build-essential cmake libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev
sudo apt-get install build-essential cmake libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
- name: build application
run: |
cd build
cmake -DWITH_UPNP=${{ matrix.with_upnp }} .
make -j3
build-static:
name: Static build with UPnP
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: install packages
run: |
sudo apt-get update
sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
- name: build application
run: make DEBUG=no USE_STATIC=yes USE_UPNP=yes -j`nproc`
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: i2pd-amd64-static
path: i2pd

View File

@@ -47,6 +47,10 @@ else
LD_DEBUG = -s
endif
ifneq (, $(DESTDIR))
PREFIX = $(DESTDIR)
endif
ifneq (, $(findstring darwin, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
ifeq ($(HOMEBREW),1)

View File

@@ -20,10 +20,10 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9
else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6
NEEDED_CXXFLAGS += -std=c++11
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9
else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9
NEEDED_CXXFLAGS += -std=c++17
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10 - 19
else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+
# NEEDED_CXXFLAGS += -std=c++20
NEEDED_CXXFLAGS += -std=c++17
LDLIBS = -latomic
@@ -33,34 +33,23 @@ endif
NEEDED_CXXFLAGS += -fPIC
ifeq ($(USE_STATIC),yes)
# NOTE: on glibc you will get this warning:
# Using 'getaddrinfo' in statically linked applications requires at runtime
# the shared libraries from the glibc version used for linking
LIBDIR := /usr/lib/$(SYS)
LDLIBS += $(LIBDIR)/libboost_system.a
LDLIBS += $(LIBDIR)/libboost_date_time.a
LDLIBS += $(LIBDIR)/libboost_filesystem.a
LDLIBS += $(LIBDIR)/libboost_program_options.a
LDLIBS += $(LIBDIR)/libssl.a
LDLIBS += $(LIBDIR)/libcrypto.a
LDLIBS += $(LIBDIR)/libz.a
ifeq ($(USE_UPNP),yes)
LDLIBS += $(LIBDIR)/libminiupnpc.a
endif
LDLIBS += -lpthread -ldl
else
LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes)
LDLIBS += -lminiupnpc
endif
endif
LDLIBS += -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lssl -lcrypto -lz
# UPNP Support (miniupnpc 1.5 and higher)
ifeq ($(USE_UPNP),yes)
LDLIBS += -lminiupnpc
NEEDED_CXXFLAGS += -DUSE_UPNP
endif
# NOTE: on glibc you will get this warning:
# Using 'getaddrinfo' in statically linked applications requires at runtime
# the shared libraries from the glibc version used for linking
ifeq ($(USE_STATIC),yes)
LDLIBS += -static -ldl -lpthread -static-libgcc -static-libstdc++
else
LDLIBS += -lpthread
endif
ifeq ($(USE_AESNI),yes)
ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that
NEEDED_CXXFLAGS += -D__AES__ -maes

View File

@@ -69,12 +69,12 @@ Build instructions:
**Supported systems:**
* GNU/Linux - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml)
* CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/)
* Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc.
* GNU/Linux (Debian, Ubuntu, etc) - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml)
* CentOS, Fedora, Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/)
* Alpine, ArchLinux, openSUSE, Gentoo, etc.
* Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml)
* Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml)
* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml)
* Mac OS - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml)
* Docker image - [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml)
* Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd)
* FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml)
* Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -29,7 +29,7 @@ namespace util
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
setlocale(LC_ALL, "Russian");
//setlocale(LC_ALL, "Russian");
setlocale(LC_TIME, "C");
i2p::log::SetThrowFunction ([](const std::string& s)
@@ -61,7 +61,7 @@ namespace util
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
setlocale(LC_ALL, "Russian");
//setlocale(LC_ALL, "Russian");
setlocale(LC_TIME, "C");
#ifdef WIN32_APP
if (!i2p::win32::StartWin32App ()) return false;

File diff suppressed because it is too large Load Diff

View File

@@ -122,6 +122,8 @@ port = 7070
## Path to web console, default "/"
# webroot = /
## Uncomment following lines to enable Web Console authentication
## You should not use Web Console via public networks without additional encryption.
## HTTP authentication is not encryption layer!
# auth = true
# user = i2pd
# pass = changeme
@@ -139,6 +141,8 @@ port = 4444
## Optional keys file for proxy local destination
# keys = http-proxy-keys.dat
## Enable address helper for adding .i2p domains with "jump URLs" (default: true)
## You should disable this feature if your i2pd HTTP Proxy is public,
## because anyone could spoof the short domain via addresshelper and forward other users to phishing links
# addresshelper = true
## Address of a proxy server inside I2P, which is used to visit regular Internet
# outproxy = http://false.i2p

View File

@@ -1,293 +1,293 @@
/*
* Copyright (c) 2021-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*
******************************************************************
*
* This is style sheet for webconsole, with @media selectors for adaptive
* view on desktop and mobile devices, respecting preferred user's color
* scheme used in system/browser.
*
* Minified copy of that style sheet is bundled inside i2pd sources.
*/
:root {
--main-bg-color: #fafafa;
--main-text-color: #103456;
--main-link-color: #894c84;
--main-link-hover-color: #fafafa;
}
@media (prefers-color-scheme: dark) {
:root {
--main-bg-color: #242424;
--main-text-color: #17ab5c;
--main-link-color: #bf64b7;
--main-link-hover-color: #000000;
}
}
body {
font: 100%/1.5em sans-serif;
margin: 0;
padding: 1.5em;
background: var(--main-bg-color);
color: var(--main-text-color);
}
a, .slide label {
text-decoration: none;
color: var(--main-link-color);
}
a:hover, .slide label:hover, button[type=submit]:hover {
color: var(--main-link-hover-color);
background: var(--main-link-color);
}
a.button {
appearance: button;
text-decoration: none;
padding: 0 5px;
border: 1px solid var(--main-link-color);
}
.header {
font-size: 2.5em;
text-align: center;
margin: 1em 0;
color: var(--main-link-color);
}
.wrapper {
margin: 0 auto;
padding: 1em;
max-width: 64em;
}
.menu {
display: block;
float: left;
overflow: hidden;
padding: 4px;
max-width: 12em;
white-space: nowrap;
text-overflow: ellipsis;
}
.listitem {
display: block;
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.tableitem {
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.content {
float: left;
font-size: 1em;
margin-left: 2em;
padding: 4px;
max-width: 50em;
overflow: auto;
}
.tunnel.established {
color: #56B734;
}
.tunnel.expiring {
color: #D3AE3F;
}
.tunnel.failed {
color: #D33F3F;
}
.tunnel.building {
color: #434343;
}
caption {
font-size: 1.5em;
text-align: center;
color: var(--main-link-color);
}
table {
display: table;
border-collapse: collapse;
text-align: center;
}
table.extaddr {
text-align: left;
}
table.services {
width: 100%;
}
textarea {
background-color: var(--main-bg-color);
color: var(--main-text-color);
word-break: break-all;
}
.streamdest {
width: 120px;
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis;
}
.slide div.slidecontent, .slide [type="checkbox"] {
display: none;
}
.slide [type="checkbox"]:checked ~ div.slidecontent {
display: block;
margin-top: 0;
padding: 0;
}
.disabled {
color: #D33F3F;
}
.enabled {
color: #56B734;
}
button[type=submit] {
background-color: transparent;
color: var(--main-link-color);
text-decoration: none;
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input, select, select option {
background-color: var(--main-bg-color);
color: var(--main-link-color);
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input:focus, select:focus, select option:focus {
outline: none;
}
input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
}
@media screen and (max-width: 1150px) { /* adaptive style */
.wrapper {
max-width: 58em;
}
.content {
max-width: 40em;
}
}
@media screen and (max-width: 980px) {
body {
font: 100%/1.2em sans-serif;
padding: 1.2em 0 0 0;
}
.menu {
width: 100%;
max-width: unset;
display: block;
float: none;
position: unset;
font-size: 16px;
text-align: center;
}
.menu a, .commands a {
display: inline-block;
padding: 4px;
}
.content {
float: none;
margin-left: unset;
margin-top: 16px;
max-width: 100%;
width: 100%;
text-align: center;
}
a, .slide label {
display: block;
}
.header {
margin: unset;
font-size: 1.5em;
}
small {
display: block
}
a.button {
appearance: button;
text-decoration: none;
margin-top: 10px;
padding: 6px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
width: -webkit-fill-available;
}
input, select {
width: 35%;
text-align: center;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 18px;
}
table.extaddr {
margin: auto;
text-align: unset;
}
textarea {
width: -webkit-fill-available;
height: auto;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 12px;
}
button[type=submit] {
padding: 5px 15px;
background: transparent;
border: 2px solid var(--main-link-color);
cursor: pointer;
-webkit-border-radius: 5px;
border-radius: 5px;
position: relative;
height: 36px;
display: -webkit-inline-box;
margin-top: 10px;
}
}
/*
* Copyright (c) 2021-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*
******************************************************************
*
* This is style sheet for webconsole, with @media selectors for adaptive
* view on desktop and mobile devices, respecting preferred user's color
* scheme used in system/browser.
*
* Minified copy of that style sheet is bundled inside i2pd sources.
*/
:root {
--main-bg-color: #fafafa;
--main-text-color: #103456;
--main-link-color: #894c84;
--main-link-hover-color: #fafafa;
}
@media (prefers-color-scheme: dark) {
:root {
--main-bg-color: #242424;
--main-text-color: #17ab5c;
--main-link-color: #bf64b7;
--main-link-hover-color: #000000;
}
}
body {
font: 100%/1.5em sans-serif;
margin: 0;
padding: 1.5em;
background: var(--main-bg-color);
color: var(--main-text-color);
}
a, .slide label {
text-decoration: none;
color: var(--main-link-color);
}
a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover {
color: var(--main-link-hover-color);
background: var(--main-link-color);
}
a.button {
appearance: button;
text-decoration: none;
padding: 0 5px;
border: 1px solid var(--main-link-color);
}
.header {
font-size: 2.5em;
text-align: center;
margin: 1em 0;
color: var(--main-link-color);
}
.wrapper {
margin: 0 auto;
padding: 1em;
max-width: 64em;
}
.menu {
display: block;
float: left;
overflow: hidden;
padding: 4px;
max-width: 12em;
white-space: nowrap;
text-overflow: ellipsis;
}
.listitem {
display: block;
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.tableitem {
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.content {
float: left;
font-size: 1em;
margin-left: 2em;
padding: 4px;
max-width: 50em;
overflow: auto;
}
.tunnel.established {
color: #56B734;
}
.tunnel.expiring {
color: #D3AE3F;
}
.tunnel.failed {
color: #D33F3F;
}
.tunnel.building {
color: #434343;
}
caption {
font-size: 1.5em;
text-align: center;
color: var(--main-link-color);
}
table {
display: table;
border-collapse: collapse;
text-align: center;
}
table.extaddr {
text-align: left;
}
table.services {
width: 100%;
}
textarea {
background-color: var(--main-bg-color);
color: var(--main-text-color);
word-break: break-all;
}
.streamdest {
width: 120px;
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis;
}
.slide div.slidecontent, .slide [type="checkbox"] {
display: none;
}
.slide [type="checkbox"]:checked ~ div.slidecontent {
display: block;
margin-top: 0;
padding: 0;
}
.disabled {
color: #D33F3F;
}
.enabled {
color: #56B734;
}
button[type=submit] {
background-color: transparent;
color: var(--main-link-color);
text-decoration: none;
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input, select, select option {
background-color: var(--main-bg-color);
color: var(--main-link-color);
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input:focus, select:focus, select option:focus {
outline: none;
}
input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
}
@media screen and (max-width: 1150px) { /* adaptive style */
.wrapper {
max-width: 58em;
}
.content {
max-width: 40em;
}
}
@media screen and (max-width: 980px) {
body {
font: 100%/1.2em sans-serif;
padding: 1.2em 0 0 0;
}
.menu {
width: 100%;
max-width: unset;
display: block;
float: none;
position: unset;
font-size: 16px;
text-align: center;
}
.menu a, .commands a {
display: inline-block;
padding: 4px;
}
.content {
float: none;
margin-left: unset;
margin-top: 16px;
max-width: 100%;
width: 100%;
text-align: center;
}
a, .slide label {
display: block;
}
.header {
margin: unset;
font-size: 1.5em;
}
small {
display: block
}
a.button {
appearance: button;
text-decoration: none;
margin-top: 10px;
padding: 6px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
width: -webkit-fill-available;
}
input, select {
width: 35%;
text-align: center;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 18px;
}
table.extaddr {
margin: auto;
text-align: unset;
}
textarea {
width: -webkit-fill-available;
height: auto;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 12px;
}
button[type=submit] {
padding: 5px 15px;
background: transparent;
border: 2px solid var(--main-link-color);
cursor: pointer;
-webkit-border-radius: 5px;
border-radius: 5px;
position: relative;
height: 36px;
display: -webkit-inline-box;
margin-top: 10px;
}
}

View File

@@ -103,18 +103,18 @@ namespace http {
int num;
if ((num = seconds / 86400) > 0) {
s << num << " " << tr("day", "days", num) << ", ";
s << ntr("%d day", "%d days", num, num) << ", ";
seconds -= num * 86400;
}
if ((num = seconds / 3600) > 0) {
s << num << " " << tr("hour", "hours", num) << ", ";
s << ntr("%d hour", "%d hours", num, num) << ", ";
seconds -= num * 3600;
}
if ((num = seconds / 60) > 0) {
s << num << " " << tr("minute", "minutes", num) << ", ";
s << ntr("%d minute", "%d minutes", num, num) << ", ";
seconds -= num * 60;
}
s << seconds << " " << tr("second", "seconds", seconds);
s << ntr("%d second", "%d seconds", seconds, seconds);
}
static void ShowTraffic (std::stringstream& s, uint64_t bytes)
@@ -122,11 +122,11 @@ namespace http {
s << std::fixed << std::setprecision(2);
auto numKBytes = (double) bytes / 1024;
if (numKBytes < 1024)
s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB");
s << tr(/* tr: Kibibyte */ "%.2f KiB", numKBytes);
else if (numKBytes < 1024 * 1024)
s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB");
s << tr(/* tr: Mebibyte */ "%.2f MiB", numKBytes / 1024);
else
s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB");
s << tr(/* tr: Gibibyte */ "%.2f GiB", numKBytes / 1024 / 1024);
}
static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
@@ -150,7 +150,8 @@ namespace http {
else stateText = tr("unknown");
s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>, ";
s << " " << (int) (bytes / 1024) << "&nbsp;" << tr(/* tr: Kibibit */ "KiB") << "\r\n";
ShowTraffic(s, bytes);
s << "\r\n";
}
static void SetLogLevel (const std::string& level)
@@ -198,7 +199,7 @@ namespace http {
if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels())
s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">" << tr("Transit Tunnels") << "</a><br>\r\n";
s <<
" <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr ("Transports") << "</a><br>\r\n"
" <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr("Transports") << "</a><br>\r\n"
" <a href=\"" << webroot << "?page=" << HTTP_PAGE_I2P_TUNNELS << "\">" << tr("I2P tunnels") << "</a><br>\r\n";
if (i2p::client::context.GetSAMBridge ())
s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSIONS << "\">" << tr("SAM sessions") << "</a><br>\r\n";
@@ -247,7 +248,7 @@ namespace http {
break;
case eRouterErrorFullConeNAT:
s << " - " << tr("Full cone NAT");
break;
break;
case eRouterErrorNoDescriptors:
s << " - " << tr("No Descriptors");
break;
@@ -290,13 +291,13 @@ namespace http {
s << "<b>" << tr("Tunnel creation success rate") << ":</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n";
s << "<b>" << tr("Received") << ":</b> ";
ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ());
s << " (" << (double) i2p::transport::transports.GetInBandwidth15s () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n";
s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetInBandwidth15s () / 1024) << ")<br>\r\n";
s << "<b>" << tr("Sent") << ":</b> ";
ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ());
s << " (" << (double) i2p::transport::transports.GetOutBandwidth15s () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n";
s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << ")<br>\r\n";
s << "<b>" << tr("Transit") << ":</b> ";
ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ());
s << " (" << (double) i2p::transport::transports.GetTransitBandwidth15s () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n";
s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << ")<br>\r\n";
s << "<b>" << tr("Data path") << ":</b> " << i2p::fs::GetUTF8DataDir() << "<br>\r\n";
s << "<div class='slide'>";
if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) {
@@ -338,7 +339,7 @@ namespace http {
s << "<td>" << address->host.to_string() << ":" << address->port << "</td>\r\n";
else
{
s << "<td>" << tr("supported");
s << "<td>" << tr(/* tr: Shown when router doesn't publish itself and have "Firewalled" state */ "supported");
if (address->port)
s << " :" << address->port;
s << "</td>\r\n";
@@ -466,7 +467,7 @@ namespace http {
}
s << "&#8658; " << it->GetTunnelID () << ":me";
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )";
s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ());
s << "</div>\r\n";
}
@@ -486,22 +487,26 @@ namespace http {
);
}
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr("ms") << " )";
s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ());
s << "</div>\r\n";
}
}
s << "<br>\r\n";
s << "<b>" << tr("Tags") << "</b><br>\r\n" << tr("Incoming") << ": <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n";
s << "<b>" << tr("Tags") << "</b><br>\r\n"
<< tr("Incoming") << ": <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n";
if (!dest->GetSessions ().empty ()) {
std::stringstream tmp_s; uint32_t out_tags = 0;
for (const auto& it: dest->GetSessions ()) {
tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "</td><td>" << it.second->GetNumOutgoingTags () << "</td></tr>\r\n";
out_tags += it.second->GetNumOutgoingTags ();
}
s << "<div class='slide'><label for='slide-tags'>" << tr("Outgoing") << ": <i>" << out_tags << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-tags\" />\r\n"
<< "<div class=\"slidecontent\">\r\n<table>\r\n<thead><th>" << tr("Destination") << "</th><th>" << tr("Amount") << "</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n";
s << "<div class='slide'><label for='slide-tags'>" << tr("Outgoing") << ": <i>" << out_tags << "</i></label>\r\n"
<< "<input type=\"checkbox\" id=\"slide-tags\" />\r\n"
<< "<div class=\"slidecontent\">\r\n"
<< "<table>\r\n<thead><th>" << tr("Destination") << "</th><th>" << tr("Amount") << "</th></thead>\r\n"
<< "<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n";
} else
s << tr("Outgoing") << ": <i>0</i><br>\r\n";
s << "<br>\r\n";
@@ -516,8 +521,11 @@ namespace http {
tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "</td><td>" << it.second->GetState () << "</td></tr>\r\n";
ecies_sessions++;
}
s << "<div class='slide'><label for='slide-ecies-sessions'>" << tr("Tags sessions") << ": <i>" << ecies_sessions << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-ecies-sessions\" />\r\n"
<< "<div class=\"slidecontent\">\r\n<table>\r\n<thead><th>" << tr("Destination") << "</th><th>" << tr("Status") << "</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n";
s << "<div class='slide'><label for='slide-ecies-sessions'>" << tr("Tags sessions") << ": <i>" << ecies_sessions << "</i></label>\r\n"
<< "<input type=\"checkbox\" id=\"slide-ecies-sessions\" />\r\n"
<< "<div class=\"slidecontent\">\r\n<table>\r\n"
<< "<thead><th>" << tr("Destination") << "</th><th>" << tr("Status") << "</th></thead>\r\n"
<< "<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n";
} else
s << tr("Tags sessions") << ": <i>0</i><br>\r\n";
s << "<br>\r\n";
@@ -642,7 +650,7 @@ namespace http {
}
else if (!i2p::context.IsFloodfill ())
{
s << "<b>" << tr("LeaseSets") << ":</b> " << tr("not floodfill") << ".<br>\r\n";
s << "<b>" << tr("LeaseSets") << ":</b> " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".<br>\r\n";
}
else
{
@@ -671,7 +679,7 @@ namespace http {
}
s << "&#8658; " << it->GetTunnelID () << ":me";
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr("ms") << " )";
s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ());
s << "</div>\r\n";
}
@@ -691,7 +699,7 @@ namespace http {
);
}
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr("ms") << " )";
s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ());
s << "</div>\r\n";
}
@@ -729,19 +737,20 @@ namespace http {
s << "<br>\r\n<small>" << tr("<b>Note:</b> any action done here are not persistent and not changes your config files.") << "</small>\r\n<br>\r\n";
auto loglevel = i2p::log::Logger().GetLogLevel();
s << "<b>" << tr("Logging level") << "</b><br>\r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\"> none </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\"> error </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\"> warn </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\"> info </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n";
s << " <a class=\"button" << (loglevel == eLogNone ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\"> none </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogError ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\"> error </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogWarning ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\"> warn </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogInfo ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\"> info </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogDebug ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n";
uint16_t maxTunnels = GetMaxNumTransitTunnels ();
s << "<b>" << tr("Transit tunnels limit") << "</b><br>\r\n";
s << "<form method=\"get\" action=\"" << webroot << "\">\r\n";
s << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_LIMITTRANSIT << "\">\r\n";
s << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n";
s << " <input type=\"number\" min=\"0\" max=\"65535\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n";
s << " <input type=\"number\" min=\"0\" max=\"" << TRANSIT_TUNNELS_LIMIT <<"\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n";
s << " <button type=\"submit\">" << tr("Change") << "</button>\r\n";
s << "</form>\r\n<br>\r\n";
@@ -771,23 +780,24 @@ namespace http {
{
if (i2p::tunnel::tunnels.CountTransitTunnels())
{
s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n";
s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th></thead><tbody class=\"tableitem\">";
for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ())
{
s << "<div class=\"listitem\">\r\n";
if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
s << it->GetTunnelID () << " &#8658; ";
s << "<tr><td></td><td>" << it->GetTunnelID () << "</td><td>&#8658;</td><td>";
else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it))
s << " &#8658; " << it->GetTunnelID ();
s << "<tr><td>&#8658;</td><td>" << it->GetTunnelID () << "</td><td></td><td>";
else
s << " &#8658; " << it->GetTunnelID () << " &#8658; ";
s << " " << it->GetNumTransmittedBytes () << "</div>\r\n";
s << "<tr><td>&#8658;</td><td>" << it->GetTunnelID () << "</td><td>&#8658;</td><td>";
ShowTraffic(s, it->GetNumTransmittedBytes ());
s << "</td></tr>\r\n";
}
s << "</div>\r\n";
s << "</tbody></table>\r\n";
}
else
{
s << "<b>" << tr("Transit Tunnels") << ":</b> " << tr("no transit tunnels currently built") << ".<br>\r\n";
s << "<b>" << tr("Transit Tunnels") << ":</b> " << tr(/* Message on transit tunnels page */ "no transit tunnels currently built") << ".<br>\r\n";
}
}
@@ -884,7 +894,7 @@ namespace http {
s << "</div>\r\n";
}
else
s << "<b>" << tr("SAM sessions") << ":</b> " << tr("no sessions currently running") << ".<br>\r\n";
s << "<b>" << tr("SAM sessions") << ":</b> " << tr(/* Message on SAM sessions page */ "no sessions currently running") << ".<br>\r\n";
}
void ShowSAMSession (std::stringstream& s, const std::string& id)
@@ -993,7 +1003,7 @@ namespace http {
for (auto& it: serverForwards)
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> &#8656; ";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "</div>\r\n"<< std::endl;
@@ -1203,7 +1213,7 @@ namespace http {
url.parse_query(params);
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
std::string redirect = "5; url=" + webroot + "?page=commands";
std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands";
std::string token = params["token"];
if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ())
@@ -1277,20 +1287,20 @@ namespace http {
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("StreamID can't be null") << "<br>\r\n<br>\r\n";
s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a><br>\r\n";
s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>";
redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32;
s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32;
res.add_header("Refresh", redirect.c_str());
return;
}
else if (cmd == HTTP_COMMAND_LIMITTRANSIT)
{
uint32_t limit = std::stoul(params["limit"], nullptr);
if (limit > 0 && limit <= 65535)
if (limit > 0 && limit <= TRANSIT_TUNNELS_LIMIT)
SetMaxNumTransitTunnels (limit);
else {
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Transit tunnels count must not exceed 65535") << "\r\n<br>\r\n<br>\r\n";
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Transit tunnels count must not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n<br>\r\n<br>\r\n";
s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a>\r\n<br>\r\n";
s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>";
s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
res.add_header("Refresh", redirect.c_str());
return;
}
@@ -1365,7 +1375,7 @@ namespace http {
s << "<b>" << tr("SUCCESS") << "</b>:&nbsp;" << tr("Command accepted") << "<br><br>\r\n";
s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a><br>\r\n";
s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>";
s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
res.add_header("Refresh", redirect.c_str());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -24,6 +24,8 @@ namespace http
{
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds
const int COMMAND_REDIRECT_TIMEOUT = 5; // in seconds
const int TRANSIT_TUNNELS_LIMIT = 65535;
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -38,7 +38,7 @@ namespace http
"@media (prefers-color-scheme: dark) { :root { --main-bg-color: #242424; --main-text-color: #17ab5c; --main-link-color: #bf64b7; --main-link-hover-color: #000000; } }\r\n"
"body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: var(--main-bg-color); color: var(--main-text-color); }\r\n"
"a, .slide label { text-decoration: none; color: var(--main-link-color); }\r\n"
"a:hover, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n"
"a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n"
"a.button { appearance: button; text-decoration: none; padding: 0 5px; border: 1px solid var(--main-link-color); }\r\n"
".header { font-size: 2.5em; text-align: center; margin: 1em 0; color: var(--main-link-color); }\r\n"
".wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n"

View File

@@ -64,10 +64,10 @@ namespace afrikaans // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"dag", "dae"}},
{"hours", {"uur", "ure"}},
{"minutes", {"minuut", "minute"}},
{"seconds", {"seconde", "sekondes"}},
{"%d days", {"%d dag", "%d dae"}},
{"%d hours", {"%d uur", "%d ure"}},
{"%d minutes", {"%d minuut", "%d minute"}},
{"%d seconds", {"%d seconde", "%d sekondes"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace armenian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "ԿիԲ"},
{"MiB", "ՄիԲ"},
{"GiB", "ԳիԲ"},
{"%.2f KiB", "%.2f ԿիԲ"},
{"%.2f MiB", "%.2f ՄիԲ"},
{"%.2f GiB", "%.2f ԳիԲ"},
{"building", "կառուցվում է"},
{"failed", "Անհաջող"},
{"expiring", "Լրանում է"},
@@ -68,7 +68,7 @@ namespace armenian // language namespace
{"Family", "Խմբատեսակ"},
{"Tunnel creation success rate", "Հաջողությամբ կառուցված թունելներ"},
{"Received", "Ստացվել է"},
{"KiB/s", "ԿիԲ/վ"},
{"%.2f KiB/s", "%.2f ԿիԲ/վ"},
{"Sent", "Ուղարկվել է"},
{"Transit", "Տարանցում"},
{"Data path", "Տվյալների ուղին"},
@@ -94,7 +94,7 @@ namespace armenian // language namespace
{"Type", "Տեսակը"},
{"EncType", "Գաղտնագրի տեսակը"},
{"Inbound tunnels", "Մուտքային թունելներ"},
{"ms", "մլվ"},
{"%dms", "%dմլվ"},
{"Outbound tunnels", "Ելքային թունելներ"},
{"Tags", "Թեգեր"},
{"Incoming", "Մուտքային"},
@@ -198,10 +198,10 @@ namespace armenian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"օր", "օր"}},
{"hours", {"ժամ", "ժամ"}},
{"minutes", {"րոպե", "րոպե"}},
{"seconds", {"վարկյան", "վարկյան"}},
{"%d days", {"%d օր", "%d օր"}},
{"%d hours", {"%d ժամ", "%d ժամ"}},
{"%d minutes", {"%d րոպե", "%d րոպե"}},
{"%d seconds", {"%d վարկյան", "%d վարկյան"}},
{"", {"", ""}},
};

View File

@@ -32,9 +32,9 @@ namespace chinese // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "正在构建"},
{"failed", "连接失败"},
{"expiring", "即将过期"},
@@ -70,7 +70,7 @@ namespace chinese // language namespace
{"Family", "家族"},
{"Tunnel creation success rate", "隧道创建成功率"},
{"Received", "已接收"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "已发送"},
{"Transit", "中转"},
{"Data path", "数据文件路径"},
@@ -96,7 +96,7 @@ namespace chinese // language namespace
{"Type", "类型"},
{"EncType", "加密类型"},
{"Inbound tunnels", "入站隧道"},
{"ms", "毫秒"},
{"%dms", "%d毫秒"},
{"Outbound tunnels", "出站隧道"},
{"Tags", "标签"},
{"Incoming", "传入"},
@@ -200,10 +200,10 @@ namespace chinese // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {""}},
{"hours", {""}},
{"minutes", {""}},
{"seconds", {""}},
{"%d days", {"%d "}},
{"%d hours", {"%d "}},
{"%d minutes", {"%d "}},
{"%d seconds", {"%d "}},
{"", {""}},
};

View File

@@ -31,9 +31,9 @@ namespace czech // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "vytváří se"},
{"failed", "selhalo"},
{"expiring", "končící"},
@@ -69,7 +69,7 @@ namespace czech // language namespace
{"Family", "Rodina"},
{"Tunnel creation success rate", "Úspěšnost vytváření tunelů"},
{"Received", "Přijato"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Odesláno"},
{"Transit", "Tranzit"},
{"Data path", "Cesta k data souborům"},
@@ -95,7 +95,7 @@ namespace czech // language namespace
{"Type", "Typ"},
{"EncType", "EncType"},
{"Inbound tunnels", "Příchozí tunely"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Odchozí tunely"},
{"Tags", "Štítky"},
{"Incoming", "Příchozí"},
@@ -199,10 +199,10 @@ namespace czech // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"den", "dny", "dní", "dní"}},
{"hours", {"hodina", "hodiny", "hodin", "hodin"}},
{"minutes", {"minuta", "minuty", "minut", "minut"}},
{"seconds", {"vteřina", "vteřiny", "vteřin", "vteřin"}},
{"%d days", {"%d den", "%d dny", "%d dní", "%d dní"}},
{"%d hours", {"%d hodina", "%d hodiny", "%d hodin", "%d hodin"}},
{"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}},
{"%d seconds", {"%d vteřina", "%d vteřiny", "%d vteřin", "%d vteřin"}},
{"", {"", "", "", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace french // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "Kio"},
{"MiB", "Mio"},
{"GiB", "Gio"},
{"%.2f KiB", "%.2f Kio"},
{"%.2f MiB", "%.2f Mio"},
{"%.2f GiB", "%.2f Gio"},
{"building", "En construction"},
{"failed", "échoué"},
{"expiring", "expiré"},
@@ -69,7 +69,7 @@ namespace french // language namespace
{"Family", "Famille"},
{"Tunnel creation success rate", "Taux de succès de création de tunnels"},
{"Received", "Reçu"},
{"KiB/s", "kio/s"},
{"%.2f KiB/s", "%.2f kio/s"},
{"Sent", "Envoyé"},
{"Transit", "Transité"},
{"Data path", "Emplacement des données"},
@@ -93,7 +93,7 @@ namespace french // language namespace
{"Address", "Adresse"},
{"Type", "Type"},
{"Inbound tunnels", "Tunnels entrants"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Tunnels sortants"},
{"Tags", "Balises"},
{"Incoming", "Entrant"},
@@ -194,10 +194,10 @@ namespace french // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"jour", "jours"}},
{"hours", {"heure", "heures"}},
{"minutes", {"minute", "minutes"}},
{"seconds", {"seconde", "secondes"}},
{"%d days", {"%d jour", "%d jours"}},
{"%d hours", {"%d heure", "%d heures"}},
{"%d minutes", {"%d minute", "%d minutes"}},
{"%d seconds", {"%d seconde", "%d secondes"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace german // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "In Bau"},
{"failed", "fehlgeschlagen"},
{"expiring", "läuft ab"},
@@ -69,7 +69,7 @@ namespace german // language namespace
{"Family", "Familie"},
{"Tunnel creation success rate", "Erfolgsrate der Tunnelerstellung"},
{"Received", "Eingegangen"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Gesendet"},
{"Transit", "Transit"},
{"Data path", "Datenpfad"},
@@ -95,7 +95,7 @@ namespace german // language namespace
{"Type", "Typ"},
{"EncType", "Verschlüsselungstyp"},
{"Inbound tunnels", "Eingehende Tunnel"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Ausgehende Tunnel"},
{"Tags", "Tags"},
{"Incoming", "Eingehend"},
@@ -199,10 +199,10 @@ namespace german // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"Tag", "Tage"}},
{"hours", {"Stunde", "Stunden"}},
{"minutes", {"Minute", "Minuten"}},
{"seconds", {"Sekunde", "Sekunden"}},
{"%d days", {"%d Tag", "%d Tage"}},
{"%d hours", {"%d Stunde", "%d Stunden"}},
{"%d minutes", {"%d Minute", "%d Minuten"}},
{"%d seconds", {"%d Sekunde", "%d Sekunden"}},
{"", {"", ""}},
};

View File

@@ -1,11 +1,12 @@
/*
* Copyright (c) 2021-2022, The PurpleI2P Project
* Copyright (c) 2021-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <clocale>
#include "ClientContext.h"
#include "I18N_langs.h"
#include "I18N.h"
@@ -18,9 +19,15 @@ namespace i18n
{
const auto it = i2p::i18n::languages.find(lang);
if (it == i2p::i18n::languages.end()) // fallback
{
i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale());
setlocale(LC_NUMERIC, "english");
}
else
{
i2p::client::context.SetLanguage (it->second.LocaleFunc());
setlocale(LC_NUMERIC, lang.c_str()); // set decimal point based on language
}
}
std::string translate (const std::string& arg)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, The PurpleI2P Project
* Copyright (c) 2021-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -67,17 +67,71 @@ namespace i18n
const std::map<std::string, std::vector<std::string>> m_Plurals;
std::function<int(int)> m_Formula;
};
void SetLanguage(const std::string &lang);
std::string translate (const std::string& arg);
std::string translate (const std::string& arg, const std::string& arg2, const int& n);
} // i18n
} // i2p
template<typename... TArgs>
std::string tr (TArgs&&... args)
/**
* @brief Get translation of string
* @param arg String with message
*/
template<typename TValue>
std::string tr (TValue&& arg)
{
return i2p::i18n::translate(std::forward<TArgs>(args)...);
return i2p::i18n::translate(std::forward<TValue>(arg));
}
/**
* @brief Get translation of string and format it
* @param arg String with message
* @param args Array of arguments for string formatting
*/
template<typename TValue, typename... TArgs>
std::string tr (TValue&& arg, TArgs&&... args)
{
std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg));
size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...);
size = size + 1;
std::string str(size, 0);
std::snprintf(&str.front(), size, tr_str.c_str(), std::forward<TArgs>(args)...);
return str;
}
/**
* @brief Get translation of string with plural forms
* @param arg String with message in singular form
* @param arg2 String with message in plural form
* @param n Integer, used for selection of form
*/
template<typename TValue, typename TValue2>
std::string ntr (TValue&& arg, TValue2&& arg2, int& n)
{
return i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
}
/**
* @brief Get translation of string with plural forms and format it
* @param arg String with message in singular form
* @param arg2 String with message in plural form
* @param n Integer, used for selection of form
* @param args Array of arguments for string formatting
*/
template<typename TValue, typename TValue2, typename... TArgs>
std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args)
{
std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...);
size = size + 1;
std::string str(size, 0);
std::snprintf(&str.front(), size, tr_str.c_str(), std::forward<TArgs>(args)...);
return str;
}
#endif // __I18N_H__

View File

@@ -33,6 +33,7 @@ namespace i18n
namespace italian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace russian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace spanish { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace swedish { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace turkmen { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace ukrainian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace uzbek { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
@@ -52,6 +53,7 @@ namespace i18n
{ "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} },
{ "russian", {"Русский язык", "ru", i2p::i18n::russian::GetLocale} },
{ "spanish", {"Español", "es", i2p::i18n::spanish::GetLocale} },
{ "swedish", {"Svenska", "sv", i2p::i18n::swedish::GetLocale} },
{ "turkmen", {"Türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} },
{ "ukrainian", {"Украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} },
{ "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} },

View File

@@ -31,9 +31,9 @@ namespace italian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "in costruzione"},
{"failed", "fallito"},
{"expiring", "in scadenza"},
@@ -69,7 +69,7 @@ namespace italian // language namespace
{"Family", "Famiglia"},
{"Tunnel creation success rate", "Percentuale di tunnel creati con successo"},
{"Received", "Ricevuti"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Inviati"},
{"Transit", "Transitati"},
{"Data path", "Percorso dati"},
@@ -95,7 +95,7 @@ namespace italian // language namespace
{"Type", "Tipologia"},
{"EncType", "Tipo di crittografia"},
{"Inbound tunnels", "Tunnel in entrata"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Tunnel in uscita"},
{"Tags", "Tag"},
{"Incoming", "In entrata"},
@@ -199,10 +199,10 @@ namespace italian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"giorno", "giorni"}},
{"hours", {"ora", "ore"}},
{"minutes", {"minuto", "minuti"}},
{"seconds", {"secondo", "secondi"}},
{"%d days", {"%d giorno", "%d giorni"}},
{"%d hours", {"%d ora", "%d ore"}},
{"%d minutes", {"%d minuto", "%d minuti"}},
{"%d seconds", {"%d secondo", "%d secondi"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace russian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "КиБ"},
{"MiB", "МиБ"},
{"GiB", "ГиБ"},
{"%.2f KiB", "%.2f КиБ"},
{"%.2f MiB", "%.2f МиБ"},
{"%.2f GiB", "%.2f ГиБ"},
{"building", "строится"},
{"failed", "неудачный"},
{"expiring", "истекает"},
@@ -68,7 +68,7 @@ namespace russian // language namespace
{"Family", "Семейство"},
{"Tunnel creation success rate", "Успешно построенных туннелей"},
{"Received", "Получено"},
{"KiB/s", "КиБ/с"},
{"%.2f KiB/s", "%.2f КиБ/с"},
{"Sent", "Отправлено"},
{"Transit", "Транзит"},
{"Data path", "Путь к данным"},
@@ -94,7 +94,7 @@ namespace russian // language namespace
{"Type", "Тип"},
{"EncType", "ТипШифр"},
{"Inbound tunnels", "Входящие туннели"},
{"ms", "мс"},
{"%dms", "%dмс"},
{"Outbound tunnels", "Исходящие туннели"},
{"Tags", "Теги"},
{"Incoming", "Входящие"},
@@ -198,10 +198,10 @@ namespace russian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"день", "дня", "дней"}},
{"hours", {"час", "часа", "часов"}},
{"minutes", {"минуту", "минуты", "минут"}},
{"seconds", {"секунду", "секунды", "секунд"}},
{"%d days", {"%d день", "%d дня", "%d дней"}},
{"%d hours", {"%d час", "%d часа", "%d часов"}},
{"%d minutes", {"%d минуту", "%d минуты", "%d минут"}},
{"%d seconds", {"%d секунду", "%d секунды", "%d секунд"}},
{"", {"", "", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace spanish // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "pendiente"},
{"failed", "fallido"},
{"expiring", "expiró"},
@@ -69,7 +69,7 @@ namespace spanish // language namespace
{"Family", "Familia"},
{"Tunnel creation success rate", "Tasa de éxito de creación de túneles"},
{"Received", "Recibido"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Enviado"},
{"Transit", "Tránsito"},
{"Data path", "Ruta de datos"},
@@ -95,7 +95,7 @@ namespace spanish // language namespace
{"Type", "Tipo"},
{"EncType", "TipoEncrip"},
{"Inbound tunnels", "Túneles entrantes"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Túneles salientes"},
{"Tags", "Etiquetas"},
{"Incoming", "Entrante"},
@@ -199,10 +199,10 @@ namespace spanish // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"día", "días"}},
{"hours", {"hora", "horas"}},
{"minutes", {"minuto", "minutos"}},
{"seconds", {"segundo", "segundos"}},
{"%d days", {"%d día", "%d días"}},
{"%d hours", {"%d hora", "%d horas"}},
{"%d minutes", {"%d minuto", "%d minutos"}},
{"%d seconds", {"%d segundo", "%d segundos"}},
{"", {"", ""}},
};

217
i18n/Swedish.cpp Normal file
View File

@@ -0,0 +1,217 @@
/*
* Copyright (c) 2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <map>
#include <vector>
#include <string>
#include <memory>
#include "I18N.h"
// Swedish localization file
namespace i2p
{
namespace i18n
{
namespace swedish // language namespace
{
// language name in lowercase
static std::string language = "swedish";
// See for language plural forms here:
// https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html
static int plural (int n) {
return n != 1 ? 1 : 0;
}
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "bygger"},
{"failed", "misslyckad"},
{"expiring", "utgår"},
{"established", "upprättad"},
{"unknown", "okänt"},
{"exploratory", "utforskande"},
{"Purple I2P Webconsole", "Purple I2P Webbkonsoll"},
{"<b>i2pd</b> webbkonsoll", "<b>i2pd</b>-Webbkonsoll"},
{"Main page", "Huvudsida"},
{"Router commands", "Routerkommandon"},
{"Local Destinations", "Lokala Platser"},
{"LeaseSets", "Hyresuppsättningar"},
{"Tunnels", "Tunnlar"},
{"Transit Tunnels", "Förmedlande Tunnlar"},
{"Transports", "Transporter"},
{"I2P tunnels", "I2P-tunnlar"},
{"SAM sessions", "SAM-perioder"},
{"ERROR", "FEL"},
{"OK", "OK"},
{"Testing", "Prövar"},
{"Firewalled", "Bakom Brandvägg"},
{"Unknown", "Okänt"},
{"Proxy", "Proxy"},
{"Mesh", "Mesh"},
{"Error", "Fel"},
{"Clock skew", "Tidsförskjutning"},
{"Offline", "Nedkopplad"},
{"Symmetric NAT", "Symmetrisk NAT"},
{"Uptime", "Upptid"},
{"Network status", "Nätverkstillstånd"},
{"Network status v6", "Nätverkstillstånd v6"},
{"Stopping in", "Avstängd om"},
{"Family", "Familj"},
{"Tunnel creation success rate", "Andel framgångsrika tunnlar"},
{"Received", "Mottaget"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Skickat"},
{"Transit", "Förmedlat"},
{"Data path", "Sökväg"},
{"Hidden content. Press on text to see.", "Dolt innehåll. Tryck för att visa."},
{"Router Ident", "Routeridentitet"},
{"Router Family", "Routerfamilj"},
{"Router Caps", "Routerbegränsningar"},
{"Version", "Version"},
{"Our external address", "Vår externa adress"},
{"supported", "stöds"},
{"Routers", "Routrar"},
{"Floodfills", "Översvämningsfyllare"},
{"Client Tunnels", "Klienttunnlar"},
{"Services", "Tjänster"},
{"Enabled", "Påslaget"},
{"Disabled", "Avslaget"},
{"Encrypted B33 address", "Krypterad B33-Adress"},
{"Address registration line", "Adressregistreringsrad"},
{"Domain", "Domän"},
{"Generate", "Skapa"},
{"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Uppmärksamma:</b> den resulterande strängen kan enbart användas för att registrera 2LD-domäner (exempel.i2p). För att registrera underdomäner, vänligen använd i2pd-tools."},
{"Address", "Adress"},
{"Type", "Typ"},
{"EncType", "EncTyp"},
{"Inbound tunnels", "Ingående Tunnlar"},
{"%dms", "%dms"},
{"Outbound tunnels", "Utgående Tunnlar"},
{"Tags", "Taggar"},
{"Incoming", "Ingående"},
{"Outgoing", "Utgående"},
{"Destination", "Plats"},
{"Amount", "Mängd"},
{"Incoming Tags", "Ingående Taggar"},
{"Tags sessions", "Tagg-perioder"},
{"Status", "Tillstånd"},
{"Local Destination", "Lokal Plats"},
{"Streams", "Strömmar"},
{"Close stream", "Stäng strömmen"},
{"I2CP session not found", "I2CP-period hittades inte"},
{"I2CP is not enabled", "I2CP är inte påslaget"},
{"Invalid", "Ogiltig"},
{"Store type", "Lagringstyp"},
{"Expires", "Utgångsdatum"},
{"Non Expired Leases", "Ickeutgångna Hyresuppsättningar"},
{"Gateway", "Gateway"},
{"TunnelID", "TunnelID"},
{"EndDate", "EndDate"},
{"not floodfill", "inte Översvämningsfyllare"},
{"Queue size", "Köstorlek"},
{"Run peer test", "Utför utsiktstest"},
{"Decline transit tunnels", "Avvisa förmedlande tunnlar"},
{"Accept transit tunnels", "Tillåt förmedlande tunnlar"},
{"Cancel graceful shutdown", "Avbryt välvillig avstängning"},
{"Start graceful shutdown", "Påbörja välvillig avstängning"},
{"Force shutdown", "Tvingad avstängning"},
{"Reload external CSS styles", "Ladda om externa CSS-stilar"},
{"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Uppmärksamma:</b> inga ändringar här är beständiga eller påverkar dina inställningsfiler."},
{"Logging level", "Protokollförningsnivå"},
{"Transit tunnels limit", "Begränsa förmedlande tunnlar"},
{"Change", "Ändra"},
{"Change language", "Ändra språk"},
{"no transit tunnels currently built", "inga förmedlande tunnlar har byggts"},
{"SAM disabled", "SAM avslaget"},
{"no sessions currently running", "inga perioder igång"},
{"SAM session not found", "SAM-perioder hittades ej"},
{"SAM Session", "SAM-period"},
{"Server Tunnels", "Värdtunnlar"},
{"Client Forwards", "Klientförpassningar"},
{"Server Forwards", "Värdförpassningar"},
{"Unknown page", "Okänd sida"},
{"Invalid token", "Ogiltig polett"},
{"SUCCESS", "FRAMGÅNG"},
{"Stream closed", "Ström stängd"},
{"Stream not found or already was closed", "Strömmen hittades inte eller var redan avslutad"},
{"Destination not found", "Plats hittades ej"},
{"StreamID can't be null", "Ström-ID kan inte vara null"},
{"Return to destination page", "Återvänd till platssidan"},
{"You will be redirected in 5 seconds", "Du omdirigeras inom fem sekunder"},
{"Transit tunnels count must not exceed 65535", "Förmedlande tunnlar får inte överstiga 65535"},
{"Back to commands list", "Tillbaka till kommandolistan"},
{"Register at reg.i2p", "Registrera vid reg.i2p"},
{"Description", "Beskrivning"},
{"A bit information about service on domain", "Ett stycke information om domänens tjänst"},
{"Submit", "Skicka"},
{"Domain can't end with .b32.i2p", "Domänen får inte sluta med .b32.i2p"},
{"Domain must end with .i2p", "Domänen måste sluta med .i2p"},
{"Such destination is not found", "En sådan plats hittas ej"},
{"Unknown command", "Okänt kommando"},
{"Command accepted", "Kommando accepterades"},
{"Proxy error", "Proxyfel"},
{"Proxy info", "Proxyinfo"},
{"Proxy error: Host not found", "Proxyfel: Värden hittades ej"},
{"Remote host not found in router's addressbook", "Främmande värd hittades inte i routerns adressbok"},
{"You may try to find this host on jump services below", "Du kan försöka att hitta värden genom hopptjänsterna nedan"},
{"Invalid request", "Ogiltig förfrågan"},
{"Proxy unable to parse your request", "Proxyt kan inte behandla din förfrågan"},
{"addresshelper is not supported", "adresshjälparen stöds ej"},
{"Host", "Värd"},
{"added to router's addressbook from helper", "tillagd i routerns adressbok från adresshjälparen"},
{"Click here to proceed:", "Tryck här för att fortsätta:"},
{"Continue", "Fortsätt"},
{"Addresshelper found", "Adresshjälpare hittad"},
{"already in router's addressbook", "finns redan i routerns adressbok"},
{"Click here to update record:", "Tryck här för att uppdatera:"},
{"invalid request uri", "ogiltig förfrågnings-URI"},
{"Can't detect destination host from request", "Kan inte upptäcka platsvärden från förfrågan"},
{"Outproxy failure", "Utproxyfel"},
{"bad outproxy settings", "ogiltig utproxyinställning"},
{"not inside I2P network, but outproxy is not enabled", "adressen är inte inom I2P-näverket, men utproxy är inte påslaget"},
{"unknown outproxy url", "okänt Utproxy-URL"},
{"cannot resolve upstream proxy", "hittar inte uppströmsproxyt"},
{"hostname too long", "värdnamnet är för långt"},
{"cannot connect to upstream socks proxy", "kan inte ansluta till uppströmsproxyt"},
{"Cannot negotiate with socks proxy", "Kan inte förhandla med socksproxyt"},
{"CONNECT error", "CONNECT-fel"},
{"Failed to Connect", "Anslutningen misslyckades"},
{"socks proxy error", "Socksproxyfel"},
{"failed to send request to upstream", "förfrågan uppströms kunde ej skickas"},
{"No Reply From socks proxy", "Fick inget svar från socksproxyt"},
{"cannot connect", "kan inte ansluta"},
{"http out proxy not implemented", "HTTP-Utproxy ej implementerat"},
{"cannot connect to upstream http proxy", "Kan inte ansluta till uppströms HTTP-proxy"},
{"Host is down", "Värden är nere"},
{"Can't create connection to requested host, it may be down. Please try again later.", "Kan inte ansluta till värden, den kan vara nere. Vänligen försök senare."},
{"", ""},
};
static std::map<std::string, std::vector<std::string>> plurals
{
{"%d days", {"%d Dag", "%d Dagar"}},
{"%d hours", {"%d Timme", "%d Timmar"}},
{"%d minutes", {"%d Minut", "%d Minuter"}},
{"%d seconds", {"%d Sekund", "%d Sekunder"}},
{"", {"", ""}},
};
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language
} // i18n
} // i2p

View File

@@ -31,9 +31,9 @@ namespace turkmen // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "bina"},
{"failed", "şowsuz"},
{"expiring", "möhleti gutarýar"},
@@ -68,7 +68,7 @@ namespace turkmen // language namespace
{"Family", "Maşgala"},
{"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"},
{"Received", "Alnan"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Ýerleşdirildi"},
{"Transit", "Tranzit"},
{"Data path", "Maglumat ýoly"},
@@ -94,7 +94,7 @@ namespace turkmen // language namespace
{"Type", "Görnüş"},
{"EncType", "Şifrlemek görnüşi"},
{"Inbound tunnels", "Gelýän tuneller"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Çykýan tuneller"},
{"Tags", "Bellikler"},
{"Incoming", "Gelýän"},
@@ -198,10 +198,10 @@ namespace turkmen // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"gün", "gün"}},
{"hours", {"sagat", "sagat"}},
{"minutes", {"minut", "minut"}},
{"seconds", {"sekunt", "sekunt"}},
{"%d days", {"%d gün", "%d gün"}},
{"%d hours", {"%d sagat", "%d sagat"}},
{"%d minutes", {"%d minut", "%d minut"}},
{"%d seconds", {"%d sekunt", "%d sekunt"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace ukrainian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "КіБ"},
{"MiB", "МіБ"},
{"GiB", "ГіБ"},
{"%.2f KiB", "%.2f КіБ"},
{"%.2f MiB", "%.2f МіБ"},
{"%.2f GiB", "%.2f ГіБ"},
{"building", "будується"},
{"failed", "невдалий"},
{"expiring", "завершується"},
@@ -68,7 +68,7 @@ namespace ukrainian // language namespace
{"Family", "Сімейство"},
{"Tunnel creation success rate", "Успішно побудованих тунелів"},
{"Received", "Отримано"},
{"KiB/s", "КіБ/с"},
{"%.2f KiB/s", "%.2f КіБ/с"},
{"Sent", "Відправлено"},
{"Transit", "Транзит"},
{"Data path", "Шлях до даних"},
@@ -94,7 +94,7 @@ namespace ukrainian // language namespace
{"Type", "Тип"},
{"EncType", "ТипШифр"},
{"Inbound tunnels", "Вхідні тунелі"},
{"ms", "мс"},
{"%dms", "%dмс"},
{"Outbound tunnels", "Вихідні тунелі"},
{"Tags", "Теги"},
{"Incoming", "Вхідні"},
@@ -198,10 +198,10 @@ namespace ukrainian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"день", "дня", "днів"}},
{"hours", {"годину", "години", "годин"}},
{"minutes", {"хвилину", "хвилини", "хвилин"}},
{"seconds", {"секунду", "секунди", "секунд"}},
{"%d days", {"%d день", "%d дня", "%d днів"}},
{"%d hours", {"%d годину", "%d години", "%d годин"}},
{"%d minutes", {"%d хвилину", "%d хвилини", "%d хвилин"}},
{"%d seconds", {"%d секунду", "%d секунди", "%d секунд"}},
{"", {"", "", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace uzbek // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "yaratilmoqda"},
{"failed", "muvaffaqiyatsiz"},
{"expiring", "muddati tugaydi"},
@@ -68,7 +68,7 @@ namespace uzbek // language namespace
{"Family", "Oila"},
{"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"},
{"Received", "Qabul qilindi"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Yuborilgan"},
{"Transit", "Tranzit"},
{"Data path", "Ma'lumotlar joylanishi"},
@@ -94,7 +94,7 @@ namespace uzbek // language namespace
{"Type", "Turi"},
{"EncType", "ShifrlashTuri"},
{"Inbound tunnels", "Kirish tunnellari"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Chiquvchi tunnellar"},
{"Tags", "Teglar"},
{"Incoming", "Kiruvchi"},
@@ -198,10 +198,10 @@ namespace uzbek // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"kun", "kun"}},
{"hours", {"soat", "soat"}},
{"minutes", {"daqiqa", "daqiqa"}},
{"seconds", {"soniya", "soniya"}},
{"%d days", {"%d kun", "%d kun"}},
{"%d hours", {"%d soat", "%d soat"}},
{"%d minutes", {"%d daqiqa", "%d daqiqa"}},
{"%d seconds", {"%d soniya", "%d soniya"}},
{"", {"", ""}},
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -1138,19 +1138,25 @@ namespace client
template<typename Dest>
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStreamSync (const Dest& dest, int port)
{
volatile bool done = false;
std::shared_ptr<i2p::stream::Stream> stream;
std::condition_variable streamRequestComplete;
std::mutex streamRequestCompleteMutex;
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
CreateStream (
[&streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr<i2p::stream::Stream> s)
[&done, &streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr<i2p::stream::Stream> s)
{
stream = s;
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
streamRequestComplete.notify_all ();
done = true;
},
dest, port);
streamRequestComplete.wait (l);
while (!done)
{
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
if (!done)
streamRequestComplete.wait (l);
}
return stream;
}

View File

@@ -126,7 +126,7 @@ namespace log {
if (level == "none") { m_MinLevel = eLogNone; }
else if (level == "error") { m_MinLevel = eLogError; }
else if (level == "warn") { m_MinLevel = eLogWarning; }
else if (level == "info") { m_MinLevel = eLogInfo; }
else if (level == "info") { m_MinLevel = eLogInfo; }
else if (level == "debug") { m_MinLevel = eLogDebug; }
else {
LogPrint(eLogError, "Log: Unknown loglevel: ", level);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -12,6 +12,7 @@
#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Timestamp.h"
#include "Profiling.h"
namespace i2p
@@ -22,6 +23,7 @@ namespace data
RouterProfile::RouterProfile ():
m_LastUpdateTime (boost::posix_time::second_clock::local_time()),
m_LastDeclineTime (0),
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
m_NumTimesTaken (0), m_NumTimesRejected (0)
{
@@ -131,15 +133,23 @@ namespace data
{
UpdateTime ();
if (ret > 0)
{
m_NumTunnelsDeclined++;
m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch ();
}
else
{
m_NumTunnelsAgreed++;
m_LastDeclineTime = 0;
}
}
void RouterProfile::TunnelNonReplied ()
{
m_NumTunnelsNonReplied++;
UpdateTime ();
if (m_NumTunnelsNonReplied > 2*m_NumTunnelsAgreed && m_NumTunnelsNonReplied > 3)
m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch ();
}
bool RouterProfile::IsLowPartcipationRate () const
@@ -153,8 +163,18 @@ namespace data
return m_NumTunnelsNonReplied > 10*(total + 1);
}
bool RouterProfile::IsDeclinedRecently ()
{
if (!m_LastDeclineTime) return false;
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL)
m_LastDeclineTime = 0;
return m_LastDeclineTime;
}
bool RouterProfile::IsBad ()
{
if (IsDeclinedRecently ()) return true;
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -31,6 +31,7 @@ namespace data
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days)
const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 24 * 3600; // in seconds (1 day)
const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3 * 3600; // in seconds (3 hours)
const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes)
class RouterProfile
{
@@ -55,10 +56,12 @@ namespace data
bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
bool IsLowPartcipationRate () const;
bool IsLowReplyRate () const;
bool IsDeclinedRecently ();
private:
boost::posix_time::ptime m_LastUpdateTime;
boost::posix_time::ptime m_LastUpdateTime; // TODO: use std::chrono
uint64_t m_LastDeclineTime; // in seconds
// participation
uint32_t m_NumTunnelsAgreed;
uint32_t m_NumTunnelsDeclined;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, The PurpleI2P Project
* Copyright (c) 2022-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -22,7 +22,7 @@ namespace transport
RunnableServiceWithWork ("SSU2"), m_ReceiveService ("SSU2r"),
m_SocketV4 (m_ReceiveService.GetService ()), m_SocketV6 (m_ReceiveService.GetService ()),
m_AddressV4 (boost::asio::ip::address_v4()), m_AddressV6 (boost::asio::ip::address_v6()),
m_TerminationTimer (GetService ()), m_ResendTimer (GetService ()),
m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()),
m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()),
m_IsPublished (true), m_IsSyncClockFromPeers (true), m_IsThroughProxy (false)
{
@@ -109,6 +109,7 @@ namespace transport
m_ReceiveService.Start ();
}
ScheduleTermination ();
ScheduleCleanup ();
ScheduleResend (false);
}
}
@@ -118,6 +119,7 @@ namespace transport
if (IsRunning ())
{
m_TerminationTimer.cancel ();
m_CleanupTimer.cancel ();
m_ResendTimer.cancel ();
m_IntroducersUpdateTimer.cancel ();
m_IntroducersUpdateTimerV6.cancel ();
@@ -807,6 +809,22 @@ namespace transport
it++;
}
ScheduleTermination ();
}
}
void SSU2Server::ScheduleCleanup ()
{
m_CleanupTimer.expires_from_now (boost::posix_time::seconds(SSU2_CLEANUP_INTERVAL));
m_CleanupTimer.async_wait (std::bind (&SSU2Server::HandleCleanupTimer,
this, std::placeholders::_1));
}
void SSU2Server::HandleCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Relays.begin (); it != m_Relays.begin ();)
{
if (it->second && it->second->GetState () == eSSU2SessionStateTerminated)
@@ -830,13 +848,15 @@ namespace transport
else
it++;
}
m_PacketsPool.CleanUpMt ();
m_SentPacketsPool.CleanUp ();
ScheduleTermination ();
}
}
m_IncompleteMessagesPool.CleanUp ();
m_FragmentsPool.CleanUp ();
ScheduleCleanup ();
}
}
void SSU2Server::ScheduleResend (bool more)
{
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? SSU2_RESEND_CHECK_MORE_TIMEOUT :

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, The PurpleI2P Project
* Copyright (c) 2022-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -18,7 +18,8 @@ namespace i2p
{
namespace transport
{
const int SSU2_TERMINATION_CHECK_TIMEOUT = 30; // in seconds
const int SSU2_TERMINATION_CHECK_TIMEOUT = 25; // in seconds
const int SSU2_CLEANUP_INTERVAL = 72; // in seconds
const int SSU2_RESEND_CHECK_TIMEOUT = 400; // in milliseconds
const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 100; // in milliseconds
const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 10; // in milliseconds
@@ -97,7 +98,9 @@ namespace transport
void RescheduleIntroducersUpdateTimerV6 ();
i2p::util::MemoryPool<SSU2SentPacket>& GetSentPacketsPool () { return m_SentPacketsPool; };
i2p::util::MemoryPool<SSU2IncompleteMessage>& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; };
i2p::util::MemoryPool<SSU2IncompleteMessage::Fragment>& GetFragmentsPool () { return m_FragmentsPool; };
private:
boost::asio::ip::udp::socket& OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint);
@@ -111,6 +114,9 @@ namespace transport
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
void ScheduleCleanup ();
void HandleCleanupTimer (const boost::system::error_code& ecode);
void ScheduleResend (bool more);
void HandleResendTimer (const boost::system::error_code& ecode);
@@ -147,7 +153,9 @@ namespace transport
std::list<i2p::data::IdentHash> m_Introducers, m_IntroducersV6; // introducers we are connected to
i2p::util::MemoryPoolMt<Packet> m_PacketsPool;
i2p::util::MemoryPool<SSU2SentPacket> m_SentPacketsPool;
boost::asio::deadline_timer m_TerminationTimer, m_ResendTimer,
i2p::util::MemoryPool<SSU2IncompleteMessage> m_IncompleteMessagesPool;
i2p::util::MemoryPool<SSU2IncompleteMessage::Fragment> m_FragmentsPool;
boost::asio::deadline_timer m_TerminationTimer, m_CleanupTimer, m_ResendTimer,
m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6;
std::shared_ptr<SSU2Session> m_LastSession;
bool m_IsPublished; // if we maintain introducers

View File

@@ -32,7 +32,52 @@ namespace transport
nextFragmentNum++;
}
bool SSU2IncompleteMessage::ConcatOutOfSequenceFragments ()
{
bool isLast = false;
while (outOfSequenceFragments)
{
if (outOfSequenceFragments->fragmentNum == nextFragmentNum)
{
AttachNextFragment (outOfSequenceFragments->buf, outOfSequenceFragments->len);
isLast = outOfSequenceFragments->isLast;
if (isLast)
outOfSequenceFragments = nullptr;
else
outOfSequenceFragments = outOfSequenceFragments->next;
}
else
break;
}
return isLast;
}
void SSU2IncompleteMessage::AddOutOfSequenceFragment (std::shared_ptr<SSU2IncompleteMessage::Fragment> fragment)
{
if (!fragment || !fragment->fragmentNum) return; // fragment 0 not allowed
if (fragment->fragmentNum < nextFragmentNum) return; // already processed
if (!outOfSequenceFragments)
outOfSequenceFragments = fragment;
else
{
auto frag = outOfSequenceFragments;
std::shared_ptr<Fragment> prev;
do
{
if (fragment->fragmentNum < frag->fragmentNum) break; // found
if (fragment->fragmentNum == frag->fragmentNum) return; // duplicate
prev = frag; frag = frag->next;
}
while (frag);
fragment->next = frag;
if (prev)
prev->next = fragment;
else
outOfSequenceFragments = fragment;
}
lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
}
SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter,
std::shared_ptr<const i2p::data::RouterInfo::Address> addr):
TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT),
@@ -1727,13 +1772,13 @@ namespace transport
}
else
{
m = std::make_shared<SSU2IncompleteMessage>();
m = m_Server.GetIncompleteMessagesPool ().AcquireShared ();
m_IncompleteMessages.emplace (msgID, m);
}
m->msg = msg;
m->nextFragmentNum = 1;
m->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
if (found && ConcatOutOfSequenceFragments (m))
if (found && m->ConcatOutOfSequenceFragments ())
{
// we have all follow-on fragments already
m->msg->FromNTCP2 ();
@@ -1746,11 +1791,17 @@ namespace transport
{
if (len < 5) return;
uint8_t fragmentNum = buf[0] >> 1;
if (!fragmentNum || fragmentNum >= SSU2_MAX_NUM_FRAGMENTS)
{
LogPrint (eLogWarning, "SSU2: Invalid follow-on fragment num ", fragmentNum);
return;
}
bool isLast = buf[0] & 0x01;
uint32_t msgID; memcpy (&msgID, buf + 1, 4);
auto it = m_IncompleteMessages.find (msgID);
if (it != m_IncompleteMessages.end ())
{
if (fragmentNum < it->second->nextFragmentNum) return; // duplicate
if (it->second->nextFragmentNum == fragmentNum && fragmentNum < SSU2_MAX_NUM_FRAGMENTS &&
it->second->msg)
{
@@ -1764,7 +1815,7 @@ namespace transport
}
else
{
if (ConcatOutOfSequenceFragments (it->second))
if (it->second->ConcatOutOfSequenceFragments ())
{
HandleI2NPMsg (std::move (it->second->msg));
m_IncompleteMessages.erase (it);
@@ -1778,38 +1829,17 @@ namespace transport
else
{
// follow-on fragment before first fragment
auto msg = std::make_shared<SSU2IncompleteMessage> ();
auto msg = m_Server.GetIncompleteMessagesPool ().AcquireShared ();
msg->nextFragmentNum = 0;
it = m_IncompleteMessages.emplace (msgID, msg).first;
}
// insert out of sequence fragment
if (fragmentNum >= SSU2_MAX_NUM_FRAGMENTS)
{
LogPrint (eLogWarning, "SSU2: Fragment number ", fragmentNum, " exceeds ", SSU2_MAX_NUM_FRAGMENTS);
return;
}
auto fragment = std::make_shared<SSU2IncompleteMessage::Fragment> ();
auto fragment = m_Server.GetFragmentsPool ().AcquireShared ();
memcpy (fragment->buf, buf + 5, len -5);
fragment->len = len - 5;
fragment->fragmentNum = fragmentNum;
fragment->isLast = isLast;
it->second->outOfSequenceFragments.emplace (fragmentNum, fragment);
it->second->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
}
bool SSU2Session::ConcatOutOfSequenceFragments (std::shared_ptr<SSU2IncompleteMessage> m)
{
if (!m) return false;
bool isLast = false;
for (auto it = m->outOfSequenceFragments.begin (); it != m->outOfSequenceFragments.end ();)
if (it->first == m->nextFragmentNum)
{
m->AttachNextFragment (it->second->buf, it->second->len);
isLast = it->second->isLast;
it = m->outOfSequenceFragments.erase (it);
}
else
break;
return isLast;
it->second->AddOutOfSequenceFragment (fragment);
}
void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len)
@@ -2271,23 +2301,16 @@ namespace transport
{
if (!msg) return;
int32_t msgID = msg->GetMsgID ();
#if __cplusplus >= 202002L // C++ 20 or higher
if (!m_ReceivedI2NPMsgIDs.contains (msgID))
#else
if (!m_ReceivedI2NPMsgIDs.count (msgID))
#endif
{
if (!msg->IsExpired ())
{
// m_LastActivityTimestamp is updated in ProcessData before
m_ReceivedI2NPMsgIDs.emplace (msgID, (uint32_t)m_LastActivityTimestamp);
if (!msg->IsExpired ())
{
// m_LastActivityTimestamp is updated in ProcessData before
if (m_ReceivedI2NPMsgIDs.emplace (msgID, (uint32_t)m_LastActivityTimestamp).second)
m_Handler.PutNextMessage (std::move (msg));
}
else
LogPrint (eLogDebug, "SSU2: Message ", msgID, " expired");
}
LogPrint (eLogDebug, "SSU2: Message ", msgID, " already received");
}
else
LogPrint (eLogDebug, "SSU2: Message ", msgID, " already received");
LogPrint (eLogDebug, "SSU2: Message ", msgID, " expired");
}
bool SSU2Session::ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep)

View File

@@ -171,15 +171,19 @@ namespace transport
{
uint8_t buf[SSU2_MAX_PACKET_SIZE];
size_t len;
int fragmentNum;
bool isLast;
std::shared_ptr<Fragment> next;
};
std::shared_ptr<I2NPMessage> msg;
int nextFragmentNum;
uint32_t lastFragmentInsertTime; // in seconds
std::map<int, std::shared_ptr<Fragment> > outOfSequenceFragments;
std::shared_ptr<Fragment> outOfSequenceFragments; // #1 and more
void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize);
bool ConcatOutOfSequenceFragments (); // true if message complete
void AddOutOfSequenceFragment (std::shared_ptr<Fragment> fragment);
};
struct SSU2SentPacket
@@ -306,7 +310,6 @@ namespace transport
bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate
void HandleFirstFragment (const uint8_t * buf, size_t len);
void HandleFollowOnFragment (const uint8_t * buf, size_t len);
bool ConcatOutOfSequenceFragments (std::shared_ptr<SSU2IncompleteMessage> m); // true if message complete
void HandleRelayRequest (const uint8_t * buf, size_t len);
void HandleRelayIntro (const uint8_t * buf, size_t len, int attempts = 0);
void HandleRelayResponse (const uint8_t * buf, size_t len);
@@ -342,7 +345,7 @@ namespace transport
uint32_t m_SendPacketNum, m_ReceivePacketNum;
std::set<uint32_t> m_OutOfSequencePackets; // packet nums > receive packet num
std::map<uint32_t, std::shared_ptr<SSU2SentPacket> > m_SentPackets; // packetNum -> packet
std::map<uint32_t, std::shared_ptr<SSU2IncompleteMessage> > m_IncompleteMessages; // I2NP
std::unordered_map<uint32_t, std::shared_ptr<SSU2IncompleteMessage> > m_IncompleteMessages; // msgID -> I2NP
std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice
std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_PeerTests; // same as for relay sessions
std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;

View File

@@ -332,8 +332,7 @@ namespace tunnel
Tunnels tunnels;
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr),
m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0)
{
m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0) {
}
Tunnels::~Tunnels ()
@@ -634,7 +633,7 @@ namespace tunnel
}
// delete
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
FailedTunnelCreation();
}
else
++it;
@@ -642,7 +641,7 @@ namespace tunnel
case eTunnelStateBuildFailed:
LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " failed, deleted");
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
FailedTunnelCreation();
break;
case eTunnelStateBuildReplyReceived:
// intermediate state, will be either established of build failed
@@ -651,7 +650,7 @@ namespace tunnel
default:
// success
it = pendingTunnels.erase (it);
m_NumSuccesiveTunnelCreations++;
SuccesiveTunnelCreation();
}
}
}

View File

@@ -48,6 +48,9 @@ namespace tunnel
const size_t I2NP_TUNNEL_MESSAGE_SIZE = TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34; // reserved for alignment and NTCP 16 + 6 + 12
const size_t I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE = 2*TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28; // reserved for alignment and NTCP 16 + 6 + 6
const double TCSR_SMOOTHING_CONSTANT = 0.0005; // smoothing constant in exponentially weighted moving average
const double TCSR_START_VALUE = 0.1; // start value of tunnel creation success rate
enum TunnelState
{
eTunnelStatePending,
@@ -267,8 +270,19 @@ namespace tunnel
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE> > m_I2NPTunnelEndpointMessagesMemoryPool;
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_MESSAGE_SIZE> > m_I2NPTunnelMessagesMemoryPool;
// some stats
int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations;
// Calculating of tunnel creation success rate
// A modified version of the EWMA algorithm, where alpha is increased at the beginning to accelerate similarity
void SuccesiveTunnelCreation() {
double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum;
m_TunnelCreationSuccessRate = alpha * 1 + (1 - alpha) * m_TunnelCreationSuccessRate;
};
void FailedTunnelCreation() {
double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum;
m_TunnelCreationSuccessRate = alpha * 0 + (1 - alpha) * m_TunnelCreationSuccessRate;
};
double m_TunnelCreationSuccessRate;
int m_TunnelCreationAttemptsNum;
public:
@@ -282,11 +296,7 @@ namespace tunnel
size_t CountOutboundTunnels() const;
int GetQueueSize () { return m_Queue.GetSize (); };
int GetTunnelCreationSuccessRate () const // in percents
{
int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations;
return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0;
}
int GetTunnelCreationSuccessRate () const { return std::round(m_TunnelCreationSuccessRate * 100); } // in percents
};
extern Tunnels tunnels;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -321,7 +321,7 @@ namespace proxy {
if (!m_Addresshelper)
{
LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected");
GenericProxyError(tr("Invalid request"), tr("addresshelper is not supported"));
GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported"));
return true;
}
@@ -333,24 +333,51 @@ namespace proxy {
}
else if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm)
{
const std::string referer_raw = m_ClientRequest.GetHeader("Referer");
i2p::http::URL referer_url;
if (!referer_raw.empty ())
{
referer_url.parse (referer_raw);
}
if (m_RequestURL.host != referer_url.host)
{
if (m_Confirm) // Attempt to forced overwriting by link with "&update=true" from harmful URL
{
LogPrint (eLogWarning, "HTTPProxy: Address update from addresshelper rejected for ", m_RequestURL.host, " (referer is ", m_RequestURL.host.empty() ? "empty" : "harmful", ")");
std::string full_url = m_RequestURL.to_string();
std::stringstream ss;
ss << tr("Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.",
m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str());
GenericProxyInfo(tr("Addresshelper forced update rejected"), ss.str());
}
else // Preventing unauthorized additions to the address book
{
LogPrint (eLogDebug, "HTTPProxy: Adding address from addresshelper for ", m_RequestURL.host, " (generate refer-base page)");
std::string full_url = m_RequestURL.to_string();
std::stringstream ss;
ss << tr("To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.",
m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str());
GenericProxyInfo(tr("Addresshelper request"), ss.str());
}
return true; /* request processed */
}
i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, jump);
LogPrint (eLogInfo, "HTTPProxy: Added address from addresshelper for ", m_RequestURL.host);
std::string full_url = m_RequestURL.to_string();
std::stringstream ss;
ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". ";
ss << tr("Click here to proceed:") << " <a href=\"" << full_url << "\">" << tr("Continue") << "</a>.";
GenericProxyInfo(tr("Addresshelper found"), ss.str());
ss << tr("Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.",
m_RequestURL.host.c_str(), full_url.c_str());
GenericProxyInfo(tr("Addresshelper adding"), ss.str());
return true; /* request processed */
}
else
{
std::string full_url = m_RequestURL.to_string();
std::stringstream ss;
ss << tr("Host") << " " << m_RequestURL.host << " <font color=red>" << tr("already in router's addressbook") << "</font>. ";
ss << tr(/* tr: The "record" means addressbook's record. That message appears when domain was already added to addressbook, but helper link is opened for it. */ "Click here to update record:" );
ss << " <a href=\"" << full_url << (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper=");
ss << jump << "&update=true\">" << tr("Continue") << "</a>.";
GenericProxyInfo(tr("Addresshelper found"), ss.str());
ss << tr("Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.",
m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str());
GenericProxyInfo(tr("Addresshelper update"), ss.str());
return true; /* request processed */
}
}
@@ -363,7 +390,7 @@ namespace proxy {
auto pos = uri.find(":");
if(pos == std::string::npos || pos == uri.size() - 1)
{
GenericProxyError(tr("Invalid request"), tr("invalid request uri"));
GenericProxyError(tr("Invalid request"), tr("Invalid request URI"));
return true;
}
else
@@ -423,10 +450,10 @@ namespace proxy {
if(m_ProxyURL.parse(m_OutproxyUrl))
ForwardToUpstreamProxy();
else
GenericProxyError(tr("Outproxy failure"), tr("bad outproxy settings"));
GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings"));
} else {
LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled");
std::stringstream ss; ss << tr("Host") << " " << dest_host << " " << tr("not inside I2P network, but outproxy is not enabled");
std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str());
GenericProxyError(tr("Outproxy failure"), ss.str());
}
return true;
@@ -515,13 +542,13 @@ namespace proxy {
else
{
/* unknown type, complain */
GenericProxyError(tr("unknown outproxy url"), m_ProxyURL.to_string());
GenericProxyError(tr("Unknown outproxy URL"), m_ProxyURL.to_string());
}
}
void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler)
{
if(ec) GenericProxyError(tr("cannot resolve upstream proxy"), ec.message());
if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message());
else handler(*it);
}
@@ -529,7 +556,7 @@ namespace proxy {
{
if(!ec) {
if(m_RequestURL.host.size() > 255) {
GenericProxyError(tr("hostname too long"), m_RequestURL.host);
GenericProxyError(tr("Hostname is too long"), m_RequestURL.host);
return;
}
uint16_t port = m_RequestURL.port;
@@ -556,13 +583,13 @@ namespace proxy {
reqsize += host.size();
m_socks_buf[++reqsize] = 0;
boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2));
} else GenericProxyError(tr("cannot connect to upstream socks proxy"), ec.message());
} else GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message());
}
void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred)
{
LogPrint(eLogDebug, "HTTPProxy: Upstream SOCKS handshake sent");
if(ec) GenericProxyError(tr("Cannot negotiate with socks proxy"), ec.message());
if(ec) GenericProxyError(tr("Cannot negotiate with SOCKS proxy"), ec.message());
else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2));
}
@@ -604,7 +631,7 @@ namespace proxy {
}
else
{
GenericProxyError(tr("CONNECT error"), tr("Failed to Connect"));
GenericProxyError(tr("CONNECT error"), tr("Failed to connect"));
}
}
@@ -615,7 +642,7 @@ namespace proxy {
m_send_buf = m_ClientResponse.to_string();
boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred)
{
if(ec) GenericProxyError(tr("socks proxy error"), ec.message());
if(ec) GenericProxyError(tr("SOCKS proxy error"), ec.message());
else HandoverToUpstreamProxy();
});
} else {
@@ -623,7 +650,7 @@ namespace proxy {
LogPrint(eLogDebug, "HTTPProxy: Send ", m_send_buf.size(), " bytes");
boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred)
{
if(ec) GenericProxyError(tr("failed to send request to upstream"), ec.message());
if(ec) GenericProxyError(tr("Failed to send request to upstream"), ec.message());
else HandoverToUpstreamProxy();
});
}
@@ -641,18 +668,18 @@ namespace proxy {
ss << "error code: ";
ss << (int) m_socks_buf[1];
std::string msg = ss.str();
GenericProxyError(tr("socks proxy error"), msg);
GenericProxyError(tr("SOCKS proxy error"), msg);
}
}
else GenericProxyError(tr("No Reply From socks proxy"), ec.message());
else GenericProxyError(tr("No reply from SOCKS proxy"), ec.message());
}
void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec)
{
if(!ec) {
LogPrint(eLogDebug, "HTTPProxy: Connected to http upstream");
GenericProxyError(tr("cannot connect"), tr("http out proxy not implemented"));
} else GenericProxyError(tr("cannot connect to upstream http proxy"), ec.message());
GenericProxyError(tr("Cannot connect"), tr("HTTP out proxy not implemented"));
} else GenericProxyError(tr("Cannot connect to upstream HTTP proxy"), ec.message());
}
/* will be called after some data received from client */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -440,18 +440,23 @@ namespace client
{
if (ecode != boost::asio::error::operation_aborted)
{
auto session = m_Owner.FindSession(m_ID);
if(session)
if (m_Socket.is_open ())
{
if (session->GetLocalDestination ()->IsReady ())
SendSessionCreateReplyOk ();
else
auto session = m_Owner.FindSession(m_ID);
if(session)
{
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
shared_from_this (), std::placeholders::_1));
if (session->GetLocalDestination ()->IsReady ())
SendSessionCreateReplyOk ();
else
{
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
shared_from_this (), std::placeholders::_1));
}
}
}
else
Terminate ("SAM: session socket closed");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2021, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -30,7 +30,7 @@ namespace client
{
const size_t SAM_SOCKET_BUFFER_SIZE = 8192;
const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds
const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds
const int SAM_SESSION_READINESS_CHECK_INTERVAL = 3; // in seconds
const char SAM_HANDSHAKE[] = "HELLO VERSION";
const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n";
const char SAM_HANDSHAKE_NOVERSION[] = "HELLO REPLY RESULT=NOVERSION\n";