diff --git a/CHANGELOG.md b/CHANGELOG.md index 48b4074..ae59b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,23 +5,48 @@ heading is the version number. ## 0.1.2 -Using it -- New **Conversations** page in the web UI and the terminal UI (key 3): who - talks to whom, with client, server, service and traffic. The terminal UI's - pages are now 1–9. -- The side menu no longer shows group headings that looked like buttons but - did nothing; groups are separated by lines. +Drill-down and naming +- **Show details** on any host, device or service opens a page about it: + traffic over time by application, who it talks to, services or clients, + countries and the latest flows. Everything on it can be clicked again to + keep drilling down; the browser's Back button returns. +- **Name it…** on any host or device names it on the spot; the name is + saved and shown everywhere. The Names box on Sources now shows examples. + +Countries and networks database +- Upload a database on the Sources page: MaxMind GeoLite2 or DB-IP Lite + `.mmdb` files (country or ASN) or an IP-to-ASN table (`.tsv`, `.tsv.gz`). + It is checked, saved and used for new traffic at once, without a + restart. The `.mmdb` reader was checked against MaxMind's own reader on + 60,000 lookups in six DB-IP databases with no difference. + +Simpler pages +- Top-N is one table: conversations (client, server, service, country) by + default; click a column heading to group by it, a number heading to + sort. The row of eleven tabs is gone; the less common groupings are in + **Group by**. +- "Who grew" is gone from the overview (web and terminal UI). +- Flow paths: labels, bars and flows can all be clicked; before, only the + thin bars could. +- The side menu no longer shows group headings that looked like buttons; + "Log out" no longer wraps. +- Disk in the side menu shows used and free space; hovering over the free + space shows how much the kept days of detail need at the current rate. + The old "days left" figure was an unreliable extrapolation. + +Capture - Windows local capture by name: `-capture Wi-Fi`, `-capture Ethernet` or - the number from `traffic66 interfaces`, instead of Npcap's - `\Device\NPF_{…}` names. `traffic66 interfaces` now shows each adapter's + the number from `traffic66 interfaces`, which now shows each adapter's Windows connection name and address. +- Commands copied from a web page or chat work: full-width spaces, quotes, + full-width dashes and a trailing full stop around words are ignored, and + a mistyped command gets a "did you mean" hint. Documentation -- README section "Local capture" rewritten with step-by-step instructions - for Windows, Linux and macOS, including what a Wi-Fi adapter can and - cannot see (13 languages). -- This changelog; releases now carry these notes instead of a list of pull - requests. +- README (13 languages): drill-down, naming, the database upload, local + capture step by step for Windows, Linux and macOS including what Wi-Fi + can and cannot see, new screenshots. +- This changelog; releases take their notes from it. ## 0.1.1 diff --git a/README.md b/README.md index 797cc6d..1e289c8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ in a web UI and in a terminal UI. flow records, encapsulation (GRE, IPIP, VXLAN, GENEVE, MPLS). - 13 languages in the web UI and the terminal UI. -![Overview: bandwidth by application compared with last week, who grew, top clients and services](docs/images/overview.png) +![Overview: bandwidth by application compared with last week, top clients and services](docs/images/overview.png) All screenshots come from `traffic66 demo`, a simulated company network that you can run yourself (see [Try the demo](#1-try-the-demo)). @@ -84,7 +84,8 @@ cd traffic66-windows-amd64 Open http://127.0.0.1:8066 and sign in as `admin` / `try66`. The demo builds a small company network with a day of history and live traffic from four simulated devices, including two incidents to find: start on **Overview**, -look at **Who grew**, and click your way from there. Stop it with Ctrl+C. +click a host in **Top clients**, choose **Show details**, and keep clicking +from there. Stop it with Ctrl+C. Demo data is kept in `traffic66-demo` next to the program; delete that folder to start the demo afresh. @@ -521,7 +522,12 @@ the sampling rate is not known yet. ## 7. Names, SNMP and your own networks -**Sources → Names** in the web UI takes one entry per line. It is saved as +The quickest way to name a host or a device: click its address on any page +and choose **Name it…**. Type the name and press Enter; it is saved at +once and shown everywhere instead of the bare address. + +For networks, interfaces and SNMP, **Sources → Names** in the web UI takes +one entry per line. It is saved as `inventory.txt` in the data directory, so you can also edit that file (see `inventory.txt.example`). Every line is optional. @@ -558,17 +564,27 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. Countries, networks and threat lists -Countries and network (AS) names need an IP-to-ASN table. Download the free -one from [iptoasn.com](https://iptoasn.com): +Countries and network (AS) names need a database that maps addresses to +them. Upload one in the web UI: **Sources → Countries and networks +database → Upload a database file…**. It is checked, saved in the data +directory and used for new traffic at once; no restart is needed. Traffic +already stored keeps the country it was saved with. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +Accepted files: -Any file in the same format works (tab separated: first address, last -address, AS number, country code, AS name; plain or gzip). Restart -traffic66 after replacing it; download a new one every month or so. +| File | Gives | Where to get it | +|---|---|---| +| DB-IP Lite country or ASN, `.mmdb` | country, or AS number and name | free, no account: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country or ASN, `.mmdb` | country, or AS number and name | free with a MaxMind account: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| IP-to-ASN table, `.tsv` or `.tsv.gz` | AS number, AS name and country | free: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +Upload a country database and an ASN database to get both; where several +are loaded, the `.mmdb` files take precedence for what they contain. New +versions come out monthly: upload the new file the same way to replace the +old one. + +Without the web UI, copy the file into the data directory as +`country.mmdb`, `asn.mmdb` or `asn.tsv.gz` and restart traffic66. Threat lists are plain text files with one address or network per line (text after `#` or `;` is ignored), saved as `/threats/.txt`, for @@ -592,6 +608,13 @@ port, an application, a country, a device — can be clicked: - **Show only this** / **Exclude this** adds a filter. Filters appear under the top bar and apply to every page until you remove them. - **Show its flow records** opens the matching individual flows. +- **Show details** (hosts, devices and services) opens a page about that + one host or service: its traffic over time by application, who it talks + to, which services or clients, countries and its latest flows. Every + value there can be clicked again, so you can keep drilling down; the + browser's Back button returns. +- **Name it…** (hosts and devices) gives the address a name, shown + everywhere from then on. - **Look it up online** opens the address or AS in a public lookup site. - **Copy** copies the value. @@ -599,15 +622,14 @@ Pages: | Page | What it answers | |---|---| -| Overview | How much traffic now and compared with last week, by application; what grew; top clients and services | -| Top-N | The top 66 of clients, servers, conversations, applications, ports, countries, networks, segments, devices, encapsulation or VLAN | -| Conversations | Who talks to whom: the top 66 client–server pairs with their service | +| Overview | How much traffic now and compared with last week, by application; top clients and services | +| Top-N | One table of the top 66: by default conversations (client, server, service, country). Click a blue column heading to group by it, a number heading to sort; **Group by** offers applications, networks, segments, devices, encapsulation and VLAN | | Flow paths | Which segment talks to which application in which country | | Geo & networks | Traffic by country and by network (AS) | | Threat intel | Hosts that talked to addresses on your threat lists, and how much they sent | | Flow records | Individual flows, newest first, with selectable columns | | Interface check | Flow numbers next to the interface counters, worst first, with reasons | -| Sources | Devices, sampling, loss, collectors, SNMP, and **Names** | +| Sources | Devices, sampling, loss, collectors, SNMP, the countries and networks database, and **Names** | Above the pages: time range (15 minutes to 30 days), an optional search box, automatic refresh every 30 seconds, and **Copy link**, which copies a @@ -619,12 +641,16 @@ Ranges longer than 6 hours start on a whole hour, so every number on the page counts exactly the same time: "24 hours" covers the last 24 whole hours plus the current one. Top-N over these ranges comes from hourly summaries; filters are not available there, and the page says so. Choose a -shorter range to filter. **Conversations** always reads the flow detail, so over long ranges -at high flow rates it can take a while; one hour is fastest. +shorter range to filter. Conversations always read the flow detail, so over long ranges +at high flow rates they can take a while; one hour is fastest. + +The side menu shows how much disk the data uses and how much is free; +hover over the free space to see how much the kept days of detail need at +the current rate (estimated once there is a day of data). -![Top-N: the top 66 clients of the last hour](docs/images/topn.png) +![Top-N: the top 66 conversations of the last hour](docs/images/topn.png) -![Conversations: who talks to whom, with the service](docs/images/conv.png) +![Details of one host: its traffic, who it talks to, services, countries and latest flows](docs/images/detail.png) ![Flow paths: which segment uses which application towards which country](docs/images/paths.png) @@ -646,13 +672,13 @@ traffic66 runs as another user, as a service does, use `-user` and `-password` instead. `-lang` picks the language (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`). -Keys: 1–9 pages, ↑↓ select, Enter actions on the selected value, f show +Keys: 1–8 pages, ↑↓ select, Enter actions on the selected value, f show only, x exclude, / search, t time range, c clear filters, w open the same view in a browser, q quit. ![Terminal UI: overview](docs/images/tui-overview.png) -![Terminal UI: Top-N clients](docs/images/tui-topn.png) +![Terminal UI: Top-N conversations](docs/images/tui-topn.png) ## 11. Local capture @@ -761,7 +787,7 @@ Options of `traffic66` and `traffic66 demo`: | `-sampling-wait` | `5m` | how long records wait for a sampling rate | | `-capture` | | capture on a local interface (repeatable) | | `-inventory` | `/inventory.txt` | names file | -| `-asn` | `/asn.tsv.gz` | IP-to-ASN table | +| `-asn` | `/asn.tsv.gz` | IP-to-ASN table (`.mmdb` files: upload them, or `/country.mmdb` and `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | extra threat list as `name=path` (repeatable) | | `-dns-upstream` | system resolver | DNS server for showing host names | | `-dns-rate` | `20` | reverse lookups per second at most | @@ -786,7 +812,7 @@ The data directory holds everything: | `traffic66.duckdb` | summaries, interface counters and the current hour | | `password` | login passwords (hashed) | | `inventory.txt` | names (**Sources → Names**) | -| `asn.tsv.gz`, `threats/` | lookup tables you added | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | countries and networks databases and threat lists you added | - **Backup**: stop traffic66 and copy the directory. Without stopping, copy `raw/`, `password` and `inventory.txt`; the current hour and the @@ -857,7 +883,7 @@ traffic66 then restarts instead of the machine running out of memory. | "waiting for the sampling rate" | The device has not sent its sampler options yet; most resend within minutes. If it never does, export them (`option sampler-table` on Cisco) or mark it `unsampled` in Names if it really is 1:1 | | Numbers lower than the interface counters | See **Interface check**: loss on the way, interfaces not sampled, or flows still in the device cache (active timeout longer than 60 s) | | Numbers higher than the interface counters | The same traffic sampled on two interfaces or two devices | -| No countries or networks | No IP-to-ASN table: see [Countries](#8-countries-networks-and-threat-lists) | +| No countries or networks ("Unknown") | No database loaded: upload one on **Sources**, see [Countries](#8-countries-networks-and-threat-lists) | | "The database reached its memory limit" on a page | Choose a shorter time range, or start with a larger `-memory`; details are in the log | | Forgot the password | `traffic66 passwd` on the traffic66 machine (add `-data` if traffic66 runs with it) | | `Conflicting lock is held` | Another traffic66 already uses this data directory | diff --git a/cmd/traffic66/args_test.go b/cmd/traffic66/args_test.go new file mode 100644 index 0000000..2b4cd6f --- /dev/null +++ b/cmd/traffic66/args_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestCleanArgs(t *testing.T) { + got := cleanArgs([]string{" interfaces'.", "-capture", "“Wi-Fi”", " ", "-listen", ""}) + want := []string{"interfaces", "-capture", "Wi-Fi", "-listen", ""} + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %q want %q", got, want) + } + if s := nearestCommand("interface"); s != "interfaces" { + t.Fatalf("nearest %q", s) + } + if s := nearestCommand("zzzzzz"); s != "" { + t.Fatalf("nearest %q", s) + } +} diff --git a/cmd/traffic66/main.go b/cmd/traffic66/main.go index e967b20..1757146 100644 --- a/cmd/traffic66/main.go +++ b/cmd/traffic66/main.go @@ -20,6 +20,8 @@ import ( "strings" "syscall" "time" + "unicode" + "unicode/utf8" "github.com/githubflyideas/traffic66/internal/api" "github.com/githubflyideas/traffic66/internal/auth" @@ -28,6 +30,7 @@ import ( "github.com/githubflyideas/traffic66/internal/dnsres" "github.com/githubflyideas/traffic66/internal/enrich" "github.com/githubflyideas/traffic66/internal/flow" + "github.com/githubflyideas/traffic66/internal/geo" "github.com/githubflyideas/traffic66/internal/pipeline" "github.com/githubflyideas/traffic66/internal/sim" "github.com/githubflyideas/traffic66/internal/snmp" @@ -54,10 +57,10 @@ Run "traffic66 -h" for the flags of a command. func main() { log.SetFlags(log.LstdFlags) - args := os.Args[1:] + args := cleanArgs(os.Args[1:]) cmd := "serve" if len(args) > 0 && !strings.HasPrefix(args[0], "-") { - cmd, args = args[0], args[1:] + cmd, args = strings.ToLower(args[0]), args[1:] } switch cmd { case "serve": @@ -84,12 +87,79 @@ func main() { case "help", "-h", "--help": fmt.Printf(usage, version) default: - fmt.Fprintf(os.Stderr, "unknown command %q\n\n", cmd) + fmt.Fprintf(os.Stderr, "unknown command %q", cmd) + if s := nearestCommand(cmd); s != "" { + fmt.Fprintf(os.Stderr, " (did you mean %q?)", s) + } + fmt.Fprint(os.Stderr, "\n\n") fmt.Printf(usage, version) os.Exit(2) } } +var commands = []string{"serve", "demo", "tui", "simulate", "passwd", "interfaces", "version", "help"} + +// cleanArgs undoes what copying a command from a web page or chat often adds: +// full-width or non-breaking spaces, quotes around a word, full-width dashes +// and a trailing full stop. Arguments that were only such characters are +// dropped; an empty argument is kept. +func cleanArgs(in []string) []string { + out := make([]string, 0, len(in)) + for _, orig := range in { + if orig == "" { // an empty value given on purpose, e.g. -listen "" + out = append(out, orig) + continue + } + a := strings.TrimFunc(orig, func(r rune) bool { + return unicode.IsSpace(r) || strings.ContainsRune("'\"`‘’“”「」『』", r) + }) + a = strings.TrimRight(a, ".。,,;;") + a = strings.TrimFunc(a, func(r rune) bool { + return unicode.IsSpace(r) || strings.ContainsRune("'\"`‘’“”「」『』", r) + }) + if strings.HasPrefix(a, "-") || strings.HasPrefix(a, "—") || strings.HasPrefix(a, "–") { + _, n := utf8.DecodeRuneInString(a) + a = "-" + a[n:] + } + if a != "" { + out = append(out, a) + } + } + return out +} + +// nearestCommand returns the command within two edits of s, if any. +func nearestCommand(s string) string { + best, bestD := "", 3 + for _, c := range commands { + if d := editDistance(s, c); d < bestD { + best, bestD = c, d + } + } + return best +} + +func editDistance(a, b string) int { + ra, rb := []rune(a), []rune(b) + prev := make([]int, len(rb)+1) + for j := range prev { + prev[j] = j + } + for i := 1; i <= len(ra); i++ { + cur := make([]int, len(rb)+1) + cur[0] = i + for j := 1; j <= len(rb); j++ { + cost := 1 + if ra[i-1] == rb[j-1] { + cost = 0 + } + cur[j] = min(prev[j]+1, cur[j-1]+1, prev[j-1]+cost) + } + prev = cur + } + return prev[len(rb)] +} + type multiFlag []string func (m *multiFlag) String() string { return strings.Join(*m, ",") } @@ -175,8 +245,22 @@ func serve(args []string, demo bool) { } else { log.Printf("ASN table: %d ranges from %s", asn.Size(), asnPath) } - } else { - log.Printf("no ASN/country table; put one at %s to see countries and networks", filepath.Join(f.data, "asn.tsv.gz")) + } + for _, name := range []string{"country.mmdb", "asn.mmdb"} { + p := filepath.Join(f.data, name) + if _, err := os.Stat(p); err != nil { + continue + } + r, err := geo.Open(p) + if err != nil { + log.Printf("%s: %v", p, err) + continue + } + asn.SetMMDB(r, name) + log.Printf("geo database %s: %s", name, r.Type) + } + if !asn.Loaded() { + log.Printf("no country/ASN database; upload one on the Sources page to see countries and networks") } thr := enrich.NewThreats() threatFiles := map[string]string{} diff --git a/docs/README.ar.md b/docs/README.ar.md index d4fd65a..2c7524e 100644 --- a/docs/README.ar.md +++ b/docs/README.ar.md @@ -20,7 +20,7 @@ التهديدات، وسجلات التدفق، والتغليف (GRE وIPIP وVXLAN وGENEVE وMPLS). - 13 لغة في واجهة الويب والواجهة الطرفية. -![نظرة عامة: استهلاك عرض النطاق حسب التطبيق مقارنةً بالأسبوع الماضي، وما الذي زاد، وأبرز العملاء والخدمات](images/overview.png) +![نظرة عامة: استهلاك عرض النطاق حسب التطبيق مقارنةً بالأسبوع الماضي، وأبرز العملاء والخدمات](images/overview.png) جميع لقطات الشاشة مأخوذة من `traffic66 demo`، وهي شبكة شركة محاكاة يمكنك تشغيلها بنفسك (انظر [جرّب العرض التوضيحي](#1-try-the-demo)). @@ -89,8 +89,8 @@ cd traffic66-windows-amd64 افتح http://127.0.0.1:8066 وسجّل الدخول باسم `admin` وكلمة المرور `try66`. ينشئ العرض التوضيحي شبكة شركة صغيرة بسجلّ يوم كامل وحركة حية من أربعة أجهزة -محاكاة، وفيها حادثتان عليك اكتشافهما: ابدأ من **نظرة عامة**، وانظر إلى -**من زادت حركته**، ثم تابع بالنقر. أوقفه بـ Ctrl+C. تُحفظ بيانات العرض في +محاكاة، وفيها حادثتان عليك اكتشافهما: ابدأ من **نظرة عامة**، وانقر على +مضيف في **أكثر العملاء**، واختر **عرض التفاصيل**، ثم تابع النقر من هناك. أوقفه بـ Ctrl+C. تُحفظ بيانات العرض في `traffic66-demo` بجوار البرنامج؛ احذف هذا المجلد لتبدأ العرض من جديد. يستخدم العرض التوضيحي المنافذ نفسها التي يستخدمها التثبيت الفعلي (8066، @@ -548,7 +548,11 @@ NetFlow/IPFIX (عدّادات الواجهات تتضمنه، وعدّ التد ## 7. الأسماء وSNMP وشبكاتك الخاصة -تقبل **المصادر ← الأسماء** في واجهة الويب إدخالًا واحدًا في كل سطر. يُحفظ +أسرع طريقة لتسمية مضيف أو جهاز: انقر على عنوانه في أي صفحة واختر **تسمية…**. +اكتب الاسم واضغط Enter؛ يُحفظ فورًا ويظهر في كل مكان بدلًا من العنوان المجرد. + +أما للشبكات والواجهات وSNMP، فتقبل **المصادر ← الأسماء** في واجهة الويب +إدخالًا واحدًا في كل سطر. يُحفظ المحتوى باسم `inventory.txt` في دليل البيانات، لذا يمكنك تعديل هذا الملف مباشرة أيضًا (انظر `inventory.txt.example`). كل سطر اختياري. @@ -586,17 +590,25 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. الدول والشبكات وقوائم التهديدات -تحتاج أسماء الدول والشبكات (AS) إلى جدول IP-to-ASN. نزّل الجدول المجاني من -[iptoasn.com](https://iptoasn.com): +تحتاج أسماء الدول والشبكات (AS) إلى قاعدة بيانات تربط العناوين بها. ارفعها من +واجهة الويب: **المصادر ← قاعدة بيانات الدول والشبكات ← رفع ملف قاعدة بيانات…**. +يُفحص الملف ويُحفظ في دليل البيانات ويُستخدم للحركة الجديدة فورًا؛ ولا حاجة +إلى إعادة التشغيل. أما الحركة المخزّنة من قبل فتحتفظ بالدولة التي حُفظت بها. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +الملفات المقبولة: -يصلح أي ملف بالتنسيق نفسه (مفصول بعلامات الجدولة: العنوان الأول، العنوان -الأخير، رقم AS، رمز الدولة، اسم AS؛ نص عادي أو gzip). أعد تشغيل traffic66 -بعد استبداله؛ ونزّل نسخة جديدة كل شهر تقريبًا. +| الملف | ما يقدّمه | من أين تحصل عليه | +|---|---|---| +| DB-IP Lite للدول أو ASN، `.mmdb` | الدولة، أو رقم AS واسمه | مجاني، دون حساب: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country أو ASN، `.mmdb` | الدولة، أو رقم AS واسمه | مجاني مع حساب MaxMind: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| جدول IP-to-ASN، `.tsv` أو `.tsv.gz` | رقم AS واسم AS والدولة | مجاني: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +ارفع قاعدة بيانات للدول وأخرى لـ ASN لتحصل على الاثنين معًا؛ وعند تحميل عدة +ملفات، تكون الأولوية لملفات `.mmdb` فيما تحتويه. تصدر نسخ جديدة كل شهر: ارفع +الملف الجديد بالطريقة نفسها ليحلّ محل القديم. + +دون واجهة الويب، انسخ الملف إلى دليل البيانات باسم `country.mmdb` أو +`asn.mmdb` أو `asn.tsv.gz` وأعد تشغيل traffic66. قوائم التهديدات ملفات نصية عادية فيها عنوان أو شبكة في كل سطر (يُتجاهل النص بعد `#` أو `;`)، تُحفظ في `/threats/.txt`، @@ -622,6 +634,11 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh - **اعرض هذا فقط** / **استبعد هذا** يضيف مرشّحًا. تظهر المرشّحات أسفل الشريط العلوي وتنطبق على كل الصفحات حتى تزيلها. - **اعرض سجلات تدفقه** يفتح التدفقات الفردية المطابقة. +- **عرض التفاصيل** (المضيفات والأجهزة والخدمات) يفتح صفحة عن ذلك المضيف أو + الخدمة وحده: حركته عبر الزمن حسب التطبيق، ومن يتواصل معه، وأي خدمات أو + عملاء، والدول، وأحدث تدفقاته. كل قيمة هناك قابلة للنقر من جديد، فتستطيع + مواصلة التعمق؛ ويعيدك زر الرجوع في المتصفح. +- **تسمية…** (المضيفات والأجهزة) تمنح العنوان اسمًا يظهر في كل مكان من بعدها. - **ابحث عنه على الإنترنت** يفتح العنوان أو AS في موقع استعلام عام. - **نسخ** ينسخ القيمة. @@ -629,15 +646,14 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh | الصفحة | ما الذي تجيب عنه | |---|---| -| نظرة عامة | حجم الحركة الآن ومقارنةً بالأسبوع الماضي حسب التطبيق؛ ما الذي زاد؛ أبرز العملاء والخدمات | -| Top-N | أعلى 66 من العملاء أو الخوادم أو المحادثات أو التطبيقات أو المنافذ أو الدول أو الشبكات أو المقاطع أو الأجهزة أو أنواع التغليف أو شبكات VLAN | -| المحادثات | من يتواصل مع من: أعلى 66 زوجًا من العملاء والخوادم مع الخدمة المستخدمة | +| نظرة عامة | حجم الحركة الآن ومقارنةً بالأسبوع الماضي حسب التطبيق؛ أبرز العملاء والخدمات | +| Top-N | جدول واحد لأعلى 66: افتراضيًا المحادثات (العميل والخادم والخدمة والدولة). انقر عنوان عمود أزرق للتجميع حسبه، أو عنوان عمود رقمي للفرز؛ ويتيح **التجميع حسب** التطبيقات والشبكات والمقاطع والأجهزة وأنواع التغليف وشبكات VLAN | | مسارات الحركة | أي مقطع يتواصل مع أي تطبيق في أي دولة | | الجغرافيا والشبكات | الحركة حسب الدولة وحسب الشبكة (AS) | | معلومات التهديدات | المضيفات التي تواصلت مع عناوين في قوائم تهديداتك، وكم أرسلت | | سجلات التدفق | التدفقات الفردية، الأحدث أولًا، مع أعمدة قابلة للاختيار | | مطابقة الواجهات | أرقام التدفقات بجوار عدّادات الواجهات، الأسوأ أولًا، مع الأسباب | -| المصادر | الأجهزة، وأخذ العينات، والفقد، والمستقبِلات، وSNMP، و**الأسماء** | +| المصادر | الأجهزة، وأخذ العينات، والفقد، والمستقبِلات، وSNMP، وقاعدة بيانات الدول والشبكات، و**الأسماء** | فوق الصفحات: النطاق الزمني (من 15 دقيقة إلى 30 يومًا)، ومربع بحث اختياري، وتحديث تلقائي كل 30 ثانية، و**نسخ الرابط** الذي ينسخ رابطًا إلى العرض @@ -647,13 +663,17 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh تبدأ النطاقات الأطول من 6 ساعات عند ساعة كاملة، كي يحسب كل رقم في الصفحة الفترة الزمنية نفسها تمامًا: يغطي "24 ساعة" آخر 24 ساعة كاملة إضافةً إلى الساعة الجارية. تُبنى Top-N لهذه النطاقات من ملخّصات ساعية؛ والمرشّحات غير -متاحة فيها، وتنبّه الصفحة إلى ذلك. اختر نطاقًا أقصر لتتمكن من التصفية. تقرأ **المحادثات** تفاصيل +متاحة فيها، وتنبّه الصفحة إلى ذلك. اختر نطاقًا أقصر لتتمكن من التصفية. تقرأ المحادثات تفاصيل التدفقات دائمًا، لذا قد تستغرق بعض الوقت في النطاقات الطويلة عند معدلات التدفق العالية؛ ونطاق الساعة الواحدة هو الأسرع. -![Top-N: أعلى 66 عميلًا في الساعة الأخيرة](images/topn.png) +تعرض القائمة الجانبية المساحة التي تشغلها البيانات على القرص والمساحة الحرة؛ +مرّر المؤشر فوق المساحة الحرة لترى ما تحتاجه أيام التفاصيل المحفوظة بالمعدل +الحالي (يُقدَّر ذلك بعد توفر بيانات يوم كامل). + +![Top-N: أعلى 66 محادثة في الساعة الأخيرة](images/topn.png) -![المحادثات: من يتواصل مع من، ومع أي خدمة](images/conv.png) +![تفاصيل مضيف واحد: حركته، ومن يتواصل معه، والخدمات، والدول، وأحدث التدفقات](images/detail.png) ![مسارات الحركة: أي مقطع يستخدم أي تطبيق نحو أي دولة](images/paths.png) @@ -677,13 +697,13 @@ traffic66 -tui # collect and show the termina `-password` بدلًا من ذلك. يختار `-lang` اللغة (`en`، `zh`، `hi`، `es`، `ar`، `fr`، `bn`، `pt`، `ru`، `id`، `ur`، `ja`، `ko`). -المفاتيح: 1–9 للصفحات، ↑↓ للتحديد، Enter لإجراءات القيمة المحددة، f لعرض +المفاتيح: 1–8 للصفحات، ↑↓ للتحديد، Enter لإجراءات القيمة المحددة، f لعرض هذا فقط، x للاستبعاد، / للبحث، t للنطاق الزمني، c لمسح المرشّحات، w لفتح العرض نفسه في المتصفح، q للخروج. ![الواجهة الطرفية: نظرة عامة](images/tui-overview.png) -![الواجهة الطرفية: أعلى العملاء في Top-N](images/tui-topn.png) +![الواجهة الطرفية: أعلى المحادثات في Top-N](images/tui-topn.png) @@ -805,7 +825,7 @@ sudo traffic66 -capture en0 | `-sampling-wait` | `5m` | مدة انتظار السجلات لمعدّل أخذ العينات | | `-capture` | | الالتقاط على واجهة محلية (يمكن تكراره) | | `-inventory` | `/inventory.txt` | ملف الأسماء | -| `-asn` | `/asn.tsv.gz` | جدول IP-to-ASN | +| `-asn` | `/asn.tsv.gz` | جدول IP-to-ASN (ملفات `.mmdb`: ارفعها، أو `/country.mmdb` و`/asn.mmdb`) | | `-threat` | `/threats/*.txt` | قائمة تهديدات إضافية بصيغة `name=path` (يمكن تكراره) | | `-dns-upstream` | محلّل النظام | خادم DNS لعرض أسماء المضيفات | | `-dns-rate` | `20` | الحد الأقصى لاستعلامات DNS العكسية في الثانية | @@ -832,7 +852,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | الملخّصات وعدّادات الواجهات والساعة الجارية | | `password` | كلمات مرور تسجيل الدخول (مُجزّأة) | | `inventory.txt` | الأسماء (**المصادر ← الأسماء**) | -| `asn.tsv.gz`، `threats/` | جداول الاستعلام التي أضفتها | +| `country.mmdb`، `asn.mmdb`، `asn.tsv.gz`، `threats/` | قواعد بيانات الدول والشبكات وقوائم التهديدات التي أضفتها | - **النسخ الاحتياطي**: أوقف traffic66 وانسخ الدليل. من دون إيقافه، انسخ `raw/` و`password` و`inventory.txt`؛ وعندها لن تتضمن النسخة الساعة @@ -907,7 +927,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | "waiting for the sampling rate" | لم يرسل الجهاز خيارات أخذ العينات بعد؛ ومعظم الأجهزة تعيد إرسالها خلال دقائق. إن لم يفعل أبدًا، فصدّرها (`option sampler-table` على Cisco) أو علّم الجهاز بـ `unsampled` في الأسماء إن كان يصدّر فعلًا بنسبة 1:1 | | الأرقام أقل من عدّادات الواجهات | انظر **مطابقة الواجهات**: فقد في الطريق، أو واجهات لا تؤخذ منها عينات، أو تدفقات ما زالت في ذاكرة الجهاز المؤقتة (مهلة التدفق النشط أطول من 60 ثانية) | | الأرقام أعلى من عدّادات الواجهات | تؤخذ عينات الحركة نفسها على واجهتين أو جهازين | -| لا توجد دول أو شبكات | لا يوجد جدول IP-to-ASN: انظر [الدول](#8-countries-networks-and-threat-lists) | +| لا توجد دول أو شبكات ("غير معروف") | لم تُحمَّل أي قاعدة بيانات: ارفع واحدة في **المصادر**، وانظر [الدول](#8-countries-networks-and-threat-lists) | | ظهور "بلغت قاعدة البيانات حد الذاكرة ولم تتمكن من الإجابة" في صفحة | اختر مدة أقصر، أو شغّل البرنامج بقيمة `-memory` أكبر؛ التفاصيل في السجل | | نسيت كلمة المرور | `traffic66 passwd` على جهاز traffic66 (أضف `-data` إذا كان traffic66 يعمل به) | | `Conflicting lock is held` | نسخة أخرى من traffic66 تستخدم دليل البيانات هذا | diff --git a/docs/README.bn.md b/docs/README.bn.md index b93049c..1f6f964 100644 --- a/docs/README.bn.md +++ b/docs/README.bn.md @@ -18,7 +18,7 @@ database-এ রাখে, এবং দেখায় কে bandwidth ব্ record, encapsulation (GRE, IPIP, VXLAN, GENEVE, MPLS)। - web UI ও terminal UI-তে 13টি ভাষা। -![সারসংক্ষেপ: গত সপ্তাহের তুলনায় application অনুযায়ী bandwidth, কী বেড়েছে, শীর্ষ client ও service](images/overview.png) +![সারসংক্ষেপ: গত সপ্তাহের তুলনায় application অনুযায়ী bandwidth, শীর্ষ client ও service](images/overview.png) সব স্ক্রিনশট `traffic66 demo` থেকে নেওয়া, একটি simulated কোম্পানি নেটওয়ার্ক যা আপনি নিজেই চালাতে পারেন ([ডেমো চালিয়ে দেখুন](#1-try-the-demo) দেখুন)। @@ -88,8 +88,8 @@ cd traffic66-windows-amd64 http://127.0.0.1:8066 খুলুন এবং `admin` / `try66` দিয়ে সাইন ইন করুন। ডেমো একটি ছোট কোম্পানির নেটওয়ার্ক তৈরি করে, যাতে এক দিনের ইতিহাস আর চারটি simulated ডিভাইস থেকে live ট্রাফিক থাকে, সাথে খুঁজে বের করার মতো দুটি -incident: **সারসংক্ষেপ** থেকে শুরু করুন, **কার ট্রাফিক বেড়েছে** দেখুন, তারপর -ক্লিক করে করে এগোন। Ctrl+C দিয়ে বন্ধ করুন। ডেমোর ডেটা প্রোগ্রামের পাশে +incident: **সারসংক্ষেপ** থেকে শুরু করুন, **শীর্ষ ক্লায়েন্ট**-এ একটি host-এ ক্লিক করুন, +**বিস্তারিত দেখুন** বেছে নিন, তারপর ক্লিক করে করে এগোন। Ctrl+C দিয়ে বন্ধ করুন। ডেমোর ডেটা প্রোগ্রামের পাশে `traffic66-demo`-তে থাকে; ডেমো নতুন করে শুরু করতে ওই ফোল্ডারটি মুছে দিন। ডেমো আসল installation-এর মতো একই port ব্যবহার করে (8066, এবং UDP 6343, @@ -551,7 +551,12 @@ packet traffic66-এ পৌঁছানোর আগেই হারাচ্ছ ## 7. নাম, SNMP ও আপনার নিজের নেটওয়ার্ক -web UI-র **উৎস → নাম** প্রতি লাইনে একটি entry নেয়। এটি data directory-তে +কোনো host বা ডিভাইসের নাম দেওয়ার সবচেয়ে দ্রুত উপায়: যেকোনো পেজে তার address-এ +ক্লিক করে **নাম দিন…** বেছে নিন। নাম টাইপ করে Enter চাপুন; সেটি সঙ্গে সঙ্গে +সেভ হয় এবং সব জায়গায় খালি address-এর বদলে দেখানো হয়। + +নেটওয়ার্ক, interface ও SNMP-এর জন্য web UI-র **উৎস → নাম** প্রতি লাইনে একটি +entry নেয়। এটি data directory-তে `inventory.txt` হিসেবে সেভ হয়, তাই আপনি ফাইলটি সরাসরিও edit করতে পারেন (`inventory.txt.example` দেখুন)। প্রতিটি লাইন ঐচ্ছিক। @@ -589,17 +594,26 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. দেশ, নেটওয়ার্ক ও threat list -দেশ ও নেটওয়ার্ক (AS)-এর নামের জন্য একটি IP-to-ASN table লাগে। -[iptoasn.com](https://iptoasn.com) থেকে বিনামূল্যের table ডাউনলোড করুন: +দেশ ও নেটওয়ার্ক (AS)-এর নামের জন্য এমন একটি database লাগে যা address থেকে +সেগুলো বের করে। web UI-তে সেটি আপলোড করুন: **উৎস → দেশ ও নেটওয়ার্ক ডেটাবেস → ডেটাবেস ফাইল আপলোড করুন…**। +ফাইলটি যাচাই করা হয়, data directory-তে সেভ হয় এবং নতুন ট্রাফিকের জন্য সঙ্গে +সঙ্গে ব্যবহার হয়; restart লাগে না। আগে থেকে সংরক্ষিত ট্রাফিক যে দেশ নিয়ে সেভ +হয়েছিল সেটিই রাখে। -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +গ্রহণযোগ্য ফাইল: -একই format-এর যেকোনো ফাইল চলবে (tab দিয়ে আলাদা: প্রথম address, শেষ address, -AS number, country code, AS name; plain বা gzip)। বদলানোর পর traffic66 -restart করুন; মোটামুটি প্রতি মাসে নতুন একটি ডাউনলোড করুন। +| ফাইল | কী দেয় | কোথায় পাবেন | +|---|---|---| +| DB-IP Lite country বা ASN, `.mmdb` | দেশ, অথবা AS number ও নাম | বিনামূল্যে, account ছাড়া: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country বা ASN, `.mmdb` | দেশ, অথবা AS number ও নাম | MaxMind account থাকলে বিনামূল্যে: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| IP-to-ASN table, `.tsv` বা `.tsv.gz` | AS number, AS name ও দেশ | বিনামূল্যে: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +দুটোই পেতে একটি country database ও একটি ASN database আপলোড করুন; একাধিক লোড +থাকলে, `.mmdb` ফাইলগুলোতে যা আছে সে বিষয়ে সেগুলোই অগ্রাধিকার পায়। নতুন +version প্রতি মাসে বের হয়: পুরোনোটি বদলাতে নতুন ফাইল একইভাবে আপলোড করুন। + +web UI ছাড়া, ফাইলটি data directory-তে `country.mmdb`, `asn.mmdb` বা +`asn.tsv.gz` নামে কপি করুন এবং traffic66 restart করুন। Threat list হলো সাধারণ text ফাইল, প্রতি লাইনে একটি address বা নেটওয়ার্ক (`#` বা `;`-এর পরের লেখা উপেক্ষা করা হয়), সেভ করা হয় @@ -625,6 +639,13 @@ application, দেশ, ডিভাইস — ক্লিক করা যা - **শুধু এটি দেখান** / **এটি বাদ দিন** একটি filter যোগ করে। Filter top bar-এর নিচে দেখা যায় এবং সরিয়ে না দেওয়া পর্যন্ত প্রতিটি পেজে প্রযোজ্য থাকে। - **এর ফ্লো রেকর্ড দেখুন** মিলে যাওয়া আলাদা আলাদা flow খোলে। +- **বিস্তারিত দেখুন** (host, ডিভাইস ও service) ওই একটি host বা service সম্পর্কে + একটি পেজ খোলে: সময়ের সাথে application অনুযায়ী তার ট্রাফিক, সে কার সাথে কথা + বলে, কোন service বা client, দেশ এবং তার সর্বশেষ flow। সেখানকার প্রতিটি value + আবার ক্লিক করা যায়, তাই আরও গভীরে যেতে পারেন; browser-এর Back বোতাম ফিরিয়ে + আনে। +- **নাম দিন…** (host ও ডিভাইস) address-টিকে একটি নাম দেয়, যা তারপর থেকে সব + জায়গায় দেখানো হয়। - **অনলাইনে খুঁজুন** address বা AS কোনো public lookup সাইটে খোলে। - **কপি করুন** value-টি কপি করে। @@ -632,15 +653,14 @@ application, দেশ, ডিভাইস — ক্লিক করা যা | পেজ | কোন প্রশ্নের উত্তর দেয় | |---|---| -| সারসংক্ষেপ | এখন কত ট্রাফিক আর গত সপ্তাহের তুলনায় কত, application অনুযায়ী; কী বেড়েছে; শীর্ষ client ও service | -| Top-N | client, server, conversation, application, port, দেশ, নেটওয়ার্ক, segment, ডিভাইস, encapsulation বা VLAN-এর শীর্ষ 66 | -| কথোপকথন | কে কার সাথে কথা বলে: শীর্ষ 66 client–server জোড়া, তাদের service সহ | +| সারসংক্ষেপ | এখন কত ট্রাফিক আর গত সপ্তাহের তুলনায় কত, application অনুযায়ী; শীর্ষ client ও service | +| Top-N | শীর্ষ 66-এর একটি table: ডিফল্টভাবে conversation (client, server, service, দেশ)। কোনো নীল column heading-এ ক্লিক করলে সেটি অনুযায়ী group হয়, সংখ্যার heading-এ ক্লিক করলে sort হয়; **গ্রুপের ভিত্তি**-তে application, নেটওয়ার্ক, segment, ডিভাইস, encapsulation ও VLAN পাওয়া যায় | | ট্রাফিকের পথ | কোন segment কোন দেশের কোন application-এর সাথে কথা বলে | | ভূগোল ও নেটওয়ার্ক | দেশ ও নেটওয়ার্ক (AS) অনুযায়ী ট্রাফিক | | হুমকির তথ্য | যেসব host আপনার threat list-এর address-এর সাথে কথা বলেছে, এবং কতটা পাঠিয়েছে | | ফ্লো রেকর্ড | আলাদা আলাদা flow, নতুনগুলো আগে, বেছে নেওয়া যায় এমন column সহ | | ইন্টারফেস মিলানো | interface counter-এর পাশে flow-এর সংখ্যা, সবচেয়ে খারাপগুলো আগে, কারণসহ | -| উৎস | ডিভাইস, sampling, loss, collector, SNMP, এবং **নাম** | +| উৎস | ডিভাইস, sampling, loss, collector, SNMP, দেশ ও নেটওয়ার্ক ডেটাবেস, এবং **নাম** | পেজগুলোর ওপরে: time range (15 মিনিট থেকে 30 দিন), একটি ঐচ্ছিক search box, প্রতি 30 সেকেন্ডে automatic refresh, এবং **লিংক কপি করুন**, যা ঠিক বর্তমান @@ -650,12 +670,16 @@ view-এর (পেজ, time range ও filter) link কপি করে, যা 6 ঘণ্টার চেয়ে লম্বা range পূর্ণ ঘণ্টা থেকে শুরু হয়, যাতে পেজের প্রতিটি সংখ্যা ঠিক একই সময় গোনে: "24 ঘণ্টা" মানে শেষ 24টি পূর্ণ ঘণ্টা আর চলতি ঘণ্টা। এই range-গুলোতে Top-N আসে ঘণ্টাভিত্তিক summary থেকে; সেখানে filter -পাওয়া যায় না, আর পেজেই তা বলা থাকে। filter করতে ছোট range বেছে নিন। **কথোপকথন** সবসময় flow-এর বিস্তারিত +পাওয়া যায় না, আর পেজেই তা বলা থাকে। filter করতে ছোট range বেছে নিন। কথোপকথন (conversation) সবসময় flow-এর বিস্তারিত তথ্য পড়ে, তাই উচ্চ flow rate-এ লম্বা range-এ কিছুটা সময় লাগতে পারে; এক ঘণ্টা সবচেয়ে দ্রুত। -![Top-N: গত এক ঘণ্টার শীর্ষ 66 client](images/topn.png) +পাশের menu দেখায় data কতটা disk ব্যবহার করছে আর কতটা খালি আছে; খালি জায়গার +ওপর hover করলে দেখা যায় বর্তমান হারে রাখা দিনগুলোর বিস্তারিত তথ্যের জন্য কতটা +জায়গা লাগবে (এক দিনের data জমা হলে হিসাব করা হয়)। + +![Top-N: গত এক ঘণ্টার শীর্ষ 66 conversation](images/topn.png) -![কথোপকথন: কে কার সাথে কথা বলে, service সহ](images/conv.png) +![একটি host-এর বিস্তারিত: তার ট্রাফিক, সে কার সাথে কথা বলে, service, দেশ ও সর্বশেষ flow](images/detail.png) ![ট্রাফিকের পথ: কোন segment কোন দেশের দিকে কোন application ব্যবহার করে](images/paths.png) @@ -679,13 +703,13 @@ service হিসেবে চলে, তার বদলে `-user` ও `-passw ভাষা বেছে নেয় (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`)। -Key: 1–9 পেজ, ↑↓ বাছাই, Enter বাছাই করা value-র action, f শুধু এটি দেখান, +Key: 1–8 পেজ, ↑↓ বাছাই, Enter বাছাই করা value-র action, f শুধু এটি দেখান, x বাদ দিন, / search, t time range, c filter মুছুন, w একই view browser-এ খুলুন, q বেরিয়ে যান। ![Terminal UI: সারসংক্ষেপ](images/tui-overview.png) -![Terminal UI: Top-N client](images/tui-topn.png) +![Terminal UI: Top-N conversation](images/tui-topn.png) @@ -806,7 +830,7 @@ port-এ) দুবার গোনা হয়। | `-sampling-wait` | `5m` | record কতক্ষণ sampling rate-এর জন্য অপেক্ষা করবে | | `-capture` | | local interface-এ capture (একাধিকবার দেওয়া যায়) | | `-inventory` | `/inventory.txt` | নামের ফাইল | -| `-asn` | `/asn.tsv.gz` | IP-to-ASN table | +| `-asn` | `/asn.tsv.gz` | IP-to-ASN table (`.mmdb` ফাইল: সেগুলো আপলোড করুন, অথবা `/country.mmdb` ও `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | অতিরিক্ত threat list, `name=path` আকারে (একাধিকবার দেওয়া যায়) | | `-dns-upstream` | system resolver | host name দেখানোর জন্য DNS server | | `-dns-rate` | `20` | প্রতি সেকেন্ডে সর্বোচ্চ reverse lookup | @@ -833,7 +857,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | summary, interface counter ও চলতি ঘণ্টা | | `password` | login পাসওয়ার্ড (hashed) | | `inventory.txt` | নাম (**উৎস → নাম**) | -| `asn.tsv.gz`, `threats/` | আপনার যোগ করা lookup table | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | আপনার যোগ করা দেশ ও নেটওয়ার্ক database এবং threat list | - **ব্যাকআপ**: traffic66 বন্ধ করে directory কপি করুন। বন্ধ না করে কপি করলে `raw/`, `password` ও `inventory.txt` কপি করুন; তখন চলতি ঘণ্টা আর summary বাদ @@ -908,7 +932,7 @@ container-এর memory limit। `-memory`-র ভাগের প্রায | "waiting for the sampling rate" | ডিভাইস এখনও sampler options পাঠায়নি; বেশিরভাগই কয়েক মিনিটের মধ্যে আবার পাঠায়। কখনো না পাঠালে সেগুলো export করান (Cisco-তে `option sampler-table`) অথবা সত্যিই 1:1 হলে নাম-এ সেটিকে `unsampled` চিহ্নিত করুন | | সংখ্যা interface counter-এর চেয়ে কম | **ইন্টারফেস মিলানো** দেখুন: পথে loss, interface sample হচ্ছে না, অথবা flow এখনও ডিভাইসের cache-এ (active timeout 60 s-এর বেশি) | | সংখ্যা interface counter-এর চেয়ে বেশি | একই ট্রাফিক দুটি interface বা দুটি ডিভাইসে sample হচ্ছে | -| কোনো দেশ বা নেটওয়ার্ক নেই | IP-to-ASN table নেই: [দেশ](#8-countries-networks-and-threat-lists) দেখুন | +| কোনো দেশ বা নেটওয়ার্ক নেই ("অজানা") | কোনো database লোড করা নেই: **উৎস**-এ একটি আপলোড করুন, [দেশ](#8-countries-networks-and-threat-lists) দেখুন | | পেজে "ডেটাবেস তার মেমরি সীমায় পৌঁছেছে এবং উত্তর দিতে পারেনি" | ছোট সময়সীমা বেছে নিন, অথবা বড় `-memory` দিয়ে চালু করুন; বিস্তারিত log-এ আছে | | পাসওয়ার্ড ভুলে গেছেন | traffic66 মেশিনে `traffic66 passwd` (traffic66 `-data` দিয়ে চললে `-data` যোগ করুন) | | `Conflicting lock is held` | অন্য একটি traffic66 ইতিমধ্যে এই data directory ব্যবহার করছে | diff --git a/docs/README.es.md b/docs/README.es.md index 7e13e27..4dd3d49 100644 --- a/docs/README.es.md +++ b/docs/README.es.md @@ -19,7 +19,7 @@ propios equipos, tanto en una interfaz web como en una interfaz de terminal. MPLS). - 13 idiomas en la interfaz web y en la de terminal. -![Resumen: ancho de banda por aplicación frente a la semana pasada, qué ha crecido, principales clientes y servicios](images/overview.png) +![Resumen: ancho de banda por aplicación frente a la semana pasada, principales clientes y servicios](images/overview.png) Todas las capturas de pantalla proceden de `traffic66 demo`, una red de empresa simulada que puede ejecutar usted mismo (vea [Probar la demo](#1-try-the-demo)). @@ -89,8 +89,9 @@ cd traffic66-windows-amd64 Abra http://127.0.0.1:8066 e inicie sesión como `admin` / `try66`. La demo monta la red de una pequeña empresa con un día de historial y tráfico en vivo de cuatro equipos simulados, e incluye dos incidentes por descubrir: -empiece en **Resumen**, mire **Quién ha crecido** y siga haciendo clic a -partir de ahí. Deténgala con Ctrl+C. Los datos de la demo se guardan en +empiece en **Resumen**, haga clic en un host de **Principales clientes**, +elija **Ver detalles** y siga haciendo clic a partir de ahí. Deténgala con +Ctrl+C. Los datos de la demo se guardan en `traffic66-demo`, junto al programa; borre esa carpeta para empezar la demo desde cero. @@ -561,7 +562,13 @@ antes de llegar a traffic66 o aún no se conoce la tasa de muestreo. ## 7. Nombres, SNMP y redes propias -**Fuentes → Nombres** en la interfaz web admite una entrada por línea. Se +La forma más rápida de ponerle nombre a un host o a un equipo: haga clic en +su dirección en cualquier página y elija **Ponerle nombre…**. Escriba el +nombre y pulse Enter; se guarda al momento y se muestra en todas partes en +lugar de la dirección sin más. + +Para redes, interfaces y SNMP, **Fuentes → Nombres** en la interfaz web +admite una entrada por línea. Se guarda como `inventory.txt` en el directorio de datos, así que también puede editar ese archivo (vea `inventory.txt.example`). Todas las líneas son opcionales. @@ -602,18 +609,28 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. Países, redes y listas de amenazas -Los países y los nombres de red (AS) necesitan una tabla IP-a-ASN. Descargue -la gratuita de [iptoasn.com](https://iptoasn.com): +Los países y los nombres de red (AS) necesitan una base de datos que asocie +las direcciones con ellos. Súbala en la interfaz web: **Fuentes → Base de +datos de países y redes → Subir un archivo de base de datos…**. Se +comprueba, se guarda en el directorio de datos y se usa al momento para el +tráfico nuevo; no hace falta reiniciar. El tráfico ya almacenado conserva el +país con el que se guardó. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +Archivos admitidos: -Sirve cualquier archivo con el mismo formato (separado por tabuladores: -primera dirección, última dirección, número de AS, código de país, nombre -del AS; en texto plano o gzip). Reinicie traffic66 después de sustituirlo; -descargue uno nuevo más o menos cada mes. +| Archivo | Aporta | Dónde conseguirlo | +|---|---|---| +| DB-IP Lite country o ASN, `.mmdb` | país, o número y nombre de AS | gratis, sin cuenta: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country o ASN, `.mmdb` | país, o número y nombre de AS | gratis con una cuenta de MaxMind: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| Tabla IP-a-ASN, `.tsv` o `.tsv.gz` | número de AS, nombre de AS y país | gratis: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +Suba una base de datos de países y otra de ASN para tener ambas cosas; si +hay varias cargadas, los archivos `.mmdb` tienen prioridad en lo que +contienen. Cada mes salen versiones nuevas: suba el archivo nuevo de la +misma manera para sustituir al anterior. + +Sin la interfaz web, copie el archivo en el directorio de datos como +`country.mmdb`, `asn.mmdb` o `asn.tsv.gz` y reinicie traffic66. Las listas de amenazas son archivos de texto plano con una dirección o red por línea (se ignora lo que va tras `#` o `;`), guardados como @@ -641,6 +658,13 @@ dirección, un puerto, una aplicación, un país, un equipo) admite clic: que los quite. - **Ver sus registros de flujo** abre los flujos individuales correspondientes. +- **Ver detalles** (hosts, equipos y servicios) abre una página sobre ese + host o servicio: su tráfico en el tiempo por aplicación, con quién habla, + qué servicios o clientes, países y sus últimos flujos. Ahí también se + puede hacer clic en cualquier valor, así que puede seguir profundizando; + el botón Atrás del navegador vuelve. +- **Ponerle nombre…** (hosts y equipos) le da un nombre a la dirección, que + se muestra en todas partes a partir de entonces. - **Buscarlo en línea** abre la dirección o el AS en un sitio público de consulta. - **Copiar** copia el valor. @@ -649,15 +673,14 @@ Páginas: | Página | Qué responde | |---|---| -| Resumen | Cuánto tráfico hay ahora y frente a la semana pasada, por aplicación; qué ha crecido; principales clientes y servicios | -| Top-N | Los 66 primeros clientes, servidores, conversaciones, aplicaciones, puertos, países, redes, segmentos, equipos, encapsulaciones o VLAN | -| Conversaciones | Quién habla con quién: los 66 primeros pares cliente–servidor con su servicio | +| Resumen | Cuánto tráfico hay ahora y frente a la semana pasada, por aplicación; principales clientes y servicios | +| Top-N | Una sola tabla de los 66 primeros: por defecto, conversaciones (cliente, servidor, servicio, país). Haga clic en un encabezado de columna azul para agrupar por él, o en un encabezado numérico para ordenar; **Agrupar por** ofrece aplicaciones, redes, segmentos, equipos, encapsulación y VLAN | | Rutas de tráfico | Qué segmento habla con qué aplicación en qué país | | Geografía y redes | Tráfico por país y por red (AS) | | Inteligencia de amenazas | Hosts que hablaron con direcciones de sus listas de amenazas y cuánto enviaron | | Registros de flujo | Flujos individuales, del más reciente al más antiguo, con columnas seleccionables | | Verificación de interfaces | Cifras de flujo junto a los contadores de interfaz, de peor a mejor, con motivos | -| Fuentes | Equipos, muestreo, pérdidas, colectores, SNMP y **Nombres** | +| Fuentes | Equipos, muestreo, pérdidas, colectores, SNMP, la base de datos de países y redes, y **Nombres** | Encima de las páginas: rango de tiempo (de 15 minutos a 30 días), un cuadro de búsqueda opcional, refresco automático cada 30 segundos y **Copiar enlace**, que copia un enlace exactamente a la vista actual @@ -668,12 +691,17 @@ Los rangos de más de 6 horas empiezan en una hora en punto, de modo que todas las cifras de la página cuentan exactamente el mismo tiempo: "24 horas" abarca las últimas 24 horas completas más la actual. En estos rangos, Top-N sale de resúmenes horarios; ahí no hay filtros, y la página lo indica. Elija -un rango más corto para filtrar. **Conversaciones** siempre lee el detalle de flujos, -así que en rangos largos con muchos flujos puede tardar; una hora es lo más rápido. +un rango más corto para filtrar. Las conversaciones siempre leen el detalle de flujos, +así que en rangos largos con muchos flujos pueden tardar; una hora es lo más rápido. + +El menú lateral muestra cuánto disco usan los datos y cuánto queda libre; +pase el ratón por el espacio libre para ver cuánto necesitan, al ritmo +actual, los días de detalle que se conservan (se estima en cuanto hay un +día de datos). -![Top-N: los 66 primeros clientes de la última hora](images/topn.png) +![Top-N: las 66 primeras conversaciones de la última hora](images/topn.png) -![Conversaciones: quién habla con quién, con el servicio](images/conv.png) +![Detalles de un host: su tráfico, con quién habla, servicios, países y últimos flujos](images/detail.png) ![Rutas de tráfico: qué segmento usa qué aplicación hacia qué país](images/paths.png) @@ -697,13 +725,13 @@ defecto). Si traffic66 se ejecuta con otro usuario, como ocurre con un servicio, use `-user` y `-password`. `-lang` elige el idioma (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`). -Teclas: 1–9 páginas, ↑↓ seleccionar, Enter acciones sobre el valor +Teclas: 1–8 páginas, ↑↓ seleccionar, Enter acciones sobre el valor seleccionado, f mostrar solo, x excluir, / buscar, t rango de tiempo, c quitar filtros, w abrir la misma vista en un navegador, q salir. ![Interfaz de terminal: resumen](images/tui-overview.png) -![Interfaz de terminal: Top-N de clientes](images/tui-topn.png) +![Interfaz de terminal: Top-N de conversaciones](images/tui-topn.png) @@ -828,7 +856,7 @@ Opciones de `traffic66` y `traffic66 demo`: | `-sampling-wait` | `5m` | cuánto esperan los registros a una tasa de muestreo | | `-capture` | | captura en una interfaz local (repetible) | | `-inventory` | `/inventory.txt` | archivo de nombres | -| `-asn` | `/asn.tsv.gz` | tabla IP-a-ASN | +| `-asn` | `/asn.tsv.gz` | tabla IP-a-ASN (archivos `.mmdb`: súbalos, o `/country.mmdb` y `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | lista de amenazas adicional como `name=path` (repetible) | | `-dns-upstream` | resolver del sistema | servidor DNS para mostrar nombres de host | | `-dns-rate` | `20` | máximo de resoluciones inversas por segundo | @@ -855,7 +883,7 @@ El directorio de datos lo contiene todo: | `traffic66.duckdb` | resúmenes, contadores de interfaz y la hora en curso | | `password` | contraseñas de acceso (con hash) | | `inventory.txt` | nombres (**Fuentes → Nombres**) | -| `asn.tsv.gz`, `threats/` | tablas de consulta que haya añadido | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | bases de datos de países y redes y listas de amenazas que haya añadido | - **Copia de seguridad**: detenga traffic66 y copie el directorio. Sin detenerlo, copie `raw/`, `password` e `inventory.txt`; en ese caso faltan @@ -935,7 +963,7 @@ máquina sin memoria. | "waiting for the sampling rate" | El equipo aún no ha enviado sus opciones de sampler; la mayoría las reenvía en pocos minutos. Si no lo hace nunca, expórtelas (`option sampler-table` en Cisco) o márquelo como `unsampled` en Nombres si de verdad es 1:1 | | Cifras por debajo de los contadores de interfaz | Vea **Verificación de interfaces**: pérdidas por el camino, interfaces sin muestrear o flujos aún en la caché del equipo (timeout activo superior a 60 s) | | Cifras por encima de los contadores de interfaz | El mismo tráfico se muestrea en dos interfaces o en dos equipos | -| No hay países ni redes | Falta la tabla IP-a-ASN: vea [Países](#8-countries-networks-and-threat-lists) | +| No hay países ni redes ("Desconocido") | No hay ninguna base de datos cargada: suba una en **Fuentes**; vea [Países](#8-countries-networks-and-threat-lists) | | "La base de datos llegó a su límite de memoria y no pudo responder" en una página | Elija un intervalo más corto o inicie con un `-memory` mayor; los detalles están en el registro | | Contraseña olvidada | `traffic66 passwd` en la máquina de traffic66 (añada `-data` si traffic66 se ejecuta con él) | | `Conflicting lock is held` | Otro traffic66 ya usa este directorio de datos | diff --git a/docs/README.fr.md b/docs/README.fr.md index 4215e72..8d29282 100644 --- a/docs/README.fr.md +++ b/docs/README.fr.md @@ -21,7 +21,7 @@ terminal. IPIP, VXLAN, GENEVE, MPLS). - 13 langues dans l'interface web et dans l'interface terminal. -![Vue d'ensemble : bande passante par application comparée à la semaine dernière, ce qui a augmenté, principaux clients et services](images/overview.png) +![Vue d'ensemble : bande passante par application comparée à la semaine dernière, principaux clients et services](images/overview.png) Toutes les captures d'écran proviennent de `traffic66 demo`, un réseau d'entreprise simulé que vous pouvez lancer vous-même (voir [Essayer la démo](#1-try-the-demo)). @@ -92,7 +92,8 @@ Ouvrez http://127.0.0.1:8066 et connectez-vous avec `admin` / `try66`. La démo construit le réseau d'une petite entreprise avec une journée d'historique et du trafic en direct provenant de quatre équipements simulés, dont deux incidents à trouver : commencez par -**Vue d'ensemble**, regardez **Qui a augmenté**, puis cliquez de proche en +**Vue d'ensemble**, cliquez sur un hôte dans **Principaux clients**, +choisissez **Voir les détails** et continuez à cliquer de proche en proche. Arrêtez-la avec Ctrl+C. Les données de la démo sont conservées dans `traffic66-demo` à côté du programme ; supprimez ce dossier pour repartir de zéro. @@ -575,7 +576,13 @@ d'échantillonnage n'est pas encore connu. ## 7. Noms, SNMP et vos propres réseaux -**Sources → Noms** dans l'interface web accepte une entrée par ligne. Le +Le moyen le plus rapide de nommer un hôte ou un équipement : cliquez sur son +adresse sur n'importe quelle page et choisissez **Nommer…**. Tapez le nom +et appuyez sur Entrée ; il est enregistré aussitôt et affiché partout à la +place de l'adresse brute. + +Pour les réseaux, les interfaces et SNMP, **Sources → Noms** dans +l'interface web accepte une entrée par ligne. Le contenu est enregistré sous `inventory.txt` dans le répertoire de données ; vous pouvez donc aussi éditer ce fichier (voir `inventory.txt.example`). Toutes les lignes sont facultatives. @@ -617,18 +624,29 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. Pays, réseaux et listes de menaces -Les pays et les noms de réseau (AS) nécessitent une table IP-vers-ASN. -Téléchargez la table gratuite de [iptoasn.com](https://iptoasn.com) : +Les pays et les noms de réseau (AS) nécessitent une base de données qui +associe les adresses à ces informations. Importez-en une dans l'interface +web : **Sources → Base de données pays et réseaux → Importer un fichier de +base de données…**. Le fichier est vérifié, enregistré dans le répertoire +de données et utilisé aussitôt pour le nouveau trafic ; aucun redémarrage +n'est nécessaire. Le trafic déjà stocké garde le pays avec lequel il a été +enregistré. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +Fichiers acceptés : -Tout fichier au même format convient (séparé par des tabulations : première -adresse, dernière adresse, numéro d'AS, code pays, nom de l'AS ; brut ou -gzip). Redémarrez traffic66 après l'avoir remplacé ; téléchargez-en un -nouveau environ tous les mois. +| Fichier | Fournit | Où le trouver | +|---|---|---| +| DB-IP Lite country ou ASN, `.mmdb` | pays, ou numéro et nom d'AS | gratuit, sans compte : [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country ou ASN, `.mmdb` | pays, ou numéro et nom d'AS | gratuit avec un compte MaxMind : [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| Table IP-vers-ASN, `.tsv` ou `.tsv.gz` | numéro d'AS, nom d'AS et pays | gratuit : [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +Importez une base pays et une base ASN pour avoir les deux ; lorsque +plusieurs sont chargées, les fichiers `.mmdb` l'emportent pour ce qu'ils +contiennent. De nouvelles versions sortent chaque mois : importez le +nouveau fichier de la même manière pour remplacer l'ancien. + +Sans l'interface web, copiez le fichier dans le répertoire de données sous +le nom `country.mmdb`, `asn.mmdb` ou `asn.tsv.gz` et redémarrez traffic66. Les listes de menaces sont des fichiers texte avec une adresse ou un réseau par ligne (le texte après `#` ou `;` est ignoré), enregistrés sous @@ -656,6 +674,14 @@ chaque page (adresse, port, application, pays, équipement) est cliquable : pages jusqu'à ce que vous les retiriez. - **Voir ses enregistrements de flux** ouvre les flux individuels correspondants. +- **Voir les détails** (hôtes, équipements et services) ouvre une page + consacrée à cet hôte ou à ce service : son trafic dans le temps par + application, avec qui il communique, quels services ou clients, les pays + et ses derniers flux. Chaque valeur y est à nouveau cliquable, ce qui + permet de creuser toujours plus loin ; le bouton Retour du navigateur + ramène en arrière. +- **Nommer…** (hôtes et équipements) donne un nom à l'adresse, affiché + partout par la suite. - **Rechercher en ligne** ouvre l'adresse ou l'AS sur un site public de recherche. - **Copier** copie la valeur. @@ -664,15 +690,14 @@ Pages : | Page | À quoi elle répond | |---|---| -| Vue d'ensemble | Combien de trafic maintenant et par rapport à la semaine dernière, par application ; ce qui a augmenté ; principaux clients et services | -| Top-N | Le top 66 des clients, serveurs, conversations, applications, ports, pays, réseaux, segments, équipements, encapsulations ou VLAN | -| Conversations | Qui parle à qui : le top 66 des paires client–serveur avec leur service | +| Vue d'ensemble | Combien de trafic maintenant et par rapport à la semaine dernière, par application ; principaux clients et services | +| Top-N | Un seul tableau du top 66 : par défaut les conversations (client, serveur, service, pays). Cliquez sur un en-tête de colonne bleu pour regrouper par cette colonne, sur un en-tête numérique pour trier ; **Regrouper par** propose applications, réseaux, segments, équipements, encapsulation et VLAN | | Chemins du trafic | Quel segment parle à quelle application dans quel pays | | Géographie et réseaux | Trafic par pays et par réseau (AS) | | Menaces | Hôtes ayant communiqué avec des adresses de vos listes de menaces, et volume envoyé | | Enregistrements de flux | Flux individuels, du plus récent au plus ancien, avec colonnes au choix | | Contrôle des interfaces | Chiffres de flux à côté des compteurs d'interface, les pires en premier, avec les causes | -| Sources | Équipements, échantillonnage, pertes, collecteurs, SNMP et **Noms** | +| Sources | Équipements, échantillonnage, pertes, collecteurs, SNMP, la base de données pays et réseaux, et **Noms** | Au-dessus des pages : la plage de temps (de 15 minutes à 30 jours), un champ de recherche facultatif, le rafraîchissement automatique toutes les @@ -685,12 +710,17 @@ chaque chiffre de la page porte exactement sur la même durée : "24 heures" couvre les 24 dernières heures entières plus l'heure en cours. Sur ces plages, Top-N s'appuie sur des agrégats horaires ; les filtres n'y sont pas disponibles, et la page le signale. Choisissez une plage plus courte pour -filtrer. **Conversations** lit toujours le détail des flux : sur de longues plages +filtrer. Les conversations lisent toujours le détail des flux : sur de longues plages avec beaucoup de flux, cela peut prendre un moment ; une heure est le plus rapide. -![Top-N : le top 66 des clients de la dernière heure](images/topn.png) +Le menu latéral indique l'espace disque utilisé par les données et l'espace +libre ; survolez l'espace libre pour voir ce dont les jours de détail +conservés ont besoin au rythme actuel (estimation disponible dès qu'il y a +une journée de données). + +![Top-N : le top 66 des conversations de la dernière heure](images/topn.png) -![Conversations : qui parle à qui, avec le service](images/conv.png) +![Détails d'un hôte : son trafic, avec qui il communique, services, pays et derniers flux](images/detail.png) ![Chemins du trafic : quel segment utilise quelle application vers quel pays](images/paths.png) @@ -715,14 +745,14 @@ c'est le cas d'un service, utilisez plutôt `-user` et `-password`. `-lang` choisit la langue (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`). -Touches : 1–9 pages, ↑↓ sélection, Entrée actions sur la valeur +Touches : 1–8 pages, ↑↓ sélection, Entrée actions sur la valeur sélectionnée, f afficher seulement, x exclure, / rechercher, t plage de temps, c effacer les filtres, w ouvrir la même vue dans un navigateur, q quitter. ![Interface terminal : vue d'ensemble](images/tui-overview.png) -![Interface terminal : Top-N des clients](images/tui-topn.png) +![Interface terminal : Top-N des conversations](images/tui-topn.png) @@ -848,7 +878,7 @@ Options de `traffic66` et `traffic66 demo` : | `-sampling-wait` | `5m` | durée pendant laquelle les enregistrements attendent un taux d'échantillonnage | | `-capture` | | capture sur une interface locale (répétable) | | `-inventory` | `/inventory.txt` | fichier de noms | -| `-asn` | `/asn.tsv.gz` | table IP-vers-ASN | +| `-asn` | `/asn.tsv.gz` | table IP-vers-ASN (fichiers `.mmdb` : importez-les, ou `/country.mmdb` et `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | liste de menaces supplémentaire sous la forme `name=path` (répétable) | | `-dns-upstream` | résolveur du système | serveur DNS pour afficher les noms d'hôte | | `-dns-rate` | `20` | nombre maximal de résolutions inverses par seconde | @@ -875,7 +905,7 @@ Le répertoire de données contient tout : | `traffic66.duckdb` | agrégats, compteurs d'interface et heure en cours | | `password` | mots de passe de connexion (hachés) | | `inventory.txt` | noms (**Sources → Noms**) | -| `asn.tsv.gz`, `threats/` | tables de correspondance que vous avez ajoutées | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | bases de données pays et réseaux et listes de menaces que vous avez ajoutées | - **Sauvegarde** : arrêtez traffic66 et copiez le répertoire. Sans l'arrêter, copiez `raw/`, `password` et `inventory.txt` ; l'heure en @@ -957,7 +987,7 @@ lieu que la machine manque de mémoire. | "waiting for the sampling rate" | L'équipement n'a pas encore envoyé ses options de sampler ; la plupart les renvoient en quelques minutes. S'il ne le fait jamais, exportez-les (`option sampler-table` sur Cisco) ou marquez-le `unsampled` dans Noms s'il est réellement en 1:1 | | Chiffres inférieurs aux compteurs d'interface | Voir **Contrôle des interfaces** : pertes en route, interfaces non échantillonnées, ou flux encore dans le cache de l'équipement (timeout actif supérieur à 60 s) | | Chiffres supérieurs aux compteurs d'interface | Le même trafic est échantillonné sur deux interfaces ou deux équipements | -| Pas de pays ni de réseaux | Pas de table IP-vers-ASN : voir [Pays](#8-countries-networks-and-threat-lists) | +| Pas de pays ni de réseaux ("Inconnu") | Aucune base de données chargée : importez-en une dans **Sources**, voir [Pays](#8-countries-networks-and-threat-lists) | | "La base de données a atteint sa limite de mémoire et n'a pas pu répondre" sur une page | Choisissez une période plus courte, ou lancez avec un `-memory` plus grand ; les détails sont dans le journal | | Mot de passe oublié | `traffic66 passwd` sur la machine traffic66 (ajoutez `-data` si traffic66 tourne avec) | | `Conflicting lock is held` | Un autre traffic66 utilise déjà ce répertoire de données | diff --git a/docs/README.hi.md b/docs/README.hi.md index 707c083..cdce3e6 100644 --- a/docs/README.hi.md +++ b/docs/README.hi.md @@ -18,7 +18,7 @@ counters से मेल खाते हैं — web UI में भी औ flow records, encapsulation (GRE, IPIP, VXLAN, GENEVE, MPLS)। - web UI और terminal UI में 13 भाषाएँ। -![सारांश: पिछले हफ़्ते की तुलना में application के हिसाब से bandwidth, क्या बढ़ा, top clients और services](images/overview.png) +![सारांश: पिछले हफ़्ते की तुलना में application के हिसाब से bandwidth, top clients और services](images/overview.png) सभी स्क्रीनशॉट `traffic66 demo` से लिए गए हैं, एक simulated कंपनी नेटवर्क जिसे आप ख़ुद चला सकते हैं ([डेमो चलाकर देखें](#1-try-the-demo) देखें)। @@ -88,8 +88,8 @@ cd traffic66-windows-amd64 http://127.0.0.1:8066 खोलें और `admin` / `try66` से साइन इन करें। डेमो एक छोटी कंपनी का नेटवर्क बनाता है, जिसमें एक दिन का इतिहास और चार simulated डिवाइसों से live ट्रैफ़िक होता है, और उसमें ढूँढने के लिए दो incidents भी हैं: -**सारांश** से शुरू करें, **किसका ट्रैफ़िक बढ़ा** देखें, और वहाँ से क्लिक करते -हुए आगे बढ़ें। Ctrl+C से बंद करें। डेमो का डेटा प्रोग्राम के बगल में +**सारांश** से शुरू करें, **शीर्ष क्लाइंट** में किसी host पर क्लिक करें, +**विवरण देखें** चुनें, और वहाँ से क्लिक करते हुए आगे बढ़ें। Ctrl+C से बंद करें। डेमो का डेटा प्रोग्राम के बगल में `traffic66-demo` में रहता है; डेमो नए सिरे से शुरू करने के लिए वह फ़ोल्डर हटा दें। @@ -553,7 +553,12 @@ export packets traffic66 तक पहुँचने से पहले खो ## 7. नाम, SNMP और आपके अपने नेटवर्क -web UI में **स्रोत → नाम** हर लाइन में एक entry लेता है। यह data directory में +किसी host या डिवाइस को नाम देने का सबसे तेज़ तरीका: किसी भी पेज पर उसके address +पर क्लिक करें और **नाम दें…** चुनें। नाम टाइप करके Enter दबाएँ; यह तुरंत सेव हो +जाता है और हर जगह खाली address की जगह दिखता है। + +नेटवर्क, interfaces और SNMP के लिए, web UI में **स्रोत → नाम** हर लाइन में एक +entry लेता है। यह data directory में `inventory.txt` के रूप में सेव होता है, इसलिए आप वह फ़ाइल सीधे भी edit कर सकते हैं (`inventory.txt.example` देखें)। हर लाइन वैकल्पिक है। @@ -591,17 +596,27 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. देश, नेटवर्क और threat lists -देशों और नेटवर्क (AS) के नामों के लिए IP-to-ASN table चाहिए। -[iptoasn.com](https://iptoasn.com) से मुफ़्त table डाउनलोड करें: +देशों और नेटवर्क (AS) के नामों के लिए एक database चाहिए जो addresses को उनसे +जोड़ता है। इसे web UI में अपलोड करें: **स्रोत → देश और नेटवर्क डेटाबेस → डेटाबेस फ़ाइल अपलोड करें…**। +फ़ाइल जाँची जाती है, data directory में सेव होती है और नए ट्रैफ़िक के लिए तुरंत +इस्तेमाल होती है; restart की ज़रूरत नहीं। पहले से सेव ट्रैफ़िक वही देश रखता है +जिसके साथ वह सेव हुआ था। -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +स्वीकार की जाने वाली फ़ाइलें: -इसी format की कोई भी फ़ाइल चलेगी (tab से अलग: पहला address, आख़िरी address, -AS number, country code, AS name; plain या gzip)। इसे बदलने के बाद traffic66 -restart करें; लगभग हर महीने नई फ़ाइल डाउनलोड करें। +| फ़ाइल | क्या देती है | कहाँ से लें | +|---|---|---| +| DB-IP Lite country या ASN, `.mmdb` | देश, या AS number और नाम | मुफ़्त, बिना account: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country या ASN, `.mmdb` | देश, या AS number और नाम | MaxMind account के साथ मुफ़्त: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| IP-to-ASN table, `.tsv` या `.tsv.gz` | AS number, AS name और देश | मुफ़्त: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +दोनों पाने के लिए एक country database और एक ASN database अपलोड करें; जहाँ कई +लोड हों, वहाँ `.mmdb` फ़ाइलों में जो जानकारी है उसके लिए उन्हें प्राथमिकता मिलती +है। नए versions हर महीने आते हैं: पुरानी फ़ाइल बदलने के लिए नई फ़ाइल इसी तरह +अपलोड करें। + +web UI के बिना, फ़ाइल को data directory में `country.mmdb`, `asn.mmdb` या +`asn.tsv.gz` नाम से कॉपी करें और traffic66 restart करें। Threat lists सादी text फ़ाइलें होती हैं, हर लाइन में एक address या नेटवर्क (`#` या `;` के बाद का टेक्स्ट नज़रअंदाज़ होता है), जो @@ -627,6 +642,13 @@ application, देश, डिवाइस — पर क्लिक किय - **केवल यही दिखाएँ** / **इसे हटाएँ** एक filter जोड़ता है। Filters top bar के नीचे दिखते हैं और हटाए जाने तक हर पेज पर लागू रहते हैं। - **इसके फ़्लो रिकॉर्ड देखें** मेल खाते अलग-अलग flows खोलता है। +- **विवरण देखें** (hosts, डिवाइस और services) उस एक host या service के बारे में + एक पेज खोलता है: समय के साथ application के हिसाब से उसका ट्रैफ़िक, वह किससे + बात करता है, कौन-सी services या clients, देश और उसके नवीनतम flows। वहाँ की हर + value पर फिर से क्लिक किया जा सकता है, ताकि आप और गहराई में जा सकें; browser + का Back बटन वापस ले जाता है। +- **नाम दें…** (hosts और डिवाइस) address को एक नाम देता है, जो उसके बाद हर जगह + दिखता है। - **ऑनलाइन खोजें** address या AS को किसी public lookup साइट पर खोलता है। - **कॉपी करें** value कॉपी करता है। @@ -634,15 +656,14 @@ application, देश, डिवाइस — पर क्लिक किय | पेज | किस सवाल का जवाब देता है | |---|---| -| सारांश | अभी कितना ट्रैफ़िक है और पिछले हफ़्ते की तुलना में कितना, application के हिसाब से; क्या बढ़ा; top clients और services | -| Top-N | clients, servers, conversations, applications, ports, देशों, नेटवर्कों, segments, डिवाइसों, encapsulation या VLAN के top 66 | -| बातचीत | कौन किससे बात करता है: top 66 client–server जोड़े, उनकी service के साथ | +| सारांश | अभी कितना ट्रैफ़िक है और पिछले हफ़्ते की तुलना में कितना, application के हिसाब से; top clients और services | +| Top-N | top 66 की एक table: डिफ़ॉल्ट रूप से conversations (client, server, service, देश)। किसी नीले column heading पर क्लिक करके उसके हिसाब से group करें, किसी संख्या वाले heading पर क्लिक करके sort करें; **इसके अनुसार समूह** में applications, नेटवर्क, segments, डिवाइस, encapsulation और VLAN मिलते हैं | | ट्रैफ़िक के रास्ते | कौन-सा segment किस देश में किस application से बात करता है | | भूगोल और नेटवर्क | देश के हिसाब से और नेटवर्क (AS) के हिसाब से ट्रैफ़िक | | ख़तरे की जानकारी | वे hosts जिन्होंने आपकी threat lists के addresses से बात की, और उन्होंने कितना भेजा | | फ़्लो रिकॉर्ड | अलग-अलग flows, सबसे नए पहले, चुने जा सकने वाले columns के साथ | | इंटरफ़ेस मिलान | interface counters के बगल में flow के आँकड़े, सबसे ख़राब पहले, कारणों के साथ | -| स्रोत | डिवाइस, sampling, loss, collectors, SNMP, और **नाम** | +| स्रोत | डिवाइस, sampling, loss, collectors, SNMP, देश और नेटवर्क डेटाबेस, और **नाम** | पेजों के ऊपर: time range (15 मिनट से 30 दिन), एक वैकल्पिक search box, हर 30 सेकंड पर automatic refresh, और **लिंक कॉपी करें**, जो ठीक मौजूदा view (पेज, @@ -652,12 +673,16 @@ time range और filters) का link कॉपी करता है ता 6 घंटे से लंबी ranges पूरे घंटे से शुरू होती हैं, ताकि पेज का हर आँकड़ा ठीक एक जैसा समय गिने: "24 घंटे" में पिछले 24 पूरे घंटे और मौजूदा घंटा शामिल हैं। इन ranges पर Top-N घंटेवार summaries से आता है; वहाँ filters उपलब्ध नहीं -हैं, और पेज यह बता देता है। filter करने के लिए छोटी range चुनें। **बातचीत** हमेशा flow detail पढ़ता है, इसलिए +हैं, और पेज यह बता देता है। filter करने के लिए छोटी range चुनें। बातचीत (conversations) हमेशा flow detail पढ़ती है, इसलिए ऊँची flow दरों पर लंबी ranges में इसमें कुछ समय लग सकता है; एक घंटा सबसे तेज़ है। -![Top-N: पिछले घंटे के top 66 clients](images/topn.png) +side menu दिखाता है कि data कितनी disk इस्तेमाल करता है और कितनी ख़ाली है; +ख़ाली जगह पर hover करें तो दिखता है कि मौजूदा दर पर रखे गए दिनों के detail को +कितनी जगह चाहिए (एक दिन का data होने के बाद अनुमान लगाया जाता है)। + +![Top-N: पिछले घंटे की top 66 conversations](images/topn.png) -![बातचीत: कौन किससे बात करता है, service के साथ](images/conv.png) +![एक host का विवरण: उसका ट्रैफ़िक, वह किससे बात करता है, services, देश और नवीनतम flows](images/detail.png) ![ट्रैफ़िक के रास्ते: कौन-सा segment किस देश की ओर किस application का इस्तेमाल करता है](images/paths.png) @@ -681,13 +706,13 @@ directory पढ़ सकता है (डिफ़ॉल्ट न हो त `-password` इस्तेमाल करें। `-lang` भाषा चुनता है (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`)। -Keys: 1–9 पेज, ↑↓ चुनें, Enter चुनी गई value पर actions, f केवल यही दिखाएँ, +Keys: 1–8 पेज, ↑↓ चुनें, Enter चुनी गई value पर actions, f केवल यही दिखाएँ, x हटाएँ, / search, t time range, c filters साफ़ करें, w यही view browser में खोलें, q बाहर निकलें। ![Terminal UI: सारांश](images/tui-overview.png) -![Terminal UI: Top-N clients](images/tui-topn.png) +![Terminal UI: Top-N conversations](images/tui-topn.png) @@ -808,7 +833,7 @@ packets (जैसे दो mirror ports पर) दो बार गिने | `-sampling-wait` | `5m` | records sampling rate का कितनी देर इंतज़ार करें | | `-capture` | | local interface पर capture (दोहराया जा सकता है) | | `-inventory` | `/inventory.txt` | नामों की फ़ाइल | -| `-asn` | `/asn.tsv.gz` | IP-to-ASN table | +| `-asn` | `/asn.tsv.gz` | IP-to-ASN table (`.mmdb` फ़ाइलें: उन्हें अपलोड करें, या `/country.mmdb` और `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | अतिरिक्त threat list, `name=path` के रूप में (दोहराया जा सकता है) | | `-dns-upstream` | system resolver | host names दिखाने के लिए DNS server | | `-dns-rate` | `20` | प्रति सेकंड अधिकतम reverse lookups | @@ -835,7 +860,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | summaries, interface counters और मौजूदा घंटा | | `password` | login पासवर्ड (hashed) | | `inventory.txt` | नाम (**स्रोत → नाम**) | -| `asn.tsv.gz`, `threats/` | आपकी जोड़ी हुई lookup tables | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | आपके जोड़े हुए देश और नेटवर्क databases और threat lists | - **बैकअप**: traffic66 रोकें और directory कॉपी करें। बिना रोके `raw/`, `password` और `inventory.txt` कॉपी करें; तब मौजूदा घंटा और @@ -912,7 +937,7 @@ restart हो जाता है। | "waiting for the sampling rate" | डिवाइस ने अभी तक अपने sampler options नहीं भेजे; ज़्यादातर कुछ मिनटों में दोबारा भेज देते हैं। अगर कभी न भेजे, तो उन्हें export करवाएँ (Cisco पर `option sampler-table`) या अगर वह सच में 1:1 है तो नाम में उसे `unsampled` लिखें | | आँकड़े interface counters से कम | **इंटरफ़ेस मिलान** देखें: रास्ते में loss, interfaces sample नहीं हो रहे, या flows अभी डिवाइस के cache में हैं (active timeout 60 s से लंबा) | | आँकड़े interface counters से ज़्यादा | वही ट्रैफ़िक दो interfaces या दो डिवाइसों पर sample हो रहा है | -| कोई देश या नेटवर्क नहीं | IP-to-ASN table नहीं है: [देश](#8-countries-networks-and-threat-lists) देखें | +| कोई देश या नेटवर्क नहीं ("अज्ञात") | कोई database लोड नहीं है: **स्रोत** पर एक अपलोड करें, [देश](#8-countries-networks-and-threat-lists) देखें | | पेज पर "डेटाबेस अपनी मेमोरी सीमा तक पहुँच गया और जवाब नहीं दे सका" | छोटी समय सीमा चुनें, या बड़े `-memory` के साथ शुरू करें; विवरण log में है | | पासवर्ड भूल गए | traffic66 मशीन पर `traffic66 passwd` (अगर traffic66 `-data` के साथ चलता है तो `-data` जोड़ें) | | `Conflicting lock is held` | कोई दूसरा traffic66 पहले से यही data directory इस्तेमाल कर रहा है | diff --git a/docs/README.id.md b/docs/README.id.md index 263fc41..27e813e 100644 --- a/docs/README.id.md +++ b/docs/README.id.md @@ -19,7 +19,7 @@ antarmuka terminal. ancaman, catatan flow, enkapsulasi (GRE, IPIP, VXLAN, GENEVE, MPLS). - 13 bahasa di antarmuka web dan antarmuka terminal. -![Ringkasan: bandwidth per aplikasi dibanding minggu lalu, apa yang naik, klien dan layanan teratas](images/overview.png) +![Ringkasan: bandwidth per aplikasi dibanding minggu lalu, klien dan layanan teratas](images/overview.png) Semua tangkapan layar berasal dari `traffic66 demo`, jaringan perusahaan simulasi yang bisa Anda jalankan sendiri (lihat [Coba demo](#1-try-the-demo)). @@ -89,8 +89,8 @@ cd traffic66-windows-amd64 Buka http://127.0.0.1:8066 dan login sebagai `admin` / `try66`. Demo ini membangun jaringan kantor kecil dengan riwayat satu hari dan trafik live dari empat perangkat simulasi, lengkap dengan dua insiden yang bisa Anda -cari: mulai dari **Ringkasan**, lihat **Siapa yang naik**, lalu telusuri -dengan mengeklik. Hentikan dengan Ctrl+C. Data demo disimpan di +cari: mulai dari **Ringkasan**, klik sebuah host di **Klien teratas**, pilih +**Lihat detail**, lalu terus telusuri dengan mengeklik. Hentikan dengan Ctrl+C. Data demo disimpan di `traffic66-demo` di sebelah program; hapus folder itu untuk memulai demo dari awal. @@ -562,7 +562,12 @@ mencapai traffic66, atau sampling rate belum diketahui. ## 7. Nama, SNMP, dan jaringan Anda sendiri -**Sumber → Nama** di antarmuka web menerima satu entri per baris. Isinya +Cara tercepat memberi nama host atau perangkat: klik alamatnya di halaman mana +pun lalu pilih **Beri nama…**. Ketik namanya dan tekan Enter; nama langsung +disimpan dan ditampilkan di mana-mana menggantikan alamat polosnya. + +Untuk jaringan, interface, dan SNMP, **Sumber → Nama** di antarmuka web +menerima satu entri per baris. Isinya disimpan sebagai `inventory.txt` di direktori data, jadi Anda juga bisa mengedit file itu langsung (lihat `inventory.txt.example`). Setiap baris bersifat opsional. @@ -602,18 +607,27 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. Negara, jaringan, dan daftar ancaman -Nama negara dan jaringan (AS) membutuhkan tabel IP-to-ASN. Unduh yang -gratis dari [iptoasn.com](https://iptoasn.com): +Negara dan nama jaringan (AS) membutuhkan basis data yang memetakan alamat ke +keduanya. Unggah di antarmuka web: **Sumber → Basis data negara dan jaringan → Unggah file basis data…**. +File diperiksa, disimpan di direktori data, dan langsung dipakai untuk trafik +baru; tidak perlu restart. Trafik yang sudah tersimpan tetap memakai negara +saat ia disimpan. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +File yang diterima: -File apa pun dengan format yang sama bisa dipakai (dipisah tab: alamat -pertama, alamat terakhir, nomor AS, kode negara, nama AS; teks biasa atau -gzip). Restart traffic66 setelah menggantinya; unduh yang baru kira-kira -sebulan sekali. +| File | Memberikan | Tempat mendapatkannya | +|---|---|---| +| DB-IP Lite country atau ASN, `.mmdb` | negara, atau nomor dan nama AS | gratis, tanpa akun: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country atau ASN, `.mmdb` | negara, atau nomor dan nama AS | gratis dengan akun MaxMind: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| Tabel IP-to-ASN, `.tsv` atau `.tsv.gz` | nomor AS, nama AS, dan negara | gratis: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +Unggah basis data negara dan basis data ASN untuk mendapatkan keduanya; bila +beberapa dimuat, file `.mmdb` didahulukan untuk isi yang dimilikinya. Versi +baru terbit setiap bulan: unggah file baru dengan cara yang sama untuk +mengganti yang lama. + +Tanpa antarmuka web, salin file ke direktori data sebagai `country.mmdb`, +`asn.mmdb`, atau `asn.tsv.gz` lalu restart traffic66. Daftar ancaman adalah file teks biasa berisi satu alamat atau jaringan per baris (teks setelah `#` atau `;` diabaikan), disimpan sebagai @@ -640,6 +654,13 @@ aplikasi, negara, perangkat — bisa diklik: muncul di bawah bar atas dan berlaku di semua halaman sampai Anda menghapusnya. - **Lihat catatan flow-nya** membuka flow-flow individual yang cocok. +- **Lihat detail** (host, perangkat, dan layanan) membuka halaman tentang satu + host atau layanan itu: trafiknya dari waktu ke waktu per aplikasi, dengan + siapa ia berbicara, layanan atau klien mana, negara, dan flow terbarunya. + Setiap nilai di sana bisa diklik lagi, jadi Anda bisa terus menelusuri; + tombol Back di browser membawa Anda kembali. +- **Beri nama…** (host dan perangkat) memberi alamat itu sebuah nama, yang + sejak itu ditampilkan di mana-mana. - **Cari secara online** membuka alamat atau AS tersebut di situs lookup publik. - **Salin** menyalin nilainya. @@ -648,15 +669,14 @@ Halaman: | Halaman | Pertanyaan yang dijawab | |---|---| -| Ringkasan | Berapa trafik sekarang dan dibanding minggu lalu, per aplikasi; apa yang naik; klien dan layanan teratas | -| Top-N | 66 teratas untuk klien, server, percakapan, aplikasi, port, negara, jaringan, segmen, perangkat, enkapsulasi, atau VLAN | -| Percakapan | Siapa berbicara dengan siapa: 66 pasangan klien–server teratas beserta layanannya | +| Ringkasan | Berapa trafik sekarang dan dibanding minggu lalu, per aplikasi; klien dan layanan teratas | +| Top-N | Satu tabel 66 teratas: secara default percakapan (klien, server, layanan, negara). Klik judul kolom biru untuk mengelompokkan menurut kolom itu, judul kolom angka untuk mengurutkan; **Kelompokkan menurut** menawarkan aplikasi, jaringan, segmen, perangkat, enkapsulasi, dan VLAN | | Jalur trafik | Segmen mana berbicara dengan aplikasi apa di negara mana | | Geografi & jaringan | Trafik per negara dan per jaringan (AS) | | Intel ancaman | Host yang berkomunikasi dengan alamat di daftar ancaman Anda, dan berapa banyak yang mereka kirim | | Catatan flow | Flow individual, terbaru di atas, dengan kolom yang bisa dipilih | | Pencocokan antarmuka | Angka flow di samping counter interface, yang paling buruk di atas, beserta alasannya | -| Sumber | Perangkat, sampling, loss, collector, SNMP, dan **Nama** | +| Sumber | Perangkat, sampling, loss, collector, SNMP, basis data negara dan jaringan, dan **Nama** | Di atas halaman: rentang waktu (15 menit sampai 30 hari), kotak pencarian opsional, refresh otomatis setiap 30 detik, dan **Salin tautan**, yang @@ -669,13 +689,18 @@ setiap angka di halaman menghitung waktu yang persis sama: "24 jam" mencakup 24 jam penuh terakhir ditambah jam yang sedang berjalan. Top-N untuk rentang ini diambil dari ringkasan per jam; filter tidak tersedia di sana, dan halaman memberi tahu hal itu. Pilih rentang yang lebih pendek untuk -memfilter. **Percakapan** selalu membaca detail flow, jadi untuk rentang -panjang dengan laju flow tinggi halaman ini bisa butuh waktu; satu jam paling +memfilter. Percakapan selalu dibaca dari detail flow, jadi untuk rentang +panjang dengan laju flow tinggi bisa butuh waktu; satu jam paling cepat. -![Top-N: 66 klien teratas dalam satu jam terakhir](images/topn.png) +Menu samping menunjukkan berapa banyak disk yang dipakai data dan berapa yang +masih kosong; arahkan kursor ke ruang kosong untuk melihat berapa yang +dibutuhkan detail untuk hari-hari yang disimpan pada laju saat ini +(diperkirakan setelah ada data satu hari). + +![Top-N: 66 percakapan teratas dalam satu jam terakhir](images/topn.png) -![Percakapan: siapa berbicara dengan siapa, beserta layanannya](images/conv.png) +![Detail satu host: trafiknya, dengan siapa ia berbicara, layanan, negara, dan flow terbaru](images/detail.png) ![Jalur trafik: segmen mana memakai aplikasi apa menuju negara mana](images/paths.png) @@ -699,13 +724,13 @@ berjalan sebagai user lain, seperti halnya service, gunakan `-user` dan `-password`. `-lang` memilih bahasa (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`). -Tombol: 1–9 halaman, ↑↓ pilih, Enter aksi untuk nilai terpilih, f tampilkan +Tombol: 1–8 halaman, ↑↓ pilih, Enter aksi untuk nilai terpilih, f tampilkan hanya ini, x kecualikan, / cari, t rentang waktu, c hapus filter, w buka tampilan yang sama di browser, q keluar. ![Antarmuka terminal: ringkasan](images/tui-overview.png) -![Antarmuka terminal: klien Top-N](images/tui-topn.png) +![Antarmuka terminal: percakapan Top-N](images/tui-topn.png) @@ -828,7 +853,7 @@ Opsi untuk `traffic66` dan `traffic66 demo`: | `-sampling-wait` | `5m` | berapa lama record menunggu sampling rate | | `-capture` | | capture di interface lokal (bisa diulang) | | `-inventory` | `/inventory.txt` | file nama | -| `-asn` | `/asn.tsv.gz` | tabel IP-to-ASN | +| `-asn` | `/asn.tsv.gz` | tabel IP-to-ASN (file `.mmdb`: unggah, atau `/country.mmdb` dan `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | daftar ancaman tambahan dalam format `name=path` (bisa diulang) | | `-dns-upstream` | resolver sistem | server DNS untuk menampilkan nama host | | `-dns-rate` | `20` | maksimum reverse lookup per detik | @@ -855,7 +880,7 @@ Semua tersimpan di direktori data: | `traffic66.duckdb` | ringkasan, counter interface, dan jam berjalan | | `password` | kata sandi login (dalam bentuk hash) | | `inventory.txt` | nama (**Sumber → Nama**) | -| `asn.tsv.gz`, `threats/` | tabel lookup yang Anda tambahkan | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | basis data negara dan jaringan serta daftar ancaman yang Anda tambahkan | - **Backup**: hentikan traffic66 lalu salin direktorinya. Tanpa menghentikannya, salin `raw/`, `password`, dan `inventory.txt`; jam @@ -934,7 +959,7 @@ kehabisan memori. | "waiting for the sampling rate" | Perangkat belum mengirim sampler options; kebanyakan mengirim ulang dalam beberapa menit. Jika tidak pernah, ekspor opsi itu (`option sampler-table` di Cisco) atau tandai perangkat sebagai `unsampled` di Nama jika memang 1:1 | | Angka lebih rendah dari counter interface | Lihat **Pencocokan antarmuka**: loss di jalan, interface tidak di-sampling, atau flow masih di cache perangkat (active timeout lebih dari 60 detik) | | Angka lebih tinggi dari counter interface | Trafik yang sama di-sampling di dua interface atau dua perangkat | -| Tidak ada negara atau jaringan | Tidak ada tabel IP-to-ASN: lihat [Negara](#8-countries-networks-and-threat-lists) | +| Tidak ada negara atau jaringan ("Tidak diketahui") | Tidak ada basis data yang dimuat: unggah di **Sumber**, lihat [Negara](#8-countries-networks-and-threat-lists) | | "Basis data mencapai batas memorinya dan tidak bisa menjawab" di sebuah halaman | Pilih rentang waktu yang lebih pendek, atau jalankan dengan `-memory` yang lebih besar; detailnya ada di log | | Lupa kata sandi | `traffic66 passwd` di mesin traffic66 (tambahkan `-data` jika traffic66 berjalan dengannya) | | `Conflicting lock is held` | traffic66 lain sudah memakai direktori data ini | diff --git a/docs/README.ja.md b/docs/README.ja.md index 3c5d091..3ce6970 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -10,7 +10,7 @@ sFlow・NetFlow・IPFIX のフロー分析を 1 つのプログラムで行い - Top 66 ランキング、フローの流れ、国とネットワーク、脅威リストとの一致、フローレコード、カプセル化(GRE、IPIP、VXLAN、GENEVE、MPLS)。 - Web UI とターミナル UI は 13 言語に対応しています。 -![概要:アプリケーション別の帯域と先週との比較、増えたもの、上位のクライアントとサービス](images/overview.png) +![概要:アプリケーション別の帯域と先週との比較、上位のクライアントとサービス](images/overview.png) スクリーンショットはすべて `traffic66 demo` で撮影したものです。これは自分で実行できる模擬的な社内ネットワークです([デモを試す](#1-try-the-demo) を参照)。 @@ -75,7 +75,7 @@ cd traffic66-windows-amd64 .\traffic66.exe demo -password try66 ``` -http://127.0.0.1:8066 を開き、`admin` / `try66` でサインインします。デモでは小さな社内ネットワークが構築され、1 日分の履歴と、シミュレートした 4 台の機器からのライブトラフィックが流れます。中には見つけてほしいインシデントが 2 件仕込まれています。**概要** から始めて**いつもより増えたホスト** を確認し、あとはクリックでたどってください。Ctrl+C で停止します。デモデータはプログラムと同じ場所の `traffic66-demo` に保存されます。このフォルダーを削除するとデモを最初からやり直せます。 +http://127.0.0.1:8066 を開き、`admin` / `try66` でサインインします。デモでは小さな社内ネットワークが構築され、1 日分の履歴と、シミュレートした 4 台の機器からのライブトラフィックが流れます。中には見つけてほしいインシデントが 2 件仕込まれています。**概要** から始めて **上位クライアント** のホストをクリックし、**詳細を表示** を選んで、そこからさらにクリックでたどってください。Ctrl+C で停止します。デモデータはプログラムと同じ場所の `traffic66-demo` に保存されます。このフォルダーを削除するとデモを最初からやり直せます。 デモは本番インストールと同じポート(8066 と UDP 6343、2055、4739)を使います。本番環境と並べて動かす場合は、別のポートを指定してください:`traffic66 demo -password try66 -addr :8067 -listen ""`。 @@ -472,7 +472,9 @@ softflowd -i eth0 -n 192.0.2.50:2055 -v 9 -t maxlife=60 ## 7. 名前、SNMP、自社ネットワーク -Web UI の **受信状況 → 名前** には 1 行に 1 エントリを書きます。内容はデータディレクトリに`inventory.txt` として保存されるので、このファイルを直接編集することもできます(`inventory.txt.example` を参照)。どの行も省略可能です。 +ホストや機器に名前を付ける一番手早い方法は、どのページでもそのアドレスをクリックして **名前を付ける…** を選ぶことです。名前を入力して Enter を押すとすぐに保存され、以後どこでもアドレスの代わりにその名前が表示されます。 + +ネットワーク、インターフェース、SNMP については、Web UI の **受信状況 → 名前** に 1 行に 1 エントリを書きます。内容はデータディレクトリに`inventory.txt` として保存されるので、このファイルを直接編集することもできます(`inventory.txt.example` を参照)。どの行も省略可能です。 ``` # your networks: traffic between them is "internal" @@ -502,14 +504,19 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. 国、ネットワーク、脅威リスト -国とネットワーク(AS)名の表示には IP-ASN 対応表が必要です。[iptoasn.com](https://iptoasn.com) から無料のものをダウンロードできます: +国とネットワーク(AS)名の表示には、アドレスからそれらを引くデータベースが必要です。Web UI でアップロードしてください:**受信状況 → 国・ネットワークデータベース → データベースファイルをアップロード…**。ファイルは検証されてデータディレクトリに保存され、新しいトラフィックにすぐ使われます。再起動は不要です。保存済みのトラフィックは、保存時の国のままです。 -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +使えるファイル: -同じ形式であればどのファイルでも使えます(タブ区切り:先頭アドレス、末尾アドレス、AS 番号、国コード、AS 名。プレーンテキストまたは gzip)。差し替えたら traffic66 を再起動してください。月に 1 回程度、新しいものをダウンロードするとよいでしょう。 +| ファイル | 得られる情報 | 入手先 | +|---|---|---| +| DB-IP Lite country または ASN、`.mmdb` | 国、または AS 番号と名前 | 無料、アカウント不要:[db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country または ASN、`.mmdb` | 国、または AS 番号と名前 | MaxMind アカウントがあれば無料:[maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| IP-ASN 対応表、`.tsv` または `.tsv.gz` | AS 番号、AS 名、国 | 無料:[iptoasn.com](https://iptoasn.com)(`ip2asn-combined.tsv.gz`) | + +国データベースと ASN データベースを両方アップロードすれば両方が得られます。複数読み込まれている場合、`.mmdb` ファイルに含まれる情報はそちらが優先されます。新しい版は毎月出ます。同じ方法で新しいファイルをアップロードすれば古いものと置き換わります。 + +Web UI を使わない場合は、ファイルを `country.mmdb`、`asn.mmdb`、`asn.tsv.gz` のいずれかの名前でデータディレクトリにコピーし、traffic66 を再起動してください。 脅威リストは 1 行に 1 つのアドレスまたはネットワークを書いたプレーンテキストファイルで(`#` または `;` 以降は無視されます)、`/threats/.txt` として保存します。例: @@ -530,6 +537,8 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh - **これだけ表示** / **これを除外** でフィルターを追加します。フィルターはトップバーの下に表示され、外すまですべてのページに適用されます。 - **フローレコードを見る** で、該当する個々のフローを開きます。 +- **詳細を表示**(ホスト、機器、サービス)で、そのホストまたはサービス 1 つについてのページを開きます。アプリケーション別のトラフィックの推移、通信相手、サービスまたはクライアント、国、最新のフローが表示されます。そこでもすべての値をクリックできるので、さらに掘り下げていけます。ブラウザーの戻るボタンで戻れます。 +- **名前を付ける…**(ホストと機器)で、アドレスに名前を付けます。以後どこでもその名前が表示されます。 - **外部サイトで調べる** で、アドレスや AS を公開の検索サイトで開きます。 - **コピー** で値をコピーします。 @@ -537,23 +546,24 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh | ページ | 分かること | |---|---| -| 概要 | 現在のトラフィック量と先週との比較(アプリケーション別)、増えたもの、上位のクライアントとサービス | -| Top-N | クライアント、サーバー、通信ペア、アプリケーション、ポート、国、ネットワーク、セグメント、機器、カプセル化、VLAN の上位 66 件 | -| 会話 | 誰が誰と通信しているか:クライアントとサーバーの組み合わせ上位 66 件とそのサービス | +| 概要 | 現在のトラフィック量と先週との比較(アプリケーション別)、上位のクライアントとサービス | +| Top-N | 上位 66 件を 1 つの表で表示。既定は会話(クライアント、サーバー、サービス、国)。青い列見出しをクリックするとその列でグループ化、数値の列見出しをクリックすると並べ替えます。**グループ化** ではアプリケーション、ネットワーク、セグメント、機器、カプセル化、VLAN も選べます | | フローの流れ | どのセグメントがどの国のどのアプリケーションと通信しているか | | 地域とネットワーク | 国別・ネットワーク(AS)別のトラフィック | | 脅威インテル | 脅威リスト上のアドレスと通信したホストと、その送信量 | | フローレコード | 個々のフロー(新しい順、表示列は選択可能) | | インターフェース照合 | フローの数値とインターフェースカウンターの対比(差の大きい順、理由付き) | -| 受信状況 | 機器、サンプリング、ロス、コレクター、SNMP、**名前** | +| 受信状況 | 機器、サンプリング、ロス、コレクター、SNMP、国・ネットワークデータベース、**名前** | ページの上部には、時間範囲(15 分〜30 日)、検索ボックス(任意)、30 秒ごとの自動更新、そして **リンクをコピー** があります。**リンクをコピー** は、現在の表示(ページ、時間範囲、フィルター)そのものへのリンクをコピーするので、同僚に送るのに便利です。言語はブラウザーの設定に従い、メニューの一番下で変更できます。 -6 時間より長い時間範囲は正時から始まるため、ページ上のすべての数値がまったく同じ時間を集計します。「24 時間」は、直近の区切りのよい 24 時間に現在の 1 時間を加えた範囲です。これらの範囲の Top-N は 1 時間ごとの集計から作られるため、フィルターは使えません(ページにもその旨が表示されます)。フィルターを使うには短い範囲を選んでください。**会話** は常にフローの明細を読むため、フローの多い環境で長い範囲を選ぶと時間がかかることがあります。1 時間が最速です。 +6 時間より長い時間範囲は正時から始まるため、ページ上のすべての数値がまったく同じ時間を集計します。「24 時間」は、直近の区切りのよい 24 時間に現在の 1 時間を加えた範囲です。これらの範囲の Top-N は 1 時間ごとの集計から作られるため、フィルターは使えません(ページにもその旨が表示されます)。フィルターを使うには短い範囲を選んでください。会話は常にフローの明細を読むため、フローの多い環境で長い範囲を選ぶと時間がかかることがあります。1 時間が最速です。 + +サイドメニューには、データが使っているディスク容量と空き容量が表示されます。空き容量にマウスを重ねると、現在のペースで明細を保持日数分残すのに必要な容量が分かります(1 日分のデータがたまると推定されます)。 -![Top-N:直近 1 時間のクライアント上位 66 件](images/topn.png) +![Top-N:直近 1 時間の会話上位 66 件](images/topn.png) -![会話:誰が誰とどのサービスで通信しているか](images/conv.png) +![1 台のホストの詳細:トラフィック、通信相手、サービス、国、最新のフロー](images/detail.png) ![フローの流れ:どのセグメントがどの国に向けてどのアプリケーションを使っているか](images/paths.png) @@ -573,11 +583,11 @@ traffic66 -tui # collect and show the termina traffic66 のマシン上では、データディレクトリを読み取れれば `traffic66 tui` は自動でサインインします(デフォルト以外の場所なら `-data` を指定)。サービスとして動かす場合のようにtraffic66 が別のユーザーで動いているときは、代わりに `-user` と `-password` を使います。`-lang` で言語を選びます(`en`、`zh`、`hi`、`es`、`ar`、`fr`、`bn`、`pt`、`ru`、`id`、`ur`、`ja`、`ko`)。 -キー操作:1–9 ページ切り替え、↑↓ 選択、Enter 選択した値に対する操作、f これだけ表示、x 除外、/ 検索、t 時間範囲、c フィルター解除、w 同じ表示をブラウザーで開く、q 終了。 +キー操作:1–8 ページ切り替え、↑↓ 選択、Enter 選択した値に対する操作、f これだけ表示、x 除外、/ 検索、t 時間範囲、c フィルター解除、w 同じ表示をブラウザーで開く、q 終了。 ![ターミナル UI:概要](images/tui-overview.png) -![ターミナル UI:Top-N クライアント](images/tui-topn.png) +![ターミナル UI:Top-N 会話](images/tui-topn.png) @@ -682,7 +692,7 @@ sudo traffic66 -capture en0 | `-sampling-wait` | `5m` | レコードがサンプリングレートを待つ時間 | | `-capture` | | ローカルインターフェースでキャプチャ(複数指定可) | | `-inventory` | `/inventory.txt` | 名前ファイル | -| `-asn` | `/asn.tsv.gz` | IP-ASN 対応表 | +| `-asn` | `/asn.tsv.gz` | IP-ASN 対応表(`.mmdb` ファイルはアップロードするか、`/country.mmdb` と `/asn.mmdb` に置く) | | `-threat` | `/threats/*.txt` | 追加の脅威リストを `name=path` で指定(複数指定可) | | `-dns-upstream` | システムのリゾルバー | ホスト名表示に使う DNS サーバー | | `-dns-rate` | `20` | 1 秒あたりの逆引き回数の上限 | @@ -708,7 +718,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | 集計、インターフェースカウンター、現在の 1 時間分 | | `password` | ログインパスワード(ハッシュ化) | | `inventory.txt` | 名前(**受信状況 → 名前**) | -| `asn.tsv.gz`, `threats/` | 追加した参照テーブル | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | 追加した国・ネットワークデータベースと脅威リスト | - **バックアップ**:traffic66 を停止してディレクトリをコピーします。停止せずに行う場合は`raw/`、`password`、`inventory.txt` をコピーします。この場合、現在の 1 時間分と集計は含まれません。 - **移動**:traffic66 を停止し、ディレクトリを移動して、新しい場所を `-data` で指定して起動します。 @@ -752,7 +762,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | "waiting for the sampling rate" | 機器がまだサンプラーオプションを送っていません。多くの機器は数分以内に再送します。いつまでも送られない場合はエクスポートを設定する(Cisco では `option sampler-table`)か、本当に 1:1 なら名前で `unsampled` を付けます | | 数値がインターフェースカウンターより小さい | **インターフェース照合** を確認:途中でのロス、サンプリングされていないインターフェース、またはフローがまだ機器のキャッシュ内にある(アクティブタイムアウトが 60 秒より長い) | | 数値がインターフェースカウンターより大きい | 同じトラフィックを 2 つのインターフェースまたは 2 台の機器でサンプリングしています | -| 国やネットワークが表示されない | IP-ASN 対応表がありません:[国](#8-countries-networks-and-threat-lists) を参照 | +| 国やネットワークが表示されない("不明") | データベースが読み込まれていません。**受信状況** でアップロードしてください:[国](#8-countries-networks-and-threat-lists) を参照 | | ページに "データベースがメモリ上限に達したため、応答できませんでした" と表示される | 期間を短くするか、より大きな `-memory` で起動してください。詳細はログにあります | | パスワードを忘れた | traffic66 のマシンで `traffic66 passwd`(traffic66 を `-data` 付きで動かしている場合は `-data` も付ける) | | `Conflicting lock is held` | 別の traffic66 がすでにこのデータディレクトリを使っています | diff --git a/docs/README.ko.md b/docs/README.ko.md index 22808b2..941be27 100644 --- a/docs/README.ko.md +++ b/docs/README.ko.md @@ -16,7 +16,7 @@ sFlow, NetFlow, IPFIX 플로 분석을 하나의 프로그램으로 처리합니 캡슐화(GRE, IPIP, VXLAN, GENEVE, MPLS). - 웹 UI와 터미널 UI 모두 13개 언어를 지원합니다. -![개요: 지난주와 비교한 애플리케이션별 대역폭, 늘어난 항목, 상위 클라이언트와 서비스](images/overview.png) +![개요: 지난주와 비교한 애플리케이션별 대역폭, 상위 클라이언트와 서비스](images/overview.png) 모든 스크린샷은 직접 실행해 볼 수 있는 가상의 회사 네트워크인 `traffic66 demo`에서 찍은 것입니다([데모 실행해 보기](#1-try-the-demo) 참조). @@ -84,8 +84,8 @@ cd traffic66-windows-amd64 http://127.0.0.1:8066 을 열고 `admin` / `try66`으로 로그인합니다. 데모는 작은 회사 네트워크를 만들어 하루치 이력과 시뮬레이션 장비 4대의 실시간 트래픽을 보여 주며, 찾아볼 -만한 사고 두 건이 들어 있습니다. **개요**에서 시작해 **평소보다 늘어난 곳**을 보고, 거기서부터 -클릭하며 따라가 보십시오. Ctrl+C로 중지합니다. 데모 데이터는 프로그램 옆의 `traffic66-demo`에 +만한 사고 두 건이 들어 있습니다. **개요**에서 시작해 **상위 클라이언트**의 호스트를 클릭하고 +**상세 보기**를 고른 뒤, 거기서부터 계속 클릭해 보십시오. Ctrl+C로 중지합니다. 데모 데이터는 프로그램 옆의 `traffic66-demo`에 저장되며, 이 폴더를 삭제하면 데모를 처음부터 다시 시작할 수 있습니다. 데모는 실제 설치와 같은 포트(8066, UDP 6343, 2055, 4739)를 사용합니다. 실제 인스턴스와 @@ -535,7 +535,12 @@ traffic66에 도착하기 전에 유실됨, 샘플링 레이트를 아직 모름 ## 7. 이름, SNMP, 자체 네트워크 -웹 UI의 **수집 상태 → 이름**에는 한 줄에 하나씩 항목을 적습니다. 내용은 데이터 디렉터리에 +호스트나 장비에 이름을 붙이는 가장 빠른 방법: 아무 페이지에서나 그 주소를 클릭하고 +**이름 붙이기…** 메뉴를 고릅니다. 이름을 입력하고 Enter를 누르면 바로 저장되고, 이후 모든 +곳에서 주소 대신 그 이름이 표시됩니다. + +네트워크, 인터페이스, SNMP는 웹 UI의 **수집 상태 → 이름**에 한 줄에 하나씩 항목을 적습니다. +내용은 데이터 디렉터리에 `inventory.txt`로 저장되므로 이 파일을 직접 편집해도 됩니다(`inventory.txt.example` 참조). 모든 줄은 선택 사항입니다. @@ -572,17 +577,25 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. 국가, 네트워크, 위협 목록 -국가와 네트워크(AS) 이름을 표시하려면 IP-ASN 매핑 테이블이 필요합니다. -[iptoasn.com](https://iptoasn.com)에서 무료로 내려받을 수 있습니다: +국가와 네트워크(AS) 이름을 표시하려면 주소를 국가와 네트워크로 매핑하는 데이터베이스가 +필요합니다. 웹 UI에서 업로드하십시오: **수집 상태 → 국가 및 네트워크 데이터베이스 → +데이터베이스 파일 업로드…**. 파일은 검사를 거쳐 데이터 디렉터리에 저장되고 새 트래픽에 바로 +사용되며, 재시작할 필요는 없습니다. 이미 저장된 트래픽은 저장될 때의 국가를 유지합니다. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +사용할 수 있는 파일: -같은 형식이면 어떤 파일이든 됩니다(탭 구분: 시작 주소, 끝 주소, AS 번호, 국가 코드, AS 이름. -일반 텍스트 또는 gzip). 파일을 교체한 뒤에는 traffic66을 재시작하고, 한 달에 한 번 정도 새로 -내려받으십시오. +| 파일 | 제공 정보 | 구하는 곳 | +|---|---|---| +| DB-IP Lite country 또는 ASN, `.mmdb` | 국가, 또는 AS 번호와 이름 | 무료, 계정 불필요: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country 또는 ASN, `.mmdb` | 국가, 또는 AS 번호와 이름 | MaxMind 계정이 있으면 무료: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| IP-ASN 매핑 테이블, `.tsv` 또는 `.tsv.gz` | AS 번호, AS 이름, 국가 | 무료: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +국가 데이터베이스와 ASN 데이터베이스를 함께 업로드하면 둘 다 얻을 수 있습니다. 여러 개가 +로드되어 있으면 `.mmdb` 파일에 담긴 정보는 그 파일이 우선합니다. 새 버전은 매달 나옵니다. +같은 방법으로 새 파일을 업로드하면 이전 파일을 대체합니다. + +웹 UI 없이 하려면 파일을 데이터 디렉터리에 `country.mmdb`, `asn.mmdb` 또는 `asn.tsv.gz`로 +복사하고 traffic66을 재시작하십시오. 위협 목록은 한 줄에 주소나 네트워크 하나씩 적은 일반 텍스트 파일이며(`#` 또는 `;` 뒤는 무시됨), `/threats/.txt`로 저장합니다. 예: @@ -607,6 +620,12 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh - **이것만 보기** / **이것 제외**는 필터를 추가합니다. 필터는 상단 바 아래에 표시되며, 제거할 때까지 모든 페이지에 적용됩니다. - **플로 레코드 보기**는 해당하는 개별 플로를 엽니다. +- **상세 보기**(호스트, 장비, 서비스)는 그 호스트나 서비스 하나에 대한 페이지를 엽니다. + 애플리케이션별 트래픽 추이, 통신 상대, 서비스 또는 클라이언트, 국가, 최근 플로를 보여 + 줍니다. 그곳의 모든 값도 다시 클릭할 수 있어 계속 파고들 수 있으며, 브라우저의 뒤로 + 버튼으로 돌아옵니다. +- **이름 붙이기…**(호스트와 장비)는 주소에 이름을 붙이며, 이후 모든 곳에 그 이름이 + 표시됩니다. - **온라인에서 조회**는 주소나 AS를 공개 조회 사이트에서 엽니다. - **복사**는 값을 복사합니다. @@ -614,15 +633,14 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh | 페이지 | 알 수 있는 것 | |---|---| -| 개요 | 현재 트래픽 양과 지난주 대비 변화(애플리케이션별), 늘어난 항목, 상위 클라이언트와 서비스 | -| Top-N | 클라이언트, 서버, 대화, 애플리케이션, 포트, 국가, 네트워크, 세그먼트, 장비, 캡슐화 또는 VLAN의 상위 66개 | -| 대화 | 누가 누구와 통신하는지: 상위 66개 클라이언트–서버 쌍과 그 서비스 | +| 개요 | 현재 트래픽 양과 지난주 대비 변화(애플리케이션별), 상위 클라이언트와 서비스 | +| Top-N | 상위 66개를 담은 하나의 표: 기본은 대화(클라이언트, 서버, 서비스, 국가). 파란 열 제목을 클릭하면 그 열로 그룹화하고, 숫자 열 제목을 클릭하면 정렬합니다. **그룹 기준**에서 애플리케이션, 네트워크, 세그먼트, 장비, 캡슐화, VLAN을 고를 수 있습니다 | | 트래픽 경로 | 어느 세그먼트가 어느 국가의 어느 애플리케이션과 통신하는지 | | 지역 및 네트워크 | 국가별, 네트워크(AS)별 트래픽 | | 위협 정보 | 위협 목록에 있는 주소와 통신한 호스트와 그 전송량 | | 플로 레코드 | 개별 플로, 최신순, 표시할 열 선택 가능 | | 인터페이스 대조 | 플로 수치와 인터페이스 카운터 비교, 차이가 큰 순, 원인 포함 | -| 수집 상태 | 장비, 샘플링, 유실, 수집기, SNMP, **이름** | +| 수집 상태 | 장비, 샘플링, 유실, 수집기, SNMP, 국가 및 네트워크 데이터베이스, **이름** | 페이지 위쪽에는 시간 범위(15분~30일), 선택적 검색 상자, 30초마다 자동 새로 고침, 그리고 **링크 복사**가 있습니다. **링크 복사**는 현재 보고 있는 화면(페이지, 시간 범위, 필터) 그대로의 @@ -632,12 +650,16 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh 6시간보다 긴 범위는 정시에 시작하므로 페이지의 모든 수치가 정확히 같은 시간을 집계합니다. "24시간"은 지난 24개의 온전한 시간에 현재 시간을 더한 범위입니다. 이런 범위의 Top-N은 시간 단위 요약에서 가져오므로 필터를 쓸 수 없으며, 페이지에도 그렇게 표시됩니다. 필터를 -쓰려면 더 짧은 범위를 선택하십시오. **대화**는 항상 플로 상세를 읽으므로 플로가 많은 환경에서 +쓰려면 더 짧은 범위를 선택하십시오. 대화는 항상 플로 상세를 읽으므로 플로가 많은 환경에서 긴 범위를 고르면 시간이 걸릴 수 있습니다. 1시간이 가장 빠릅니다. -![Top-N: 최근 1시간의 상위 66개 클라이언트](images/topn.png) +사이드 메뉴에는 데이터가 쓰는 디스크 용량과 남은 용량이 표시됩니다. 남은 용량에 마우스를 +올리면 현재 속도로 상세 데이터를 보관 일수만큼 유지하는 데 필요한 용량을 볼 수 있습니다 +(하루치 데이터가 쌓이면 추정됨). + +![Top-N: 최근 1시간의 상위 66개 대화](images/topn.png) -![대화: 누가 누구와 어떤 서비스로 통신하는지](images/conv.png) +![호스트 하나의 상세: 트래픽, 통신 상대, 서비스, 국가, 최근 플로](images/detail.png) ![트래픽 경로: 어느 세그먼트가 어느 국가로 어느 애플리케이션을 쓰는지](images/paths.png) @@ -660,12 +682,12 @@ traffic66 서버에서는 데이터 디렉터리를 읽을 수 있으면 `traffi 대신 `-user`와 `-password`를 사용합니다. `-lang`으로 언어를 고릅니다(`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`). -키: 1–9 페이지, ↑↓ 선택, Enter 선택한 값에 대한 동작, f 이것만 보기, x 제외, / 검색, +키: 1–8 페이지, ↑↓ 선택, Enter 선택한 값에 대한 동작, f 이것만 보기, x 제외, / 검색, t 시간 범위, c 필터 해제, w 같은 화면을 브라우저에서 열기, q 종료. ![터미널 UI: 개요](images/tui-overview.png) -![터미널 UI: Top-N 클라이언트](images/tui-topn.png) +![터미널 UI: Top-N 대화](images/tui-topn.png) @@ -783,7 +805,7 @@ sudo traffic66 -capture en0 | `-sampling-wait` | `5m` | 레코드가 샘플링 레이트를 기다리는 시간 | | `-capture` | | 로컬 인터페이스에서 캡처(반복 지정 가능) | | `-inventory` | `/inventory.txt` | 이름 파일 | -| `-asn` | `/asn.tsv.gz` | IP-ASN 매핑 테이블 | +| `-asn` | `/asn.tsv.gz` | IP-ASN 매핑 테이블(`.mmdb` 파일은 업로드하거나 `/country.mmdb`와 `/asn.mmdb`에 둠) | | `-threat` | `/threats/*.txt` | 추가 위협 목록, `name=path` 형식(반복 지정 가능) | | `-dns-upstream` | 시스템 리졸버 | 호스트 이름 표시에 쓰는 DNS 서버 | | `-dns-rate` | `20` | 초당 최대 역방향 조회 횟수 | @@ -809,7 +831,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | 요약, 인터페이스 카운터, 현재 시간대 데이터 | | `password` | 로그인 비밀번호(해시) | | `inventory.txt` | 이름(**수집 상태 → 이름**) | -| `asn.tsv.gz`, `threats/` | 추가한 조회 테이블 | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | 추가한 국가 및 네트워크 데이터베이스와 위협 목록 | - **백업**: traffic66을 중지하고 디렉터리를 복사합니다. 중지하지 않고 하려면 `raw/`, `password`, `inventory.txt`를 복사합니다. 이 경우 현재 시간대 데이터와 요약은 빠집니다. @@ -875,7 +897,7 @@ Top 66은 약 9초이며, 소요 시간은 범위에 비례해 늘고 코어가 | "waiting for the sampling rate" | 장비가 아직 샘플러 옵션을 보내지 않았습니다. 대부분 몇 분 안에 다시 보냅니다. 끝내 보내지 않으면 익스포트하도록 설정하거나(Cisco는 `option sampler-table`), 정말 1:1이라면 이름에서 `unsampled`로 표시합니다 | | 수치가 인터페이스 카운터보다 낮음 | **인터페이스 대조** 확인: 경로상 유실, 샘플링되지 않은 인터페이스, 또는 플로가 아직 장비 캐시에 있음(액티브 타임아웃이 60초보다 김) | | 수치가 인터페이스 카운터보다 높음 | 같은 트래픽을 두 인터페이스 또는 두 장비에서 샘플링함 | -| 국가나 네트워크가 표시되지 않음 | IP-ASN 매핑 테이블이 없음: [국가](#8-countries-networks-and-threat-lists) 참조 | +| 국가나 네트워크가 표시되지 않음("알 수 없음") | 로드된 데이터베이스가 없음: **수집 상태**에서 업로드. [국가](#8-countries-networks-and-threat-lists) 참조 | | 페이지에 "데이터베이스가 메모리 한도에 도달해 응답하지 못했습니다" 표시 | 기간을 줄이거나 더 큰 `-memory`로 시작. 자세한 내용은 로그 참조 | | 비밀번호를 잊어버림 | traffic66 서버에서 `traffic66 passwd`(traffic66을 `-data`와 함께 실행한다면 `-data` 추가) | | `Conflicting lock is held` | 다른 traffic66이 이미 이 데이터 디렉터리를 사용 중 | diff --git a/docs/README.pt.md b/docs/README.pt.md index 8d962bc..eea61ab 100644 --- a/docs/README.pt.md +++ b/docs/README.pt.md @@ -19,7 +19,7 @@ equipamentos, em uma interface web e em uma interface de terminal. MPLS). - 13 idiomas na interface web e na interface de terminal. -![Visão geral: banda por aplicação em comparação com a semana passada, o que cresceu, principais clientes e serviços](images/overview.png) +![Visão geral: banda por aplicação em comparação com a semana passada, principais clientes e serviços](images/overview.png) Todas as capturas de tela vêm do `traffic66 demo`, uma rede corporativa simulada que você mesmo pode executar (veja [Testar a demo](#1-try-the-demo)). @@ -89,8 +89,8 @@ cd traffic66-windows-amd64 Abra http://127.0.0.1:8066 e entre como `admin` / `try66`. A demo monta a rede de uma pequena empresa com um dia de histórico e tráfego ao vivo de quatro equipamentos simulados, incluindo dois incidentes para encontrar: -comece em **Visão geral**, veja **Quem cresceu** e vá clicando a partir -daí. Pare com Ctrl+C. Os dados da demo ficam em `traffic66-demo`, ao lado +comece em **Visão geral**, clique em um host em **Principais clientes**, +escolha **Ver detalhes** e vá clicando a partir daí. Pare com Ctrl+C. Os dados da demo ficam em `traffic66-demo`, ao lado do programa; apague essa pasta para recomeçar a demo do zero. A demo usa as mesmas portas de uma instalação real (8066 e UDP 6343, 2055, @@ -562,7 +562,13 @@ conhecida. ## 7. Nomes, SNMP e suas próprias redes -**Fontes → Nomes** na interface web aceita uma entrada por linha. O +O jeito mais rápido de dar nome a um host ou equipamento: clique no endereço +dele em qualquer página e escolha **Dar um nome…**. Digite o nome e tecle +Enter; ele é salvo na hora e mostrado em todo lugar no lugar do endereço +puro. + +Para redes, interfaces e SNMP, **Fontes → Nomes** na interface web aceita +uma entrada por linha. O conteúdo é salvo como `inventory.txt` no diretório de dados, então você também pode editar esse arquivo (veja `inventory.txt.example`). Todas as linhas são opcionais. @@ -604,18 +610,27 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. Países, redes e listas de ameaças -Países e nomes de rede (AS) exigem uma tabela IP-para-ASN. Baixe a -gratuita em [iptoasn.com](https://iptoasn.com): +Países e nomes de rede (AS) exigem um banco de dados que associe os +endereços a eles. Envie um pela interface web: **Fontes → Banco de dados +de países e redes → Enviar arquivo de banco de dados…**. Ele é verificado, +salvo no diretório de dados e usado na hora para o tráfego novo; não é +preciso reiniciar. O tráfego já armazenado mantém o país com que foi salvo. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +Arquivos aceitos: -Qualquer arquivo no mesmo formato serve (separado por tabulação: primeiro -endereço, último endereço, número do AS, código do país, nome do AS; texto -puro ou gzip). Reinicie o traffic66 depois de trocá-lo; baixe um novo mais -ou menos todo mês. +| Arquivo | Fornece | Onde obter | +|---|---|---| +| DB-IP Lite country ou ASN, `.mmdb` | país, ou número e nome do AS | grátis, sem conta: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country ou ASN, `.mmdb` | país, ou número e nome do AS | grátis com uma conta MaxMind: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| Tabela IP-para-ASN, `.tsv` ou `.tsv.gz` | número do AS, nome do AS e país | grátis: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +Envie um banco de países e um banco de ASN para ter os dois; quando há +vários carregados, os arquivos `.mmdb` têm precedência no que contêm. Novas +versões saem todo mês: envie o arquivo novo da mesma forma para substituir +o antigo. + +Sem a interface web, copie o arquivo para o diretório de dados como +`country.mmdb`, `asn.mmdb` ou `asn.tsv.gz` e reinicie o traffic66. Listas de ameaças são arquivos de texto puro com um endereço ou rede por linha (o texto depois de `#` ou `;` é ignorado), salvos como @@ -642,6 +657,13 @@ porta, uma aplicação, um país, um equipamento) é clicável: aparecem abaixo da barra superior e valem para todas as páginas até você removê-los. - **Ver os registros de fluxo** abre os fluxos individuais correspondentes. +- **Ver detalhes** (hosts, equipamentos e serviços) abre uma página sobre + aquele host ou serviço: o tráfego ao longo do tempo por aplicação, com + quem ele fala, quais serviços ou clientes, países e os fluxos mais + recentes. Todo valor ali também é clicável, então dá para continuar + aprofundando; o botão Voltar do navegador retorna. +- **Dar um nome…** (hosts e equipamentos) dá um nome ao endereço, mostrado + em todo lugar a partir daí. - **Consultar on-line** abre o endereço ou o AS em um site público de consulta. - **Copiar** copia o valor. @@ -650,15 +672,14 @@ Páginas: | Página | O que responde | |---|---| -| Visão geral | Quanto tráfego há agora e em comparação com a semana passada, por aplicação; o que cresceu; principais clientes e serviços | -| Top-N | Os 66 maiores clientes, servidores, conversas, aplicações, portas, países, redes, segmentos, equipamentos, encapsulamentos ou VLANs | -| Conversas | Quem fala com quem: os 66 maiores pares cliente–servidor com o serviço | +| Visão geral | Quanto tráfego há agora e em comparação com a semana passada, por aplicação; principais clientes e serviços | +| Top-N | Uma única tabela dos 66 maiores: por padrão, conversas (cliente, servidor, serviço, país). Clique em um cabeçalho de coluna azul para agrupar por ele, em um cabeçalho numérico para ordenar; **Agrupar por** oferece aplicações, redes, segmentos, equipamentos, encapsulamento e VLAN | | Caminhos do tráfego | Qual segmento fala com qual aplicação em qual país | | Geografia e redes | Tráfego por país e por rede (AS) | | Ameaças | Hosts que se comunicaram com endereços das suas listas de ameaças, e quanto enviaram | | Registros de fluxo | Fluxos individuais, dos mais recentes para os mais antigos, com colunas selecionáveis | | Conferência de interfaces | Números de fluxo ao lado dos contadores de interface, piores primeiro, com os motivos | -| Fontes | Equipamentos, amostragem, perdas, coletores, SNMP e **Nomes** | +| Fontes | Equipamentos, amostragem, perdas, coletores, SNMP, o banco de dados de países e redes, e **Nomes** | Acima das páginas: intervalo de tempo (de 15 minutos a 30 dias), uma caixa de busca opcional, atualização automática a cada 30 segundos e @@ -670,12 +691,16 @@ Intervalos maiores que 6 horas começam em uma hora cheia, para que todos os números da página contem exatamente o mesmo tempo: "24 horas" cobre as últimas 24 horas cheias mais a atual. Nesses intervalos, o Top-N vem de resumos por hora; lá não há filtros, e a página avisa. Escolha um intervalo -menor para filtrar. **Conversas** sempre lê o detalhe dos fluxos, então em intervalos -longos com muitos fluxos pode demorar; uma hora é o mais rápido. +menor para filtrar. As conversas sempre leem o detalhe dos fluxos, então em intervalos +longos com muitos fluxos podem demorar; uma hora é o mais rápido. + +O menu lateral mostra quanto disco os dados usam e quanto está livre; passe +o mouse sobre o espaço livre para ver quanto os dias de detalhe mantidos +precisam no ritmo atual (estimado assim que houver um dia de dados). -![Top-N: os 66 maiores clientes da última hora](images/topn.png) +![Top-N: as 66 maiores conversas da última hora](images/topn.png) -![Conversas: quem fala com quem, com o serviço](images/conv.png) +![Detalhes de um host: o tráfego, com quem fala, serviços, países e fluxos mais recentes](images/detail.png) ![Caminhos do tráfego: qual segmento usa qual aplicação para qual país](images/paths.png) @@ -699,13 +724,13 @@ traffic66 roda com outro usuário, como acontece com um serviço, use `-user` e `-password`. `-lang` escolhe o idioma (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`). -Teclas: 1–9 páginas, ↑↓ selecionar, Enter ações sobre o valor selecionado, +Teclas: 1–8 páginas, ↑↓ selecionar, Enter ações sobre o valor selecionado, f mostrar só, x excluir, / buscar, t intervalo de tempo, c limpar filtros, w abrir a mesma visão no navegador, q sair. ![Interface de terminal: visão geral](images/tui-overview.png) -![Interface de terminal: Top-N de clientes](images/tui-topn.png) +![Interface de terminal: Top-N de conversas](images/tui-topn.png) @@ -828,7 +853,7 @@ Opções de `traffic66` e `traffic66 demo`: | `-sampling-wait` | `5m` | quanto tempo os registros esperam por uma taxa de amostragem | | `-capture` | | captura em uma interface local (repetível) | | `-inventory` | `/inventory.txt` | arquivo de nomes | -| `-asn` | `/asn.tsv.gz` | tabela IP-para-ASN | +| `-asn` | `/asn.tsv.gz` | tabela IP-para-ASN (arquivos `.mmdb`: envie-os, ou `/country.mmdb` e `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | lista de ameaças extra no formato `name=path` (repetível) | | `-dns-upstream` | resolvedor do sistema | servidor DNS para exibir nomes de host | | `-dns-rate` | `20` | máximo de consultas reversas por segundo | @@ -855,7 +880,7 @@ O diretório de dados guarda tudo: | `traffic66.duckdb` | resumos, contadores de interface e a hora atual | | `password` | senhas de login (em hash) | | `inventory.txt` | nomes (**Fontes → Nomes**) | -| `asn.tsv.gz`, `threats/` | tabelas de consulta que você adicionou | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | bancos de dados de países e redes e listas de ameaças que você adicionou | - **Backup**: pare o traffic66 e copie o diretório. Sem parar, copie `raw/`, `password` e `inventory.txt`; nesse caso ficam faltando a hora @@ -934,7 +959,7 @@ ficar sem memória. | "waiting for the sampling rate" | O equipamento ainda não enviou as opções do sampler; a maioria reenvia em poucos minutos. Se nunca enviar, exporte-as (`option sampler-table` no Cisco) ou marque-o como `unsampled` em Nomes se ele for de fato 1:1 | | Números abaixo dos contadores de interface | Veja **Conferência de interfaces**: perdas no caminho, interfaces não amostradas ou fluxos ainda no cache do equipamento (timeout ativo acima de 60 s) | | Números acima dos contadores de interface | O mesmo tráfego amostrado em duas interfaces ou dois equipamentos | -| Sem países nem redes | Falta a tabela IP-para-ASN: veja [Países](#8-countries-networks-and-threat-lists) | +| Sem países nem redes ("Desconhecido") | Nenhum banco de dados carregado: envie um em **Fontes**; veja [Países](#8-countries-networks-and-threat-lists) | | "O banco de dados atingiu o limite de memória e não conseguiu responder" em uma página | Escolha um período menor ou inicie com um `-memory` maior; os detalhes estão no log | | Esqueci a senha | `traffic66 passwd` na máquina do traffic66 (adicione `-data` se o traffic66 roda com ele) | | `Conflicting lock is held` | Outro traffic66 já usa este diretório de dados | diff --git a/docs/README.ru.md b/docs/README.ru.md index 46a4a62..b3cb308 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -18,7 +18,7 @@ записи потоков, инкапсуляция (GRE, IPIP, VXLAN, GENEVE, MPLS). - 13 языков в веб-интерфейсе и в терминальном интерфейсе. -![Обзор: полоса по приложениям в сравнении с прошлой неделей, что выросло, основные клиенты и сервисы](images/overview.png) +![Обзор: полоса по приложениям в сравнении с прошлой неделей, основные клиенты и сервисы](images/overview.png) Все скриншоты сделаны в `traffic66 demo` — смоделированной сети компании, которую можно запустить самому (см. [Попробовать демо](#1-try-the-demo)). @@ -88,7 +88,8 @@ cd traffic66-windows-amd64 Откройте http://127.0.0.1:8066 и войдите как `admin` / `try66`. Демо разворачивает небольшую сеть компании с историей за сутки и живым трафиком от четырёх имитируемых устройств; в ней спрятаны два инцидента. Начните со -страницы **Обзор**, посмотрите **Кто вырос** и дальше идите по щелчкам. +страницы **Обзор**, щёлкните хост в **Основные клиенты**, выберите +**Подробнее** и дальше идите по щелчкам. Остановка — Ctrl+C. Данные демо лежат в `traffic66-demo` рядом с программой; удалите эту папку, чтобы начать демо с нуля. @@ -558,8 +559,12 @@ NetFlow/IPFIX, пока коэффициент неизвестен; компе ## 7. Названия, SNMP и собственные сети -**Источники → Названия** в веб-интерфейсе принимает по одной записи на -строку. Содержимое сохраняется как `inventory.txt` в каталоге данных, так +Быстрее всего дать название хосту или устройству так: щёлкните его адрес на +любой странице и выберите **Дать название…**. Введите название и нажмите +Enter; оно сразу сохраняется и показывается везде вместо голого адреса. + +Для сетей, интерфейсов и SNMP раздел **Источники → Названия** в веб-интерфейсе +принимает по одной записи на строку. Содержимое сохраняется как `inventory.txt` в каталоге данных, так что можно править и сам файл (см. `inventory.txt.example`). Все строки необязательны. @@ -599,18 +604,27 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. Страны, сети и списки угроз -Для названий стран и сетей (AS) нужна таблица IP-to-ASN. Бесплатную можно -скачать с [iptoasn.com](https://iptoasn.com): +Для стран и названий сетей (AS) нужна база, сопоставляющая им адреса. +Загрузите её в веб-интерфейсе: **Источники → База стран и сетей → Загрузить файл базы…**. +Файл проверяется, сохраняется в каталоге данных и сразу используется для +нового трафика; перезапуск не нужен. Уже сохранённый трафик сохраняет ту +страну, с которой был записан. -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +Поддерживаемые файлы: -Подойдёт любой файл в том же формате (через табуляцию: первый адрес, -последний адрес, номер AS, код страны, название AS; без сжатия или в -gzip). После замены перезапустите traffic66; обновляйте файл примерно раз -в месяц. +| Файл | Что даёт | Где взять | +|---|---|---| +| DB-IP Lite country или ASN, `.mmdb` | страну или номер и название AS | бесплатно, без регистрации: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country или ASN, `.mmdb` | страну или номер и название AS | бесплатно, с учётной записью MaxMind: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| Таблица IP-to-ASN, `.tsv` или `.tsv.gz` | номер AS, название AS и страну | бесплатно: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +Чтобы получить и то и другое, загрузите базу стран и базу ASN; если загружено +несколько, файлы `.mmdb` имеют приоритет в том, что они содержат. Новые +версии выходят ежемесячно: чтобы заменить старый файл, загрузите новый тем же +способом. + +Без веб-интерфейса скопируйте файл в каталог данных как `country.mmdb`, +`asn.mmdb` или `asn.tsv.gz` и перезапустите traffic66. Списки угроз — это текстовые файлы с одним адресом или сетью на строку (текст после `#` или `;` игнорируется), сохранённые как @@ -638,6 +652,13 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh отображаются под верхней панелью и действуют на всех страницах, пока вы их не уберёте. - **Показать записи потоков** открывает соответствующие отдельные потоки. +- **Подробнее** (хосты, устройства и сервисы) открывает страницу об этом + одном хосте или сервисе: его трафик во времени по приложениям, с кем он + общается, какие сервисы или клиенты, страны и последние потоки. Любое + значение там снова можно щёлкнуть и углубляться дальше; кнопка «Назад» + в браузере возвращает обратно. +- **Дать название…** (хосты и устройства) даёт адресу название, которое + с этого момента показывается везде. - **Найти в интернете** открывает адрес или AS на публичном сайте-справочнике. - **Копировать** копирует значение. @@ -645,15 +666,14 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh | Страница | На какой вопрос отвечает | |---|---| -| Обзор | Сколько трафика сейчас и по сравнению с прошлой неделей, по приложениям; что выросло; основные клиенты и сервисы | -| Top-N | Топ 66 клиентов, серверов, сессий, приложений, портов, стран, сетей, сегментов, устройств, типов инкапсуляции или VLAN | -| Сеансы | Кто с кем общается: топ 66 пар клиент–сервер с их сервисом | +| Обзор | Сколько трафика сейчас и по сравнению с прошлой неделей, по приложениям; основные клиенты и сервисы | +| Top-N | Одна таблица топ 66: по умолчанию сеансы (клиент, сервер, сервис, страна). Щелчок по синему заголовку столбца группирует по нему, по числовому — сортирует; **Группировать по** предлагает приложения, сети, сегменты, устройства, инкапсуляцию и VLAN | | Пути трафика | Какой сегмент обращается к какому приложению в какой стране | | География и сети | Трафик по странам и по сетям (AS) | | Угрозы | Хосты, обращавшиеся к адресам из ваших списков угроз, и сколько они отправили | | Записи потоков | Отдельные потоки, новые сверху, с выбором столбцов | | Сверка интерфейсов | Цифры по потокам рядом со счётчиками интерфейсов, худшие сверху, с причинами | -| Источники | Устройства, сэмплирование, потери, коллекторы, SNMP и **Названия** | +| Источники | Устройства, сэмплирование, потери, коллекторы, SNMP, база стран и сетей и **Названия** | Над страницами: временной диапазон (от 15 минут до 30 дней), необязательная строка поиска, автообновление каждые 30 секунд и **Скопировать ссылку** — @@ -664,13 +684,18 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh странице считаются ровно за одно и то же время: «24 часа» — это последние 24 целых часа плюс текущий. Top-N за такие диапазоны строится по почасовым сводкам; фильтры там недоступны, и страница об этом сообщает. Чтобы -фильтровать, выберите диапазон покороче. **Сеансы** всегда читают подробные -данные потоков, поэтому на длинных диапазонах при большом числе потоков -страница может открываться долго; быстрее всего — один час. +фильтровать, выберите диапазон покороче. Сеансы всегда читаются из подробных +данных потоков, поэтому на длинных диапазонах при большом числе потоков +они могут строиться долго; быстрее всего — один час. + +В боковом меню видно, сколько места на диске занимают данные и сколько +свободно; наведите указатель на свободное место, чтобы увидеть, сколько +нужно хранимым дням подробных данных при текущей нагрузке (оценка появляется, +когда накопятся данные за сутки). -![Top-N: топ 66 клиентов за последний час](images/topn.png) +![Top-N: топ 66 сеансов за последний час](images/topn.png) -![Сеансы: кто с кем общается и через какой сервис](images/conv.png) +![Подробно об одном хосте: его трафик, с кем он общается, сервисы, страны и последние потоки](images/detail.png) ![Пути трафика: какой сегмент к какому приложению в какой стране обращается](images/paths.png) @@ -694,13 +719,13 @@ traffic66 -tui # collect and show the termina `-user` и `-password`. `-lang` выбирает язык (`en`, `zh`, `hi`, `es`, `ar`, `fr`, `bn`, `pt`, `ru`, `id`, `ur`, `ja`, `ko`). -Клавиши: 1–9 страницы, ↑↓ выбор, Enter действия над выбранным значением, +Клавиши: 1–8 страницы, ↑↓ выбор, Enter действия над выбранным значением, f показать только это, x исключить, / поиск, t временной диапазон, c сбросить фильтры, w открыть тот же вид в браузере, q выход. ![Терминальный интерфейс: обзор](images/tui-overview.png) -![Терминальный интерфейс: Top-N клиентов](images/tui-topn.png) +![Терминальный интерфейс: Top-N сеансов](images/tui-topn.png) @@ -823,7 +848,7 @@ Wi-Fi. | `-sampling-wait` | `5m` | сколько записи ждут коэффициент сэмплирования | | `-capture` | | захват на локальном интерфейсе (можно повторять) | | `-inventory` | `/inventory.txt` | файл названий | -| `-asn` | `/asn.tsv.gz` | таблица IP-to-ASN | +| `-asn` | `/asn.tsv.gz` | таблица IP-to-ASN (файлы `.mmdb`: загрузите их или положите как `/country.mmdb` и `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | дополнительный список угроз в виде `name=path` (можно повторять) | | `-dns-upstream` | системный резолвер | DNS-сервер для отображения имён хостов | | `-dns-rate` | `20` | максимум обратных DNS-запросов в секунду | @@ -850,7 +875,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | сводки, счётчики интерфейсов и текущий час | | `password` | пароли для входа (в виде хешей) | | `inventory.txt` | названия (**Источники → Названия**) | -| `asn.tsv.gz`, `threats/` | добавленные вами справочные таблицы | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | добавленные вами базы стран и сетей и списки угроз | - **Резервное копирование**: остановите traffic66 и скопируйте каталог. Без остановки копируйте `raw/`, `password` и `inventory.txt`; текущего часа и @@ -928,7 +953,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | "waiting for the sampling rate" | Устройство ещё не прислало sampler options; большинство устройств повторяют их в течение нескольких минут. Если так и не пришлёт, включите их экспорт (`option sampler-table` на Cisco) или пометьте устройство как `unsampled` в разделе «Названия», если оно действительно экспортирует 1:1 | | Цифры ниже счётчиков интерфейсов | См. **Сверка интерфейсов**: потери по пути, несэмплируемые интерфейсы или потоки ещё в кеше устройства (active timeout больше 60 с) | | Цифры выше счётчиков интерфейсов | Один и тот же трафик сэмплируется на двух интерфейсах или двух устройствах | -| Нет стран и сетей | Нет таблицы IP-to-ASN: см. [Страны](#8-countries-networks-and-threat-lists) | +| Нет стран и сетей («Неизвестно») | Не загружена база: загрузите её на странице **Источники**, см. [Страны](#8-countries-networks-and-threat-lists) | | "База данных упёрлась в лимит памяти и не смогла ответить" на странице | Выберите период покороче или запустите с бо́льшим `-memory`; подробности в журнале | | Забыт пароль | `traffic66 passwd` на машине с traffic66 (добавьте `-data`, если traffic66 запущен с ним) | | `Conflicting lock is held` | Этот каталог данных уже использует другой экземпляр traffic66 | diff --git a/docs/README.ur.md b/docs/README.ur.md index 7f8d4a7..0780926 100644 --- a/docs/README.ur.md +++ b/docs/README.ur.md @@ -20,7 +20,7 @@ embedded database میں رکھتا ہے، اور دکھاتا ہے کہ bandwid flow records، encapsulation (GRE، IPIP، VXLAN، GENEVE، MPLS)۔ - web UI اور terminal UI میں 13 زبانیں۔ -![جائزہ: پچھلے ہفتے کے مقابلے میں application کے حساب سے bandwidth، کیا بڑھا، سرفہرست clients اور services](images/overview.png) +![جائزہ: پچھلے ہفتے کے مقابلے میں application کے حساب سے bandwidth، سرفہرست clients اور services](images/overview.png) تمام اسکرین شاٹس `traffic66 demo` سے لیے گئے ہیں، ایک simulated کمپنی نیٹ ورک جسے آپ خود چلا سکتے ہیں ([ڈیمو چلا کر دیکھیں](#1-try-the-demo) دیکھیں)۔ @@ -90,8 +90,8 @@ cd traffic66-windows-amd64 http://127.0.0.1:8066 کھولیں اور `admin` / `try66` سے سائن اِن کریں۔ ڈیمو ایک چھوٹی کمپنی کا نیٹ ورک بناتا ہے جس میں ایک دن کی ہسٹری اور چار simulated ڈیوائسز سے live ٹریفک ہوتی ہے، اور ڈھونڈنے کے لیے دو incidents بھی ہیں: -**جائزہ** سے شروع کریں، **کس میں اضافہ ہوا** دیکھیں، اور وہاں سے کلک کرتے ہوئے -آگے بڑھیں۔ Ctrl+C سے بند کریں۔ ڈیمو کا ڈیٹا پروگرام کے ساتھ `traffic66-demo` +**جائزہ** سے شروع کریں، **سرفہرست کلائنٹ** میں کسی host پر کلک کریں، +**تفصیل دیکھیں** چنیں، اور وہاں سے کلک کرتے ہوئے آگے بڑھیں۔ Ctrl+C سے بند کریں۔ ڈیمو کا ڈیٹا پروگرام کے ساتھ `traffic66-demo` میں رہتا ہے؛ ڈیمو نئے سرے سے شروع کرنے کے لیے وہ فولڈر ڈیلیٹ کر دیں۔ ڈیمو وہی ports استعمال کرتا ہے جو اصل installation کرتی ہے (8066، اور UDP @@ -554,7 +554,12 @@ rate ابھی معلوم نہیں۔ ## 7. نام، SNMP اور آپ کے اپنے نیٹ ورک -web UI میں **ذرائع → نام** ہر لائن میں ایک entry لیتا ہے۔ یہ data directory میں +کسی host یا ڈیوائس کو نام دینے کا سب سے تیز طریقہ: کسی بھی صفحے پر اس کے address +پر کلک کریں اور **نام دیں…** چنیں۔ نام ٹائپ کر کے Enter دبائیں؛ یہ فوراً محفوظ ہو +جاتا ہے اور ہر جگہ خالی address کی بجائے دکھایا جاتا ہے۔ + +نیٹ ورکس، interfaces اور SNMP کے لیے، web UI میں **ذرائع → نام** ہر لائن میں +ایک entry لیتا ہے۔ یہ data directory میں `inventory.txt` کے طور پر محفوظ ہوتا ہے، اس لیے آپ وہ فائل براہ راست بھی edit کر سکتے ہیں (`inventory.txt.example` دیکھیں)۔ ہر لائن اختیاری ہے۔ @@ -592,17 +597,26 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. ممالک، نیٹ ورک اور threat lists -ممالک اور نیٹ ورک (AS) کے ناموں کے لیے IP-to-ASN table درکار ہے۔ -[iptoasn.com](https://iptoasn.com) سے مفت table ڈاؤن لوڈ کریں: +ممالک اور نیٹ ورک (AS) کے ناموں کے لیے ایسا database درکار ہے جو addresses کو +ان سے جوڑے۔ اسے web UI میں اپ لوڈ کریں: **ذرائع → ممالک اور نیٹ ورکس کا ڈیٹابیس → ڈیٹابیس فائل اپ لوڈ کریں…**۔ +فائل جانچی جاتی ہے، data directory میں محفوظ ہوتی ہے اور نئی ٹریفک کے لیے فوراً +استعمال ہوتی ہے؛ restart کی ضرورت نہیں۔ پہلے سے محفوظ ٹریفک وہی ملک رکھتی ہے +جس کے ساتھ وہ محفوظ ہوئی تھی۔ -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +قابلِ قبول فائلیں: -اسی format کی کوئی بھی فائل چلے گی (tab سے الگ: پہلا address، آخری address، AS -number، country code، AS name؛ plain یا gzip)۔ اسے بدلنے کے بعد traffic66 -restart کریں؛ تقریباً ہر مہینے نئی فائل ڈاؤن لوڈ کریں۔ +| فائل | کیا دیتی ہے | کہاں سے ملے گی | +|---|---|---| +| DB-IP Lite country یا ASN، `.mmdb` | ملک، یا AS number اور نام | مفت، account کے بغیر: [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country یا ASN، `.mmdb` | ملک، یا AS number اور نام | MaxMind account کے ساتھ مفت: [maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| IP-to-ASN table، `.tsv` یا `.tsv.gz` | AS number، AS name اور ملک | مفت: [iptoasn.com](https://iptoasn.com) (`ip2asn-combined.tsv.gz`) | + +دونوں حاصل کرنے کے لیے ایک country database اور ایک ASN database اپ لوڈ کریں؛ +جہاں کئی لوڈ ہوں، وہاں `.mmdb` فائلیں اپنے مواد کے لیے ترجیح پاتی ہیں۔ نئے +versions ہر مہینے آتے ہیں: پرانی فائل بدلنے کے لیے نئی فائل اسی طرح اپ لوڈ کریں۔ + +web UI کے بغیر، فائل کو data directory میں `country.mmdb`، `asn.mmdb` یا +`asn.tsv.gz` کے نام سے کاپی کریں اور traffic66 restart کریں۔ Threat lists سادہ text فائلیں ہیں، ہر لائن میں ایک address یا نیٹ ورک (`#` یا `;` کے بعد کا متن نظرانداز ہوتا ہے)، جو @@ -628,6 +642,13 @@ application، ملک، ڈیوائس — پر کلک کیا جا سکتا ہے: - **صرف یہ دکھائیں** / **اسے خارج کریں** ایک filter شامل کرتا ہے۔ filters اوپر والی bar کے نیچے نظر آتے ہیں اور ہٹائے جانے تک ہر صفحے پر لاگو رہتے ہیں۔ - **اس کے فلو ریکارڈ دکھائیں** متعلقہ انفرادی flows کھولتا ہے۔ +- **تفصیل دیکھیں** (hosts، ڈیوائسز اور services) اس ایک host یا service کے بارے + میں ایک صفحہ کھولتا ہے: وقت کے ساتھ application کے حساب سے اس کی ٹریفک، وہ کس + سے بات کرتا ہے، کون سی services یا clients، ممالک اور اس کے تازہ ترین flows۔ + وہاں کی ہر value پر دوبارہ کلک کیا جا سکتا ہے، تاکہ آپ مزید گہرائی میں جا سکیں؛ + browser کا Back بٹن واپس لے جاتا ہے۔ +- **نام دیں…** (hosts اور ڈیوائسز) address کو ایک نام دیتا ہے، جو اس کے بعد ہر + جگہ دکھایا جاتا ہے۔ - **آن لائن معلوم کریں** address یا AS کو کسی public lookup سائٹ پر کھولتا ہے۔ - **کاپی کریں** value کاپی کرتا ہے۔ @@ -635,15 +656,14 @@ application، ملک، ڈیوائس — پر کلک کیا جا سکتا ہے: | صفحہ | کس سوال کا جواب دیتا ہے | |---|---| -| جائزہ | ابھی کتنی ٹریفک ہے اور پچھلے ہفتے کے مقابلے میں کتنی، application کے حساب سے؛ کیا بڑھا؛ سرفہرست clients اور services | -| ٹاپ N | clients، servers، conversations، applications، ports، ممالک، نیٹ ورکس، segments، ڈیوائسز، encapsulation یا VLAN کے top 66 | -| گفتگو | کون کس سے بات کرتا ہے: سرفہرست 66 client–server جوڑے، ان کی service کے ساتھ | +| جائزہ | ابھی کتنی ٹریفک ہے اور پچھلے ہفتے کے مقابلے میں کتنی، application کے حساب سے؛ سرفہرست clients اور services | +| ٹاپ N | سرفہرست 66 کی ایک table: ڈیفالٹ طور پر conversations (client، server، service، ملک)۔ کسی نیلے column heading پر کلک کر کے اس کے حساب سے group کریں، کسی عددی heading پر کلک کر کے sort کریں؛ **گروپ بندی بلحاظ** میں applications، نیٹ ورکس، segments، ڈیوائسز، encapsulation اور VLAN ملتے ہیں | | ٹریفک کے راستے | کون سا segment کس ملک میں کس application سے بات کرتا ہے | | مقامات اور نیٹ ورک | ملک کے حساب سے اور نیٹ ورک (AS) کے حساب سے ٹریفک | | خطرے کی معلومات | وہ hosts جنہوں نے آپ کی threat lists کے addresses سے بات کی، اور کتنا بھیجا | | فلو ریکارڈ | انفرادی flows، نئے پہلے، منتخب کیے جا سکنے والے columns کے ساتھ | | انٹرفیس جانچ | interface counters کے ساتھ flow کے اعداد، بدترین پہلے، وجوہات کے ساتھ | -| ذرائع | ڈیوائسز، sampling، loss، collectors، SNMP، اور **نام** | +| ذرائع | ڈیوائسز، sampling، loss، collectors، SNMP، ممالک اور نیٹ ورکس کا ڈیٹابیس، اور **نام** | صفحات کے اوپر: time range (15 منٹ سے 30 دن)، ایک اختیاری search box، ہر 30 سیکنڈ پر automatic refresh، اور **لنک کاپی کریں**، جو بالکل موجودہ view (صفحہ، @@ -653,12 +673,16 @@ time range اور filters) کا link کاپی کرتا ہے تاکہ آپ اسے 6 گھنٹے سے لمبی ranges پورے گھنٹے سے شروع ہوتی ہیں، تاکہ صفحے کا ہر عدد بالکل ایک ہی وقت گنے: "24 گھنٹے" میں آخری 24 پورے گھنٹے اور موجودہ گھنٹہ شامل ہیں۔ ان ranges پر ٹاپ N گھنٹہ وار summaries سے آتا ہے؛ وہاں filters -دستیاب نہیں، اور صفحہ یہ بتا دیتا ہے۔ filter کرنے کے لیے چھوٹی range چنیں۔ **گفتگو** ہمیشہ flow کی تفصیل پڑھتا ہے، اس لیے -زیادہ flow rates پر لمبی ranges میں اسے کچھ وقت لگ سکتا ہے؛ ایک گھنٹہ سب سے تیز ہے۔ +دستیاب نہیں، اور صفحہ یہ بتا دیتا ہے۔ filter کرنے کے لیے چھوٹی range چنیں۔ گفتگو (conversations) ہمیشہ flow کی تفصیل پڑھتی ہے، اس لیے +زیادہ flow rates پر لمبی ranges میں اس میں کچھ وقت لگ سکتا ہے؛ ایک گھنٹہ سب سے تیز ہے۔ + +سائیڈ menu دکھاتا ہے کہ data کتنی disk استعمال کر رہا ہے اور کتنی خالی ہے؛ خالی +جگہ پر hover کریں تو پتا چلتا ہے کہ موجودہ رفتار پر رکھے گئے دنوں کی تفصیل کو کتنی +جگہ چاہیے (ایک دن کا data جمع ہونے کے بعد اندازہ لگایا جاتا ہے)۔ -![ٹاپ N: پچھلے گھنٹے کے top 66 clients](images/topn.png) +![ٹاپ N: پچھلے گھنٹے کی top 66 conversations](images/topn.png) -![گفتگو: کون کس سے بات کرتا ہے، service کے ساتھ](images/conv.png) +![ایک host کی تفصیل: اس کی ٹریفک، وہ کس سے بات کرتا ہے، services، ممالک اور تازہ ترین flows](images/detail.png) ![ٹریفک کے راستے: کون سا segment کس ملک کی طرف کون سی application استعمال کرتا ہے](images/paths.png) @@ -682,13 +706,13 @@ directory پڑھ سکے (ڈیفالٹ نہ ہو تو `-data` دیں)۔ اگر tr `-password` استعمال کریں۔ `-lang` زبان چنتا ہے (`en`، `zh`، `hi`، `es`، `ar`، `fr`، `bn`، `pt`، `ru`، `id`، `ur`، `ja`، `ko`)۔ -Keys: 1–9 صفحات، ↑↓ منتخب کریں، Enter منتخب value پر actions، f صرف یہ +Keys: 1–8 صفحات، ↑↓ منتخب کریں، Enter منتخب value پر actions، f صرف یہ دکھائیں، x خارج کریں، / search، t time range، c filters صاف کریں، w یہی view browser میں کھولیں، q باہر نکلیں۔ ![Terminal UI: جائزہ](images/tui-overview.png) -![Terminal UI: ٹاپ N clients](images/tui-topn.png) +![Terminal UI: ٹاپ N conversations](images/tui-topn.png) @@ -809,7 +833,7 @@ Capture کے لیے root درکار ہے؛ کچھ انسٹال نہیں کرنا | `-sampling-wait` | `5m` | records کتنی دیر sampling rate کا انتظار کریں | | `-capture` | | local interface پر capture (دہرایا جا سکتا ہے) | | `-inventory` | `/inventory.txt` | ناموں کی فائل | -| `-asn` | `/asn.tsv.gz` | IP-to-ASN table | +| `-asn` | `/asn.tsv.gz` | IP-to-ASN table (`.mmdb` فائلیں: انہیں اپ لوڈ کریں، یا `/country.mmdb` اور `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | اضافی threat list، `name=path` کی شکل میں (دہرایا جا سکتا ہے) | | `-dns-upstream` | system resolver | host names دکھانے کے لیے DNS server | | `-dns-rate` | `20` | فی سیکنڈ زیادہ سے زیادہ reverse lookups | @@ -835,7 +859,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | summaries، interface counters اور موجودہ گھنٹہ | | `password` | login پاس ورڈز (hashed) | | `inventory.txt` | نام (**ذرائع → نام**) | -| `asn.tsv.gz`، `threats/` | آپ کی شامل کردہ lookup tables | +| `country.mmdb`، `asn.mmdb`، `asn.tsv.gz`، `threats/` | آپ کے شامل کردہ ممالک اور نیٹ ورکس کے databases اور threat lists | - **بیک اپ**: traffic66 روکیں اور directory کاپی کریں۔ روکے بغیر `raw/`، `password` اور `inventory.txt` کاپی کریں؛ اس صورت میں موجودہ گھنٹہ اور @@ -911,7 +935,7 @@ container کی memory limit۔ `-memory` والے حصے کا تقریباً 2.5 | "waiting for the sampling rate" | ڈیوائس نے ابھی تک اپنے sampler options نہیں بھیجے؛ زیادہ تر چند منٹ میں دوبارہ بھیج دیتی ہیں۔ اگر کبھی نہ بھیجے تو انہیں export کروائیں (Cisco پر `option sampler-table`) یا اگر وہ واقعی 1:1 ہے تو نام میں اسے `unsampled` لکھیں | | اعداد interface counters سے کم | **انٹرفیس جانچ** دیکھیں: راستے میں loss، interfaces sample نہیں ہو رہے، یا flows ابھی ڈیوائس کی cache میں ہیں (active timeout 60 s سے لمبا) | | اعداد interface counters سے زیادہ | ایک ہی ٹریفک دو interfaces یا دو ڈیوائسز پر sample ہو رہی ہے | -| کوئی ملک یا نیٹ ورک نہیں | IP-to-ASN table نہیں ہے: [ممالک](#8-countries-networks-and-threat-lists) دیکھیں | +| کوئی ملک یا نیٹ ورک نہیں ("نامعلوم") | کوئی database لوڈ نہیں: **ذرائع** پر ایک اپ لوڈ کریں، [ممالک](#8-countries-networks-and-threat-lists) دیکھیں | | صفحے پر "ڈیٹا بیس اپنی میموری کی حد تک پہنچ گیا اور جواب نہیں دے سکا" | کم وقت کی حد چنیں، یا بڑے `-memory` کے ساتھ شروع کریں؛ تفصیل log میں ہے | | پاس ورڈ بھول گئے | traffic66 مشین پر `traffic66 passwd` (اگر traffic66 `-data` کے ساتھ چلتا ہے تو `-data` شامل کریں) | | `Conflicting lock is held` | کوئی دوسرا traffic66 پہلے سے یہی data directory استعمال کر رہا ہے | diff --git a/docs/README.zh.md b/docs/README.zh.md index 2251e2e..9ceabec 100644 --- a/docs/README.zh.md +++ b/docs/README.zh.md @@ -10,7 +10,7 @@ - Top 66 排行、流向、国家与运营商网络、威胁情报命中、流记录、封装(GRE、IPIP、VXLAN、GENEVE、MPLS)。 - Web 界面和终端界面均支持 13 种语言。 -![概览:按应用划分的带宽与上周对比、增长情况、主要客户端和服务](images/overview.png) +![概览:按应用划分的带宽与上周对比、主要客户端和服务](images/overview.png) 所有截图均来自 `traffic66 demo`,这是一个你可以自己运行的模拟公司网络(见 [试用演示](#1-try-the-demo))。 @@ -75,7 +75,7 @@ cd traffic66-windows-amd64 .\traffic66.exe demo -password try66 ``` -打开 http://127.0.0.1:8066,用 `admin` / `try66` 登录。演示模式会搭建一个小型公司网络,带一天的历史数据,外加四台模拟设备的实时流量,其中埋了两起异常事件等你去找:从 **概览** 开始,看 **比平时多了谁**,然后一路点下去即可。按 Ctrl+C 停止。演示数据保存在程序旁边的 `traffic66-demo` 目录中,删除该目录即可从头开始。 +打开 http://127.0.0.1:8066,用 `admin` / `try66` 登录。演示模式会搭建一个小型公司网络,带一天的历史数据,外加四台模拟设备的实时流量,其中埋了两起异常事件等你去找:从 **概览** 开始,在 **Top 客户端** 中点击一台主机,选择 **查看详情**,然后继续一路点下去即可。按 Ctrl+C 停止。演示数据保存在程序旁边的 `traffic66-demo` 目录中,删除该目录即可从头开始。 演示模式使用与正式安装相同的端口(8066,以及 UDP 6343、2055、4739)。如果要和正式实例同时运行,请换用其他端口:`traffic66 demo -password try66 -addr :8067 -listen ""`。 @@ -472,7 +472,9 @@ softflowd -i eth0 -n 192.0.2.50:2055 -v 9 -t maxlife=60 ## 7. 名称、SNMP 与自有网段 -Web 界面中的 **接入 → 名称** 每行一条。内容保存为数据目录下的 `inventory.txt`,因此也可以直接编辑该文件(参考 `inventory.txt.example`)。每一行都是可选的。 +给主机或设备命名最快的方法:在任意页面点击它的地址,选择 **起个名字…**。输入名称后按 Enter,立即保存,之后在所有地方都显示该名称,而不是光秃秃的地址。 + +网段、接口和 SNMP 则在 Web 界面的 **接入 → 名称** 中配置,每行一条。内容保存为数据目录下的 `inventory.txt`,因此也可以直接编辑该文件(参考 `inventory.txt.example`)。每一行都是可选的。 ``` # your networks: traffic between them is "internal" @@ -502,14 +504,19 @@ snmp 192.0.2.9 s3cret 10.99.0.9:161 ## 8. 国家、运营商网络与威胁情报列表 -显示国家和网络(AS)名称需要一张 IP 到 ASN 的映射表。可从[iptoasn.com](https://iptoasn.com) 免费下载: +显示国家和网络(AS)名称需要一个把地址映射到国家和网络的数据库。在 Web 界面中上传即可:**接入 → 国家和运营商数据库 → 上传数据库文件…**。文件会经过校验、保存到数据目录,并立即用于新的流量,无需重启。已经存储的流量保留保存时的国家。 -``` -curl -LO https://iptoasn.com/data/ip2asn-combined.tsv.gz -mv ip2asn-combined.tsv.gz /asn.tsv.gz -``` +可接受的文件: -任何相同格式的文件都可以(Tab 分隔:起始地址、结束地址、AS 号、国家代码、AS 名称;纯文本或 gzip 均可)。替换后需重启 traffic66;建议每月左右更新一次。 +| 文件 | 提供 | 获取途径 | +|---|---|---| +| DB-IP Lite country 或 ASN,`.mmdb` | 国家,或 AS 号和名称 | 免费,无需账号:[db-ip.com/db/lite.php](https://db-ip.com/db/lite.php) | +| MaxMind GeoLite2 Country 或 ASN,`.mmdb` | 国家,或 AS 号和名称 | 注册 MaxMind 账号后免费:[maxmind.com](https://www.maxmind.com/en/geolite2/signup) | +| IP 到 ASN 映射表,`.tsv` 或 `.tsv.gz` | AS 号、AS 名称和国家 | 免费:[iptoasn.com](https://iptoasn.com)(`ip2asn-combined.tsv.gz`) | + +同时上传一个国家数据库和一个 ASN 数据库即可两者兼得;加载了多个时,`.mmdb` 文件对其包含的内容优先。新版本每月发布:用同样的方式上传新文件即可替换旧文件。 + +不使用 Web 界面时,把文件复制到数据目录,命名为 `country.mmdb`、`asn.mmdb` 或 `asn.tsv.gz`,然后重启 traffic66。 威胁情报列表是纯文本文件,每行一个地址或网段(`#` 或 `;` 之后的内容会被忽略),保存为 `/threats/.txt`,例如: @@ -530,6 +537,8 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh - **只看它** / **排除它**:添加过滤条件。过滤条件显示在顶栏下方,在你移除之前对所有页面生效。 - **看它的流记录**:打开匹配的单条流记录。 +- **查看详情**(主机、设备和服务):打开关于该主机或服务的页面:按应用划分的流量随时间的变化、它在和谁通信、哪些服务或客户端、国家以及最新的流记录。那里的每个值都可以再次点击,因此可以一路深挖下去;用浏览器的后退按钮返回。 +- **起个名字…**(主机和设备):给该地址起个名字,此后在所有地方显示。 - **在外部网站查询**:在公共查询网站中打开该地址或 AS。 - **复制**:复制该值。 @@ -537,23 +546,24 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh | 页面 | 回答什么问题 | |---|---| -| 概览 | 当前流量多大、与上周相比如何,按应用拆分;什么在增长;主要客户端和服务 | -| Top-N | 客户端、服务器、会话、应用、端口、国家、网络、网段、设备、封装或 VLAN 的前 66 名 | -| 会话 | 谁在和谁通信:前 66 个客户端–服务器对及其服务 | +| 概览 | 当前流量多大、与上周相比如何,按应用拆分;主要客户端和服务 | +| Top-N | 一张前 66 名的表:默认是会话(客户端、服务器、服务、国家)。点击蓝色列标题按该列分组,点击数字列标题排序;**分组方式** 还提供应用、网络、网段、设备、封装和 VLAN | | 流向 | 哪个网段在访问哪个国家的哪个应用 | | 地理与运营商 | 按国家和按网络(AS)统计的流量 | | 威胁情报 | 与威胁情报列表中地址有通信的主机,以及它们发送了多少流量 | | 流记录 | 单条流记录,最新的在前,可选择显示列 | | 接口对账 | 流统计与接口计数器并列对比,差异最大的在前,附原因 | -| 接入 | 设备、采样、丢包、采集器、SNMP,以及 **名称** | +| 接入 | 设备、采样、丢包、采集器、SNMP、国家和运营商数据库,以及 **名称** | 页面上方有:时间范围(15 分钟到 30 天)、可选的搜索框、每 30 秒自动刷新,以及**复制链接**——复制一个精确指向当前视图(页面、时间范围和过滤条件)的链接,方便发给同事。界面语言跟随浏览器设置,可在菜单底部切换。 -超过 6 小时的时间范围从整点开始,因此页面上的每个数字统计的都是完全相同的时间段:“24 小时”涵盖最近 24 个完整小时加上当前这一小时。这些范围的 Top-N 来自小时汇总数据,无法使用过滤条件,页面上也会有提示。需要过滤时请选择较短的时间范围。**会话** 页面始终读取流明细,因此在流量速率高时选择长时间范围可能需要等一会儿;1 小时最快。 +超过 6 小时的时间范围从整点开始,因此页面上的每个数字统计的都是完全相同的时间段:“24 小时”涵盖最近 24 个完整小时加上当前这一小时。这些范围的 Top-N 来自小时汇总数据,无法使用过滤条件,页面上也会有提示。需要过滤时请选择较短的时间范围。会话始终读取流明细,因此在流量速率高时选择长时间范围可能需要等一会儿;1 小时最快。 + +侧边菜单显示数据占用了多少磁盘、还剩多少空间;鼠标悬停在剩余空间上,可以看到按当前速率保留各天明细所需的空间(有一天的数据后开始估算)。 -![Top-N:最近一小时的前 66 个客户端](images/topn.png) +![Top-N:最近一小时的前 66 个会话](images/topn.png) -![会话:谁在和谁通信,以及所用服务](images/conv.png) +![单台主机的详情:它的流量、在和谁通信、服务、国家和最新流记录](images/detail.png) ![流向:哪个网段在访问哪个国家的哪个应用](images/paths.png) @@ -573,11 +583,11 @@ traffic66 -tui # collect and show the termina 在 traffic66 主机上,只要能读取数据目录,`traffic66 tui` 就会自动登录(数据目录不是默认位置时请指定 `-data`)。如果 traffic66 以其他用户身份运行(作为服务运行时就是这样),请改用 `-user` 和 `-password`。`-lang` 选择语言(`en`、`zh`、`hi`、`es`、`ar`、`fr`、`bn`、`pt`、`ru`、`id`、`ur`、`ja`、`ko`)。 -按键:1–9 切换页面,↑↓ 选择,Enter 对选中的值执行操作,f 只看,x 排除,/ 搜索,t 时间范围,c 清除过滤条件,w 在浏览器中打开相同视图,q 退出。 +按键:1–8 切换页面,↑↓ 选择,Enter 对选中的值执行操作,f 只看,x 排除,/ 搜索,t 时间范围,c 清除过滤条件,w 在浏览器中打开相同视图,q 退出。 ![终端界面:概览](images/tui-overview.png) -![终端界面:Top-N 客户端](images/tui-topn.png) +![终端界面:Top-N 会话](images/tui-topn.png) @@ -682,7 +692,7 @@ sudo traffic66 -capture en0 | `-sampling-wait` | `5m` | 记录等待采样率的最长时间 | | `-capture` | | 在本机网卡上抓包(可重复指定) | | `-inventory` | `/inventory.txt` | 名称文件 | -| `-asn` | `/asn.tsv.gz` | IP 到 ASN 映射表 | +| `-asn` | `/asn.tsv.gz` | IP 到 ASN 映射表(`.mmdb` 文件:在 Web 界面上传,或放在 `/country.mmdb` 和 `/asn.mmdb`) | | `-threat` | `/threats/*.txt` | 额外的威胁情报列表,格式为 `name=path`(可重复指定) | | `-dns-upstream` | 系统解析器 | 用于显示主机名的 DNS 服务器 | | `-dns-rate` | `20` | 每秒反向解析次数上限 | @@ -708,7 +718,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | `traffic66.duckdb` | 汇总数据、接口计数器和当前小时的数据 | | `password` | 登录密码(哈希) | | `inventory.txt` | 名称(**接入 → 名称**) | -| `asn.tsv.gz`, `threats/` | 你添加的查询表 | +| `country.mmdb`, `asn.mmdb`, `asn.tsv.gz`, `threats/` | 你添加的国家和运营商数据库以及威胁情报列表 | - **备份**:停止 traffic66,复制整个目录。如果不停服务,就复制 `raw/`、`password`和 `inventory.txt`;这样会缺少当前小时的数据和汇总数据。 - **迁移**:停止 traffic66,移动目录,启动时用 `-data` 指向新位置。 @@ -752,7 +762,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | "waiting for the sampling rate" | 设备尚未发送采样器选项;大多数设备几分钟内会重发。如果一直不发,请配置导出(Cisco 上为 `option sampler-table`);如果确实是 1:1,可在名称中标记为 `unsampled` | | 统计值低于接口计数器 | 查看 **接口对账**:途中丢包、有接口未采样,或流仍在设备缓存中(活动超时超过 60 秒) | | 统计值高于接口计数器 | 同一流量在两个接口或两台设备上被采样 | -| 没有国家或网络信息 | 缺少 IP 到 ASN 映射表:见 [国家](#8-countries-networks-and-threat-lists) | +| 没有国家或网络信息("未知") | 未加载数据库:在 **接入** 上传一个,见 [国家](#8-countries-networks-and-threat-lists) | | 页面上出现 "数据库达到内存上限,无法完成这次查询" | 选择更短的时间范围,或用更大的 `-memory` 启动;详细信息见日志 | | 忘记密码 | 在 traffic66 主机上执行 `traffic66 passwd`(如果 traffic66 运行时使用了 `-data`,也加上它) | | `Conflicting lock is held` | 另一个 traffic66 正在使用该数据目录 | diff --git a/docs/images/conv.png b/docs/images/conv.png deleted file mode 100644 index 53e711a..0000000 Binary files a/docs/images/conv.png and /dev/null differ diff --git a/docs/images/detail.png b/docs/images/detail.png new file mode 100644 index 0000000..b027778 Binary files /dev/null and b/docs/images/detail.png differ diff --git a/docs/images/interfaces.png b/docs/images/interfaces.png index 6f293b4..f5b4b58 100644 Binary files a/docs/images/interfaces.png and b/docs/images/interfaces.png differ diff --git a/docs/images/overview-zh.png b/docs/images/overview-zh.png index 55805fc..3f7bc6a 100644 Binary files a/docs/images/overview-zh.png and b/docs/images/overview-zh.png differ diff --git a/docs/images/overview.png b/docs/images/overview.png index f1edc27..776dbe7 100644 Binary files a/docs/images/overview.png and b/docs/images/overview.png differ diff --git a/docs/images/paths.png b/docs/images/paths.png index a71d479..5116825 100644 Binary files a/docs/images/paths.png and b/docs/images/paths.png differ diff --git a/docs/images/sources.png b/docs/images/sources.png index 06087c3..7993953 100644 Binary files a/docs/images/sources.png and b/docs/images/sources.png differ diff --git a/docs/images/threats.png b/docs/images/threats.png index 186c106..17163f5 100644 Binary files a/docs/images/threats.png and b/docs/images/threats.png differ diff --git a/docs/images/topn.png b/docs/images/topn.png index 137ca11..a563004 100644 Binary files a/docs/images/topn.png and b/docs/images/topn.png differ diff --git a/docs/images/tui-overview.png b/docs/images/tui-overview.png index 66fc724..9c352e6 100644 Binary files a/docs/images/tui-overview.png and b/docs/images/tui-overview.png differ diff --git a/docs/images/tui-topn.png b/docs/images/tui-topn.png index 91983ae..5e98010 100644 Binary files a/docs/images/tui-topn.png and b/docs/images/tui-topn.png differ diff --git a/internal/api/geo.go b/internal/api/geo.go new file mode 100644 index 0000000..4c278d4 --- /dev/null +++ b/internal/api/geo.go @@ -0,0 +1,103 @@ +package api + +import ( + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/githubflyideas/traffic66/internal/enrich" + "github.com/githubflyideas/traffic66/internal/geo" +) + +// maxGeoUpload bounds an uploaded database (GeoLite2-City is about 70 MB). +const maxGeoUpload = 512 << 20 + +func (s *Server) getGeo(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, map[string]any{"sources": s.ASN.Sources()}) +} + +// putGeo installs an uploaded country/ASN database: a MaxMind DB file +// (.mmdb, country or ASN) or a tab-separated IP-to-ASN table (plain or +// gzip). It is checked, saved in the data directory, and used for new flows +// at once; flows already stored keep the country and network they got. +func (s *Server) putGeo(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxGeoUpload)) + if err != nil { + fail(w, fmt.Errorf("upload: %w", err)) + return + } + if len(b) == 0 { + fail(w, errors.New("empty file")) + return + } + var name string + switch { + case geo.IsMMDB(b): + rd, err := geo.FromBytes(b) + if err != nil { + fail(w, err) + return + } + name = rd.Kind() + ".mmdb" + if err := writeFileAtomic(filepath.Join(s.DataDir, name), b); err != nil { + fail(w, err) + return + } + s.ASN.SetMMDB(rd, name) + default: + plain := b + if len(b) > 2 && b[0] == 0x1f && b[1] == 0x8b { + zr, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + fail(w, err) + return + } + if plain, err = io.ReadAll(io.LimitReader(zr, 4*maxGeoUpload)); err != nil { + fail(w, fmt.Errorf("gzip: %w", err)) + return + } + } + tbl, err := enrich.LoadTable(bytes.NewReader(plain)) + if err != nil { + fail(w, fmt.Errorf("not a MaxMind DB (.mmdb) and not an IP-to-ASN table: %w", err)) + return + } + gz := b + if &plain[0] == &b[0] { // uploaded uncompressed: store compressed + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + zw.Write(plain) + zw.Close() + gz = buf.Bytes() + } + name = "asn.tsv.gz" + if err := writeFileAtomic(filepath.Join(s.DataDir, name), gz); err != nil { + fail(w, err) + return + } + // older tables would be preferred at the next start; keep them aside + for _, old := range []string{"asn.tsv", "ip2asn-combined.tsv.gz"} { + p := filepath.Join(s.DataDir, old) + if _, err := os.Stat(p); err == nil { + os.Rename(p, p+".replaced") + } + } + s.ASN.TakeTable(tbl, name) + } + log.Printf("geo: installed %s (%d bytes) from upload", name, len(b)) + writeJSON(w, http.StatusOK, map[string]any{"installed": name, "sources": s.ASN.Sources()}) +} + +func writeFileAtomic(path string, b []byte) error { + tmp := path + ".tmp" + if err := os.WriteFile(tmp, b, 0o644); err != nil { + return err + } + return os.Rename(tmp, path) +} diff --git a/internal/api/geo_test.go b/internal/api/geo_test.go new file mode 100644 index 0000000..e95cce0 --- /dev/null +++ b/internal/api/geo_test.go @@ -0,0 +1,63 @@ +package api + +import ( + "bytes" + "encoding/json" + "net/http/httptest" + "net/netip" + "os" + "path/filepath" + "testing" + + "github.com/githubflyideas/traffic66/internal/enrich" +) + +func TestGeoUpload(t *testing.T) { + dir := t.TempDir() + s := &Server{ASN: enrich.NewASNDB(), DataDir: dir} + up := func(b []byte) (int, map[string]any) { + w := httptest.NewRecorder() + s.putGeo(w, httptest.NewRequest("POST", "/api/geo", bytes.NewReader(b))) + var out map[string]any + json.Unmarshal(w.Body.Bytes(), &out) + return w.Code, out + } + for _, f := range []string{"country.mmdb", "asn.mmdb"} { + b, err := os.ReadFile(filepath.Join("..", "geo", "testdata", f)) + if err != nil { + t.Fatal(err) + } + if code, out := up(b); code != 200 || out["installed"] != f { + t.Fatalf("%s: %d %v", f, code, out) + } + } + tsv := "198.51.100.0\t198.51.100.255\t64501\tNL\tExample Table Net\n" + if code, out := up([]byte(tsv)); code != 200 || out["installed"] != "asn.tsv.gz" { + t.Fatalf("tsv: %d %v", code, out) + } + if code, _ := up([]byte("hello world\n")); code != 400 { + t.Fatalf("garbage accepted: %d", code) + } + for _, tc := range []struct { + ip string + asn uint32 + cc, org string + }{ + {"8.8.8.8", 15169, "US", "Google LLC"}, // country and ASN from the two .mmdb files + {"198.51.100.7", 64501, "NL", "Example Table Net"}, // only in the table + {"1.0.0.1", 0, "AU", ""}, // only a country + } { + asn, cc, org := s.ASN.Lookup(netip.MustParseAddr(tc.ip)) + if asn != tc.asn || cc != tc.cc || org != tc.org { + t.Errorf("%s: %d %s %s", tc.ip, asn, cc, org) + } + } + for _, f := range []string{"country.mmdb", "asn.mmdb", "asn.tsv.gz"} { + if _, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("%s not saved: %v", f, err) + } + } + if n := len(s.ASN.Sources()); n != 3 { + t.Errorf("%d sources", n) + } +} diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 0800e16..e811897 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -86,18 +86,6 @@ func limitParam(r *http.Request, def int) int { // ---------------------------------------------------------------- overview -type mover struct { - IP string `json:"ip"` - Delta float64 `json:"delta_bps"` - Now float64 `json:"now_bps"` - Before float64 `json:"before_bps"` - Peer string `json:"peer"` - Port string `json:"port"` - CC string `json:"cc"` - Threat string `json:"threat,omitempty"` - Role string `json:"role"` // "client" or "server" for the peer relation -} - type slicePart struct { Key string `json:"key"` Wire uint64 `json:"wire"` @@ -165,9 +153,6 @@ func (s *Server) overview(w http.ResponseWriter, r *http.Request) { } } - // movers: internal hosts whose traffic grew the most - out["movers"] = s.movers(q, qb) - donut := func(dim string, keep int) []slicePart { rows, err := s.Store.TopN(q, dim, 20) if err != nil { @@ -227,85 +212,6 @@ func (s *Server) hostWire(q store.Query, ips []string) map[string]uint64 { return out } -func (s *Server) movers(q, qb store.Query) []mover { - sum := func(q store.Query) map[string]uint64 { - m := map[string]uint64{} - for _, dim := range []string{"client", "server"} { - rows, err := s.Store.TopN(q, dim, 300) - if err != nil { - continue - } - for _, t := range rows { - if t.Int { - m[t.Key] += t.Wire - } - } - } - return m - } - cur := sum(q) - // candidates: the busiest internal hosts now; their baseline is exact - type kv struct { - ip string - w uint64 - } - var cand []kv - for ip, w := range cur { - cand = append(cand, kv{ip, w}) - } - sort.Slice(cand, func(i, j int) bool { return cand[i].w > cand[j].w }) - if len(cand) > 40 { - cand = cand[:40] - } - ips := make([]string, len(cand)) - for i, c := range cand { - ips[i] = c.ip - } - prev := s.hostWire(qb, ips) - secs := q.To.Sub(q.From).Seconds() - var ms []mover - for _, c := range cand { - d := (float64(c.w) - float64(prev[c.ip])) * 8 / secs - if d <= 0 { - continue - } - ms = append(ms, mover{IP: c.ip, Delta: d, Now: float64(c.w) * 8 / secs, Before: float64(prev[c.ip]) * 8 / secs}) - } - sort.Slice(ms, func(i, j int) bool { return ms[i].Delta > ms[j].Delta }) - if tot, err := s.Store.Totals(q); err == nil && secs > 0 { - floor := float64(tot.Wire) * 8 / secs * 0.01 - keep := ms[:0] - for _, m := range ms { - if m.Delta >= floor { - keep = append(keep, m) - } - } - ms = keep - } - if len(ms) > 5 { - ms = ms[:5] - } - for i := range ms { - qq := q - qq.Filters = append(append([]store.Filter{}, q.Filters...), store.Filter{Field: "ip", Value: ms[i].IP}) - rows, err := s.Store.TopN(qq, "conv", 1) - if err != nil || len(rows) == 0 { - continue - } - t := rows[0] - if t.Key == ms[i].IP { - ms[i].Peer, ms[i].Role = t.Key2, "client" - } else { - ms[i].Peer, ms[i].Role = t.Key, "server" - } - ms[i].Port, ms[i].CC = t.Key3, t.Extra - if a, err := netip.ParseAddr(ms[i].Peer); err == nil { - ms[i].Threat = s.Thr.Match(a) - } - } - return ms -} - // ---------------------------------------------------------------- lists func (s *Server) topn(w http.ResponseWriter, r *http.Request) { @@ -601,13 +507,15 @@ func (s *Server) status(w http.ResponseWriter, r *http.Request) { s.rates.mu.Lock() rec, rows := s.rates.recPerSec, s.rates.rowRate s.rates.mu.Unlock() + // Disk: what is used and free, and, once there is a day of data, how much + // the kept days of detail will need at the current rate. Retention + // deletes older data, so the disk does not fill as long as that fits. free := diskFree(s.DataDir) - days := -1.0 + need := int64(-1) if !u.Oldest.IsZero() { age := time.Since(u.Oldest).Hours() / 24 - if age >= 0.04 && u.DiskBytes > 0 { - perDay := float64(u.DiskBytes) / age - days = float64(free) / perDay + if age >= 1 && u.DiskBytes > 0 { + need = int64(float64(u.DiskBytes) / age * float64(s.Store.RetentionDays())) } } warn := 0 @@ -631,7 +539,7 @@ func (s *Server) status(w http.ResponseWriter, r *http.Request) { "version": s.Version, "demo": s.Demo, "uptime": int64(time.Since(s.Started).Seconds()), "records_per_sec": rec, "rows_per_sec": rows, "dropped": s.Pipe.Dropped.Load(), "dup_rows": s.Pipe.DupRows.Load(), "dedup_full": s.Pipe.DedupFull.Load(), "write_errors": s.Pipe.WriteErrs.Load(), "last_error": s.Pipe.LastError(), - "disk_bytes": u.DiskBytes, "disk_free": free, "days_left": days, "oldest": u.Oldest.UnixMilli(), + "disk_bytes": u.DiskBytes, "disk_free": free, "disk_need": need, "retention_days": s.Store.RetentionDays(), "oldest": u.Oldest.UnixMilli(), "hot_rows": u.HotRows, "segments": u.Segments, "segment_rows": u.SegmentRows, "dns_upstream": s.DNS.Upstream(), "dns_queries": s.DNS.Queries, "asn_ranges": s.ASN.Size(), "threat_lists": s.Thr.Lists(), "source_warnings": warn, "hosts": names, "skewed": s.Col.Skewed.Load(), "go": runtime.Version(), diff --git a/internal/api/server.go b/internal/api/server.go index c51faf1..1d72993 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -90,6 +90,8 @@ func (s *Server) Handler() http.Handler { api("POST /api/resolve", s.resolve) api("GET /api/inventory", s.getInventory) api("POST /api/inventory", s.putInventory) + api("GET /api/geo", s.getGeo) + api("POST /api/geo", s.putGeo) static := http.FileServer(http.FS(s.Static)) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") diff --git a/internal/enrich/enrich.go b/internal/enrich/enrich.go index ea01c94..de0ed69 100644 --- a/internal/enrich/enrich.go +++ b/internal/enrich/enrich.go @@ -6,14 +6,19 @@ package enrich import ( "bufio" "compress/gzip" + "errors" "fmt" "io" "net/netip" "os" + "path/filepath" "sort" "strconv" "strings" "sync" + "time" + + "github.com/githubflyideas/traffic66/internal/geo" ) // ---------------------------------------------------------------- locality @@ -335,13 +340,60 @@ type asRange struct { org string } -// ASNDB maps address ranges to ASN, country and organisation, loaded from a -// tab-separated file: range_start range_end as_number country description. +// ASNDB maps addresses to ASN, country and organisation. Sources: a +// tab-separated table (range_start range_end as_number country description, +// as published by iptoasn.com) and MaxMind DB files (.mmdb, as GeoLite2 and +// DB-IP Lite): a country database and an ASN database. When several are +// loaded, the .mmdb files win for what they hold. type ASNDB struct { - mu sync.RWMutex - v4, v6 []asRange + mu sync.RWMutex + v4, v6 []asRange + tsvFile string + cty, as *geo.Reader + ctyFile string + asFile string +} + +// GeoSource describes one loaded database. +type GeoSource struct { + Kind string `json:"kind"` // "table", "country" or "asn" + File string `json:"file"` // file name in the data directory + Type string `json:"type"` // database type from the file + Built time.Time `json:"built"` // build date of an .mmdb + Entries int `json:"entries"` // ranges in a table +} + +// SetMMDB installs an .mmdb reader as the country or ASN database. +func (db *ASNDB) SetMMDB(r *geo.Reader, file string) { + db.mu.Lock() + defer db.mu.Unlock() + if r.Kind() == "asn" { + db.as, db.asFile = r, file + } else { + db.cty, db.ctyFile = r, file + } +} + +// Sources lists the loaded databases. +func (db *ASNDB) Sources() []GeoSource { + db.mu.RLock() + defer db.mu.RUnlock() + var out []GeoSource + if n := len(db.v4) + len(db.v6); n > 0 { + out = append(out, GeoSource{Kind: "table", File: db.tsvFile, Type: "IP to ASN table", Entries: n}) + } + if db.cty != nil { + out = append(out, GeoSource{Kind: "country", File: db.ctyFile, Type: db.cty.Type, Built: db.cty.Built}) + } + if db.as != nil { + out = append(out, GeoSource{Kind: "asn", File: db.asFile, Type: db.as.Type, Built: db.as.Built}) + } + return out } +// Loaded reports whether any database is loaded. +func (db *ASNDB) Loaded() bool { return len(db.Sources()) > 0 } + func NewASNDB() *ASNDB { return &ASNDB{} } func (db *ASNDB) Size() int { @@ -350,6 +402,28 @@ func (db *ASNDB) Size() int { return len(db.v4) + len(db.v6) } +// LoadTable parses a TSV table without installing it, for validation. +func LoadTable(r io.Reader) (*ASNDB, error) { + t := NewASNDB() + if err := t.Load(r); err != nil { + return nil, err + } + if t.Size() == 0 { + return nil, errors.New("no address ranges found; expected tab-separated lines: first address, last address, AS number, country, name") + } + return t, nil +} + +// TakeTable installs the ranges of another ASNDB as this one's table. +func (db *ASNDB) TakeTable(o *ASNDB, file string) { + o.mu.RLock() + v4, v6 := o.v4, o.v6 + o.mu.RUnlock() + db.mu.Lock() + db.v4, db.v6, db.tsvFile = v4, v6, file + db.mu.Unlock() +} + // LoadFile reads a .tsv or .tsv.gz file and replaces the table. func (db *ASNDB) LoadFile(path string) error { f, err := os.Open(path) @@ -357,6 +431,7 @@ func (db *ASNDB) LoadFile(path string) error { return err } defer f.Close() + defer func() { db.mu.Lock(); db.tsvFile = filepath.Base(path); db.mu.Unlock() }() var r io.Reader = f if strings.HasSuffix(path, ".gz") { gz, err := gzip.NewReader(f) @@ -409,7 +484,7 @@ func (db *ASNDB) Load(r io.Reader) error { } // Lookup returns the ASN, country code and organisation for a. -func (db *ASNDB) Lookup(a netip.Addr) (uint32, string, string) { +func (db *ASNDB) Lookup(a netip.Addr) (asn uint32, cc, org string) { a = a.Unmap() db.mu.RLock() defer db.mu.RUnlock() @@ -417,11 +492,20 @@ func (db *ASNDB) Lookup(a netip.Addr) (uint32, string, string) { if a.Is6() { t = db.v6 } - i := sort.Search(len(t), func(i int) bool { return a.Less(t[i].lo) }) - 1 - if i < 0 || t[i].hi.Less(a) { - return 0, "", "" + if i := sort.Search(len(t), func(i int) bool { return a.Less(t[i].lo) }) - 1; i >= 0 && !t[i].hi.Less(a) { + asn, cc, org = t[i].asn, t[i].cc, t[i].org + } + if db.cty != nil { + if in, ok := db.cty.Lookup(a); ok && in.Country != "" { + cc = in.Country + } + } + if db.as != nil { + if in, ok := db.as.Lookup(a); ok && in.ASN != 0 { + asn, org = in.ASN, in.Org + } } - return t[i].asn, t[i].cc, t[i].org + return asn, cc, org } // ---------------------------------------------------------------- threats diff --git a/internal/geo/mmdb.go b/internal/geo/mmdb.go new file mode 100644 index 0000000..882bfe1 --- /dev/null +++ b/internal/geo/mmdb.go @@ -0,0 +1,354 @@ +// Package geo reads MaxMind DB files (.mmdb), the format of MaxMind GeoLite2 +// and DB-IP Lite databases, following the published format specification. +// Only what traffic66 needs is decoded: country code, AS number and AS +// organisation. +package geo + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "net/netip" + "os" + "strings" + "sync" + "time" +) + +var metaMarker = []byte("\xab\xcd\xefMaxMind.com") + +// Reader is an open .mmdb database held in memory. +type Reader struct { + buf []byte + data []byte // data section + nodeCount uint32 + recordSize uint32 + ipVersion uint16 + v4Start uint32 // node reached after the 96 zero bits of an IPv4 address + + Type string // database_type, e.g. "GeoLite2-Country" + Built time.Time // build_epoch + + mu sync.Mutex + cache map[uint32]Info +} + +// Info is what traffic66 uses from a record. +type Info struct { + Country string // ISO code + ASN uint32 + Org string +} + +// Open reads the file at path. +func Open(path string) (*Reader, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, err + } + return FromBytes(b) +} + +// IsMMDB reports whether b looks like a MaxMind DB. +func IsMMDB(b []byte) bool { return bytes.LastIndex(b, metaMarker) >= 0 } + +// FromBytes parses a database held in b. +func FromBytes(b []byte) (*Reader, error) { + i := bytes.LastIndex(b, metaMarker) + if i < 0 { + return nil, errors.New("not a MaxMind DB file (.mmdb)") + } + md, _, err := decode(b[i+len(metaMarker):], 0, 0) + if err != nil { + return nil, fmt.Errorf("metadata: %w", err) + } + m, ok := md.(map[string]any) + if !ok { + return nil, errors.New("metadata is not a map") + } + r := &Reader{buf: b, cache: map[uint32]Info{}} + r.nodeCount = uint32(asUint(m["node_count"])) + r.recordSize = uint32(asUint(m["record_size"])) + r.ipVersion = uint16(asUint(m["ip_version"])) + r.Type, _ = m["database_type"].(string) + if e := asUint(m["build_epoch"]); e > 0 { + r.Built = time.Unix(int64(e), 0).UTC() + } + switch r.recordSize { + case 24, 28, 32: + default: + return nil, fmt.Errorf("unsupported record size %d", r.recordSize) + } + if r.ipVersion != 4 && r.ipVersion != 6 { + return nil, fmt.Errorf("unsupported IP version %d", r.ipVersion) + } + treeSize := uint64(r.nodeCount) * uint64(r.recordSize) / 4 + if treeSize+16 > uint64(i) { + return nil, errors.New("search tree larger than the file") + } + r.data = b[treeSize+16 : i] + if r.ipVersion == 6 { + node := uint32(0) + for k := 0; k < 96 && node < r.nodeCount; k++ { + node = r.record(node, 0) + } + r.v4Start = node + } + return r, nil +} + +// record returns the left (bit 0) or right (bit 1) record of node. +func (r *Reader) record(node uint32, bit int) uint32 { + t := r.buf + switch r.recordSize { + case 24: + o := node * 6 + if bit == 1 { + o += 3 + } + return uint32(t[o])<<16 | uint32(t[o+1])<<8 | uint32(t[o+2]) + case 28: + o := node * 7 + if bit == 0 { + return uint32(t[o+3]&0xf0)<<20 | uint32(t[o])<<16 | uint32(t[o+1])<<8 | uint32(t[o+2]) + } + return uint32(t[o+3]&0x0f)<<24 | uint32(t[o+4])<<16 | uint32(t[o+5])<<8 | uint32(t[o+6]) + default: + o := node * 8 + if bit == 1 { + o += 4 + } + return binary.BigEndian.Uint32(t[o:]) + } +} + +// Lookup returns the record for a, or ok=false when the database has none. +func (r *Reader) Lookup(a netip.Addr) (Info, bool) { + a = a.Unmap() + var bits []byte + node := uint32(0) + if a.Is4() { + b := a.As4() + bits = b[:] + if r.ipVersion == 6 { + node = r.v4Start + } + } else { + if r.ipVersion == 4 { + return Info{}, false + } + b := a.As16() + bits = b[:] + } + for i := 0; i < len(bits)*8 && node < r.nodeCount; i++ { + node = r.record(node, int(bits[i/8]>>(7-i%8)&1)) + } + if node <= r.nodeCount { + return Info{}, false // node_count means "no data" + } + off := node - r.nodeCount - 16 + r.mu.Lock() + if in, ok := r.cache[off]; ok { + r.mu.Unlock() + return in, true + } + r.mu.Unlock() + v, _, err := decode(r.data, int(off), 0) + if err != nil { + return Info{}, false + } + in := infoFrom(v) + r.mu.Lock() + if len(r.cache) > 1<<20 { + r.cache = map[uint32]Info{} + } + r.cache[off] = in + r.mu.Unlock() + return in, true +} + +func infoFrom(v any) Info { + m, _ := v.(map[string]any) + var in Info + for _, k := range []string{"country", "registered_country", "represented_country"} { + if c, ok := m[k].(map[string]any); ok { + if s, ok := c["iso_code"].(string); ok && s != "" { + in.Country = s + break + } + } + } + if in.Country == "" { // ip-location-db and similar flat layouts + in.Country, _ = m["country_code"].(string) + } + in.ASN = uint32(asUint(m["autonomous_system_number"])) + in.Org, _ = m["autonomous_system_organization"].(string) + return in +} + +func asUint(v any) uint64 { + switch x := v.(type) { + case uint64: + return x + case int64: + if x > 0 { + return uint64(x) + } + case float64: + if x > 0 { + return uint64(x) + } + } + return 0 +} + +// decode reads the value at off in d and returns it and the offset after it. +// depth guards against malformed files that loop through pointers. +func decode(d []byte, off, depth int) (any, int, error) { + if depth > 32 { + return nil, 0, errors.New("nesting too deep") + } + need := func(n int) error { + if off+n > len(d) || off+n < off { + return errors.New("truncated data") + } + return nil + } + if err := need(1); err != nil { + return nil, 0, err + } + ctrl := d[off] + off++ + typ := int(ctrl >> 5) + if typ == 1 { // pointer + ss, vvv := int(ctrl>>3)&3, uint32(ctrl&7) + if err := need(ss + 1); err != nil { + return nil, 0, err + } + var p uint32 + switch ss { + case 0: + p = vvv<<8 | uint32(d[off]) + case 1: + p = (vvv<<16 | uint32(d[off])<<8 | uint32(d[off+1])) + 2048 + case 2: + p = (vvv<<24 | uint32(d[off])<<16 | uint32(d[off+1])<<8 | uint32(d[off+2])) + 526336 + default: + p = binary.BigEndian.Uint32(d[off:]) + } + v, _, err := decode(d, int(p), depth+1) + return v, off + ss + 1, err + } + if typ == 0 { // extended type + if err := need(1); err != nil { + return nil, 0, err + } + typ = 7 + int(d[off]) + off++ + } + size := int(ctrl & 0x1f) + if size >= 29 { + n := size - 28 + if err := need(n); err != nil { + return nil, 0, err + } + switch n { + case 1: + size = 29 + int(d[off]) + case 2: + size = 285 + int(d[off])<<8 + int(d[off+1]) + default: + size = 65821 + int(d[off])<<16 + int(d[off+1])<<8 + int(d[off+2]) + } + off += n + } + uintN := func() (uint64, error) { + if err := need(size); err != nil || size > 8 { + return 0, errors.New("bad integer") + } + var v uint64 + for _, c := range d[off : off+size] { + v = v<<8 | uint64(c) + } + return v, nil + } + switch typ { + case 2: // UTF-8 string + if err := need(size); err != nil { + return nil, 0, err + } + return string(d[off : off+size]), off + size, nil + case 3: // double + if err := need(8); err != nil { + return nil, 0, err + } + return math.Float64frombits(binary.BigEndian.Uint64(d[off:])), off + 8, nil + case 4: // bytes + if err := need(size); err != nil { + return nil, 0, err + } + return d[off : off+size], off + size, nil + case 5, 6, 9: // uint16, uint32, uint64 + v, err := uintN() + return v, off + size, err + case 8: // int32 + v, err := uintN() + return int64(int32(uint32(v))), off + size, err + case 10: // uint128: not needed, skipped + if err := need(size); err != nil { + return nil, 0, err + } + return nil, off + size, nil + case 7: // map + m := make(map[string]any, size) + for i := 0; i < size; i++ { + k, next, err := decode(d, off, depth+1) + if err != nil { + return nil, 0, err + } + ks, ok := k.(string) + if !ok { + return nil, 0, errors.New("map key is not a string") + } + v, next2, err := decode(d, next, depth+1) + if err != nil { + return nil, 0, err + } + m[ks] = v + off = next2 + } + return m, off, nil + case 11: // array + a := make([]any, 0, min(size, 1024)) + for i := 0; i < size; i++ { + v, next, err := decode(d, off, depth+1) + if err != nil { + return nil, 0, err + } + a = append(a, v) + off = next + } + return a, off, nil + case 14: // boolean: the value is the size + return size != 0, off, nil + case 15: // float + if err := need(4); err != nil { + return nil, 0, err + } + return float64(math.Float32frombits(binary.BigEndian.Uint32(d[off:]))), off + 4, nil + } + return nil, 0, fmt.Errorf("unsupported data type %d", typ) +} + +// Kind tells whether a database holds countries, AS numbers or both. +func (r *Reader) Kind() string { + t := strings.ToLower(r.Type) + switch { + case strings.Contains(t, "asn") || strings.Contains(t, "-as-") || strings.Contains(t, "isp"): + return "asn" + default: + return "country" + } +} diff --git a/internal/geo/mmdb_test.go b/internal/geo/mmdb_test.go new file mode 100644 index 0000000..100f3d7 --- /dev/null +++ b/internal/geo/mmdb_test.go @@ -0,0 +1,81 @@ +package geo + +import ( + "net/netip" + "testing" +) + +// The test databases were made with the mmdb-writer Python package and +// checked against MaxMind's maxminddb reader. +func TestLookup(t *testing.T) { + c, err := Open("testdata/country.mmdb") + if err != nil { + t.Fatal(err) + } + a, err := Open("testdata/asn.mmdb") + if err != nil { + t.Fatal(err) + } + v4, err := Open("testdata/country4.mmdb") + if err != nil { + t.Fatal(err) + } + if c.Kind() != "country" || a.Kind() != "asn" || c.Type != "GeoLite2-Country" { + t.Fatalf("kinds %q %q type %q", c.Kind(), a.Kind(), c.Type) + } + for _, tc := range []struct { + r *Reader + ip string + ok bool + want Info + }{ + {c, "1.0.0.5", true, Info{Country: "AU"}}, + {c, "::ffff:1.0.0.5", true, Info{Country: "AU"}}, + {c, "8.8.8.8", true, Info{Country: "US"}}, + {c, "203.0.113.7", true, Info{Country: "RU"}}, // registered_country only + {c, "2001:db8::1", true, Info{Country: "JP"}}, + {c, "9.9.9.9", false, Info{}}, + {a, "8.8.8.8", true, Info{ASN: 15169, Org: "Google LLC"}}, + {a, "2001:db8:1::5", true, Info{ASN: 64500, Org: "Example Net"}}, + {a, "2001:db8:2::5", false, Info{}}, + {v4, "192.0.2.9", true, Info{Country: "DE"}}, + {v4, "2001:db8::1", false, Info{}}, + } { + got, ok := tc.r.Lookup(netip.MustParseAddr(tc.ip)) + if ok != tc.ok || got != tc.want { + t.Errorf("%s in %s: %+v %v, want %+v %v", tc.ip, tc.r.Type, got, ok, tc.want, tc.ok) + } + } +} + +func TestRecordSizes(t *testing.T) { + // one node holding left=0x0ABCDEF / right=0x0123456 in each layout + for _, tc := range []struct { + size uint32 + node []byte + }{ + {24, []byte{0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56}}, + {28, []byte{0xAB, 0xCD, 0xEF, 0x00, 0x12, 0x34, 0x56}}, + {32, []byte{0x00, 0xAB, 0xCD, 0xEF, 0x00, 0x12, 0x34, 0x56}}, + } { + r := &Reader{buf: tc.node, recordSize: tc.size} + if l, rr := r.record(0, 0), r.record(0, 1); l != 0xABCDEF || rr != 0x123456 { + t.Errorf("size %d: %x %x", tc.size, l, rr) + } + } + r := &Reader{buf: []byte{0xAB, 0xCD, 0xEF, 0x5A, 0x12, 0x34, 0x56}, recordSize: 28} + if l, rr := r.record(0, 0), r.record(0, 1); l != 0x5ABCDEF || rr != 0xA123456 { + t.Errorf("28-bit high nibbles: %x %x", l, rr) + } +} + +func TestRejectsGarbage(t *testing.T) { + if _, err := FromBytes([]byte("hello")); err == nil { + t.Fatal("garbage accepted") + } + b := append([]byte{0, 0, 0}, metaMarker...) + b = append(b, 0xe0) // empty map: no node_count, record size 0 + if _, err := FromBytes(b); err == nil { + t.Fatal("metadata without record size accepted") + } +} diff --git a/internal/geo/testdata/asn.mmdb b/internal/geo/testdata/asn.mmdb new file mode 100644 index 0000000..860d523 Binary files /dev/null and b/internal/geo/testdata/asn.mmdb differ diff --git a/internal/geo/testdata/country.mmdb b/internal/geo/testdata/country.mmdb new file mode 100644 index 0000000..e2debdc Binary files /dev/null and b/internal/geo/testdata/country.mmdb differ diff --git a/internal/geo/testdata/country4.mmdb b/internal/geo/testdata/country4.mmdb new file mode 100644 index 0000000..8bf02de Binary files /dev/null and b/internal/geo/testdata/country4.mmdb differ diff --git a/internal/store/store.go b/internal/store/store.go index 56de0d9..af26c83 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -438,3 +438,6 @@ func (s *Store) Close() error { // TotalMemory is the machine's physical memory in bytes. func TotalMemory() uint64 { return totalMemory() } + +// RetentionDays is how many days of flow detail are kept. +func (s *Store) RetentionDays() int { return s.opt.RawDays } diff --git a/internal/tui/pages.go b/internal/tui/pages.go index 5fad1f7..d0a1000 100644 --- a/internal/tui/pages.go +++ b/internal/tui/pages.go @@ -160,15 +160,7 @@ func (a *app) load(pg, rng, di int, filters []filter, ifc string) page { Series struct { Values [][]float64 } - Baseline []float64 - Movers []struct { - IP string `json:"ip"` - Delta float64 `json:"delta_bps"` - Peer string - Port string - CC string - Threat string - } + Baseline []float64 TopClients []topRow `json:"top_clients"` } if err := a.get("overview", q(nil), &d); err != nil { @@ -196,18 +188,6 @@ func (a *app) load(pg, rng, di int, filters []filter, ifc string) page { } p.top = append(p.top, chart(total, d.Baseline, w-12, 7)...) p.top = append(p.top, txt("")) - mv := section{title: a.t("ov.movers"), cols: []column{{head: a.t("field.ip"), width: 34}, {head: "", width: 12, right: true}, {head: ""}}} - for _, m := range d.Movers { - reason := "" - if m.Peer != "" { - reason = a.t("ov.mover_reason", "peer", a.hostLabel(m.Peer), "port", m.Port, "cc", a.cc(m.CC)) - } - r := row{cells: []line{a.host(m.IP), styled(red, "+"+fmtBps(m.Delta)), txt(reason)}, target: ipTarget(a, m.IP)} - if m.Threat != "" { - r.cells[2] = append(r.cells[2], seg{red, " [" + m.Threat + "]"}) - } - mv.rows = append(mv.rows, r) - } tc := section{title: a.t("ov.top_clients"), cols: []column{{head: a.t("col.client"), width: 34}, {head: a.t("col.traffic"), width: 10, right: true}, {head: ""}, {head: a.t("col.vs_week"), width: 10, right: true}}} maxw := 1.0 for _, r := range d.TopClients { @@ -221,17 +201,9 @@ func (a *app) load(pg, rng, di int, filters []filter, ifc string) page { } tc.rows = append(tc.rows, row{cells: []line{a.host(r.Key), txt(fmtBytes(r.Wire)), styled(blue, barCells(r.Wire, maxw, 30)), txt(ch)}, target: ipTarget(a, r.Key)}) } - p.sections = []section{mv, tc} + p.sections = []section{tc} - case "topn", "conv": - conv := pageKeys[pg] == "conv" - if conv { // Conversations page: the Top-N of conversations, no tabs - for i, k := range dims { - if k == "conv" { - di = i - } - } - } + case "topn": var d struct{ Rows []topRow } if err := a.get("topn", q(map[string]string{"dim": dims[di], "limit": "66"}), &d); err != nil { return fail(err) @@ -244,11 +216,7 @@ func (a *app) load(pg, rng, di int, filters []filter, ifc string) page { tabs = append(tabs, seg{"", " " + a.t("dim."+k) + " "}) } } - if conv { - p.top = append(p.top, txt(a.t("conv.sub")), txt("")) - } else { - p.top = append(p.top, tabs, txt("")) - } + p.top = append(p.top, tabs, txt("")) sec := section{title: a.t("topn.title", "n", "66"), cols: []column{{head: "#", width: 3, right: true}, {head: a.t("dim." + dims[di])}, {head: a.t("col.traffic"), width: 10, right: true}, {head: "%", width: 6, right: true}, {head: "", width: 24}, {head: a.t("col.flows"), width: 9, right: true}}} tot, maxw := 0.0, 1.0 for _, r := range d.Rows { diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 38ba54e..9a64526 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -153,9 +153,9 @@ type page struct { err string } -var pageKeys = []string{"overview", "topn", "conv", "sankey", "geo", "threats", "records", "ifaces", "sources"} +var pageKeys = []string{"overview", "topn", "sankey", "geo", "threats", "records", "ifaces", "sources"} var ranges = []string{"15m", "1h", "6h", "24h", "7d", "30d"} -var dims = []string{"client", "server", "conv", "app", "port", "country", "asn", "segment", "exporter", "encap", "vlan"} +var dims = []string{"conv", "client", "server", "app", "port", "country", "asn", "segment", "exporter", "encap", "vlan"} type app struct { opt Options diff --git a/internal/web/static/app.css b/internal/web/static/app.css index a307fe0..c52a6c4 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -33,7 +33,7 @@ nav.dash button[aria-current="page"]{background:var(--accent-soft);color:var(--a .self .live.ok{color:var(--good)}.self .live.idle{color:var(--ink-3)} .foot{display:flex;gap:8px;align-items:center;padding:0 18px 14px} .foot select{flex:1;border:1px solid var(--line-2);border-radius:6px;padding:3px 6px;background:var(--surface);font-size:13px} -.linkbtn{border:0;background:none;color:var(--ink-3);cursor:pointer;font-size:12.5px;padding:2px} +.linkbtn{border:0;background:none;color:var(--ink-3);cursor:pointer;font-size:12.5px;padding:2px;white-space:nowrap} .linkbtn:hover{color:var(--accent-ink)} .main{min-width:0} @@ -166,3 +166,27 @@ textarea.inv{width:100%;min-height:180px;border:1px solid var(--line-2);border-r .login label{display:grid;gap:4px;font-size:13px;color:var(--ink-2)} .login input{border:1px solid var(--line-2);border-radius:7px;padding:8px 10px} .login .err{color:var(--crit);margin:0;font-size:13px} + +/* sortable / regrouping column headings */ +th.sk{cursor:pointer;user-select:none} +th.sk:hover{color:var(--accent-ink)} +th.sk[aria-sort]{color:var(--ink)} +th.sk .ar{font-size:10px;margin-inline-start:3px;opacity:.7} +th.gk{cursor:pointer;color:var(--accent-ink);text-decoration:underline dotted;text-underline-offset:3px} +.dimsel{border:1px solid var(--line-2);border-radius:6px;padding:4px 8px;background:var(--surface);font-size:13px} +.ph .tools{display:flex;gap:10px;align-items:center;margin-inline-start:auto;font-size:12.5px;color:var(--ink-3)} +/* sankey: labels and flows are clickable too */ +.sankey text{cursor:pointer}.sankey text:hover{fill:var(--accent-ink)} +.sankey rect.hit{fill:transparent} +/* naming form in the popup */ +.pop form{display:flex;gap:6px;padding:6px 6px 2px} +.pop form input{flex:1;min-width:0;border:1px solid var(--line-2);border-radius:6px;padding:5px 8px;font:inherit} +.pop form button{width:auto;background:var(--accent);color:#fff;justify-content:center} +.pop form button:hover{background:var(--accent-ink)} +/* detail page */ +.dhead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap} +.dhead h2{font-size:20px;margin:0} +.dhead .sub{color:var(--ink-3)} +.helpbox{background:var(--surface-2);border-radius:8px;padding:10px 12px;font-size:12.5px;color:var(--ink-2);margin:0 0 10px} +.helpbox pre{margin:6px 0 0;font-size:12px;white-space:pre-wrap} +.geo li{margin:3px 0} diff --git a/internal/web/static/app.js b/internal/web/static/app.js index f72bb72..51d9421 100644 --- a/internal/web/static/app.js +++ b/internal/web/static/app.js @@ -132,29 +132,32 @@ async function resolveNames() { } // ------------------------------------------------------------ state & API -const VIEWS = ['overview', 'topn', 'conv', 'sankey', 'geo', 'threats', 'records', 'ifaces', 'sources']; +const VIEWS = ['overview', 'topn', 'sankey', 'geo', 'threats', 'records', 'ifaces', 'sources', 'detail']; const RANGES = ['15m', '1h', '6h', '24h', '7d', '30d']; -const state = {v: 'overview', r: '24h', f: [], dim: 'client', ifc: null, ifdir: 'in'}; +const state = {v: 'overview', r: '24h', f: [], dim: 'conv', ifc: null, ifdir: 'in', sort: {k: 'wire', asc: false}, det: null}; function readHash() { const p = new URLSearchParams(location.hash.slice(1)); if (VIEWS.includes(p.get('v'))) state.v = p.get('v'); if (RANGES.includes(p.get('r'))) state.r = p.get('r'); if (p.get('dim')) state.dim = p.get('dim'); + const dd = p.get('d'); if (dd && dd.includes(':')) state.det = {f: dd.slice(0, dd.indexOf(':')), v: dd.slice(dd.indexOf(':') + 1)}; state.f = (p.get('f') || '').split(',').filter(Boolean).map(s => { const neg = s[0] === '!'; if (neg) s = s.slice(1); const i = s.indexOf(':'); return {f: decodeURIComponent(s.slice(0, i)), v: decodeURIComponent(s.slice(i + 1)), neg}; }).filter(x => x.f && x.v); } -function writeHash() { +function writeHash(push) { const p = new URLSearchParams({v: state.v, r: state.r}); if (state.v === 'topn') p.set('dim', state.dim); + if (state.v === 'detail' && state.det) p.set('d', state.det.f + ':' + state.det.v); if (state.f.length) p.set('f', state.f.map(x => (x.neg ? '!' : '') + encodeURIComponent(x.f) + ':' + encodeURIComponent(x.v)).join(',')); - history.replaceState(null, '', '#' + p.toString()); + const h = '#' + p.toString(); + if (push && h !== location.hash) history.pushState(null, '', h); else history.replaceState(null, '', h); } -async function api(path, extra = {}) { +async function api(path, extra = {}, filters = state.f) { const p = new URLSearchParams({range: state.r, ...extra}); - if (state.f.length) p.set('f', JSON.stringify(state.f.map(x => ({f: x.f, v: x.v, neg: x.neg})))); + if (filters.length) p.set('f', JSON.stringify(filters.map(x => ({f: x.f, v: x.v, neg: x.neg})))); const res = await fetch('/api/' + path + '?' + p); if (res.status === 401) { showLogin(); throw new Error('login'); } const j = await res.json().catch(() => ({})); @@ -265,6 +268,8 @@ function openPop(c, rect) { + ${DETAIL.has(c.k) ? `` : ''} + ${NAMEABLE.has(c.k) ? `` : ''} ${LOOKUP[c.k] ? `` : ''}`; pop.style.display = 'block'; const pr = pop.getBoundingClientRect(); @@ -272,22 +277,55 @@ function openPop(c, rect) { pop.style.top = (rect.bottom + pr.height + 8 > innerHeight ? rect.top - pr.height - 6 : rect.bottom + 6) + 'px'; pop.querySelector('button').focus(); } +// what a value's popup offers besides filtering +const DETAIL = new Set(['ip', 'exporter', 'port']); +const NAMEABLE = new Set(['ip', 'exporter']); +function showDetail(f, v) { state.det = {f: f === 'exporter' ? 'ip' : f, v}; go('detail'); } +// Name an address from anywhere: the inventory line for it is replaced +// (host for an address, device for a flow exporter) and saved at once. +function nameForm() { + const kind = ctx.k === 'exporter' ? 'device' : 'host', ip = ctx.v, cur = names.get(ip) || ''; + pop.innerHTML = `
${esc(t('pop.name'))}${esc(ip)}
+
`; + const inp = pop.querySelector('input'); inp.focus(); inp.select(); + pop.querySelector('form').onsubmit = async e => { + e.preventDefault(); + const name = inp.value.trim(); + try { + const inv = await api('inventory'); + const re = new RegExp('^\\s*(host|device)\\s+' + ip.replace(/[.:]/g, '\\$&') + '(\\s|$)', 'i'); + const lines = (inv.text || '').split('\n').filter(l => !re.test(l)); + while (lines.length && lines[lines.length - 1].trim() === '') lines.pop(); + if (name) lines.push(`${kind} ${ip} ${name}`); + const res = await fetch('/api/inventory', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({text: lines.join('\n') + '\n'})}); + if (!res.ok) { const j = await res.json().catch(() => ({})); toast(j.error || res.statusText); return; } + if (name) names.set(ip, name); else names.delete(ip); + pop.style.display = 'none'; + toast(t('pop.named')); + await loadStatus(); render(); + } catch (err) { toast(errMsg(err)); } + }; +} function act(a) { if (!ctx) return; + if (a === 'name') { nameForm(); return; } pop.style.display = 'none'; + if (a === 'detail') { showDetail(ctx.k, ctx.v); return; } if (a === 'lookup') { window.open(LOOKUP[ctx.k](ctx.v), '_blank', 'noopener'); return; } state.f = state.f.filter(x => !(x.f === ctx.k && x.v === ctx.v)); if (a === 'records') { state.f = state.f.filter(x => x.f !== ctx.k); state.f.push({f: ctx.k, v: ctx.v, neg: false}); go('records'); return; } state.f.push({f: ctx.k, v: ctx.v, neg: a === 'not'}); render(); } -pop.addEventListener('click', e => { const b = e.target.closest('button'); if (b) act(b.dataset.a); }); +pop.addEventListener('click', e => { e.stopPropagation(); const b = e.target.closest('button[data-a]'); if (b) act(b.dataset.a); }); document.addEventListener('keydown', e => { - if (pop.style.display !== 'block') return; + if (pop.style.display !== 'block' || e.target.closest?.('#pop form')) return; const k = e.key.toLowerCase(); if (k === 'escape') pop.style.display = 'none'; else if (k === 'f') act('only'); else if (k === 'x') act('not'); + else if (k === 'd' && DETAIL.has(ctx?.k)) act('detail'); + else if (k === 'n' && NAMEABLE.has(ctx?.k)) { e.preventDefault(); act('name'); } else if (k === 'enter' && !document.activeElement?.dataset?.a) { e.preventDefault(); act('records'); } }); document.addEventListener('click', e => { @@ -358,11 +396,6 @@ views.overview = async (el) => { const series = d.series || {times: [], names: [], values: []}; const areas = series.names.map((n, i) => ({name: appLabel(n), color: n === '__other__' ? OTHER : COLORS[i % 6], data: series.values[i]})); const lines = d.baseline ? [{name: t(basis === 'week' ? 'last_week' : 'prev_period'), color: 'var(--base)', dash: true, data: d.baseline}] : []; - const movers = d.movers || []; - const mmax = Math.max(1, ...movers.map(m => m.delta_bps)); - const moverHTML = movers.length ? movers.map(m => `
  • ${ipCell(m.ip)}
    +${fmtBps(m.delta_bps)}
    ${bar(m.delta_bps, mmax)} -
    ${m.before_bps < m.now_bps * 0.02 ? `${t('ov.mover_new')} ` : ''}${m.peer ? esc(t('ov.mover_reason', {peer: names.get(m.peer) || m.peer, port: m.port, cc: country(m.cc)})) : ''}${m.threat ? ` ${esc(t('thr.list'))}: ${esc(m.threat)}` : ''}
  • `).join('') - : `
  • ${t('ov.no_movers')}
  • `; const dirParts = (d.dir || []).map((p, i) => ({n: t('dir.' + p.key), v: p.wire, c: COLORS[i % 6], val: DIRS[p.key]})); const protoParts = (d.proto || []).map((p, i) => ({n: p.key === '__other__' ? t('other') : proto(+p.key), v: p.wire, c: p.key === '__other__' ? OTHER : COLORS[i % 6], val: p.key === '__other__' ? null : p.key})); const ccParts = (d.country || []).map((p, i) => ({n: country(p.key), v: p.wire, c: p.key === '__other__' ? OTHER : COLORS[i % 6], val: p.key === '__other__' ? null : p.key})); @@ -376,10 +409,9 @@ views.overview = async (el) => {
    ${t('kpi.peers')}
    ${nf(tot.peers)}
    ${esc(t('kpi.countries', {n: nf(tot.countries)}))}
    ${t('kpi.accuracy')}
    ${acc.n}
    ${acc.kind ? status(acc.kind, acc.d) : `${esc(acc.d)}`}
    -

    ${t('ov.bw_title')}

    ${lines.length ? t(basis === 'week' ? 'ov.bw_sub_week' : 'ov.bw_sub_prev') : ''}
    +

    ${t('ov.bw_title')}

    ${lines.length ? t(basis === 'week' ? 'ov.bw_sub_week' : 'ov.bw_sub_prev') : ''}
    ${areas.map((a, i) => `${series.names[i] === '__other__' ? esc(a.name) : V('app', series.names[i], a.name)}`).join('')}${lines.length ? `${esc(lines[0].name)}` : ''}
    - ${panel('c4', t('ov.movers'), t(basis === 'week' ? 'ov.movers_sub_week' : 'ov.movers_sub_prev'), `
      ${moverHTML}
    `)} ${panel('c4', t('ov.dir'), '', '
    ')} ${panel('c4', t('ov.proto'), '', '
    ')} ${panel('c4', t('ov.country'), '', '
    ')} @@ -394,17 +426,23 @@ views.overview = async (el) => { donut($('#dCC'), ccParts, 'country'); }; -const DIMS = ['client', 'server', 'conv', 'app', 'port', 'country', 'asn', 'segment', 'exporter', 'encap', 'vlan']; -// Conversations: who talks to whom, as its own page (the Top-N table for -// client, server and service, without the dimension tabs). -views.conv = el => topTable(el, 'conv', true); +const DIMS = ['conv', 'client', 'server', 'app', 'port', 'country', 'asn', 'segment', 'exporter', 'encap', 'vlan']; +// Top-N is one table. It starts with conversations (client, server, +// service, country); clicking one of those column headings groups the +// table by it, clicking a number heading sorts. The select switches to the +// less common groupings. +const GROUP_OF = {'col.client': 'client', 'col.server': 'server', 'col.service': 'port', 'col.port': 'port', 'col.country': 'country', 'col.app': 'app', 'col.asn': 'asn', 'col.segment': 'segment', 'col.device': 'exporter'}; views.topn = el => { - if (!DIMS.includes(state.dim)) state.dim = 'client'; - return topTable(el, state.dim, false); + if (!DIMS.includes(state.dim)) state.dim = 'conv'; + return topTable(el, state.dim); }; -async function topTable(el, dim, standalone) { +async function topTable(el, dim) { const d = await api('topn', {dim, limit: 66}); - const rows = d.rows || [], max = Math.max(1, ...rows.map(r => r.wire)), total = rows.reduce((s, r) => s + r.wire, 0); + let rows = d.rows || []; + const max = Math.max(1, ...rows.map(r => r.wire)), total = rows.reduce((s, r) => s + r.wire, 0); + const sk = state.sort.k, dirm = state.sort.asc ? 1 : -1; + if (sk !== 'wire') rows.sort((a, b) => dirm * ((a[sk] || 0) - (b[sk] || 0)) || b.wire - a.wire); + else if (state.sort.asc) rows.reverse(); const cell = r => { switch (dim) { case 'client': return [ipCell(r.key)]; @@ -424,14 +462,28 @@ async function topTable(el, dim, standalone) { const heads = {client: ['col.client'], server: ['col.server', 'col.country'], conv: ['col.client', 'col.server', 'col.service', 'col.country'], app: ['col.app'], port: ['col.port', 'col.app'], country: ['col.country', ''], asn: ['col.asn', 'col.org'], segment: ['col.segment'], exporter: ['col.device'], encap: ['col.encap'], vlan: ['col.vlan']}[dim]; const showPeers = ['client', 'server'].includes(dim) && rows.some(r => r.peers); + const groupTh = h => { + const g = GROUP_OF[h]; + if (!h) return ''; + if (g && g !== dim) return `${t(h)}`; + return `${t(h)}`; + }; + const sortTh = (k, label, cls = 'num') => { + const on = sk === k, ar = on ? (state.sort.asc ? '▲' : '▼') : ''; + return `${label}${ar}`; + }; + const slow = dim === 'conv' && spanMs() > 216e5 ? ` · ${esc(t('conv.slow'))}` : ''; el.innerHTML = `
    -
    ${standalone - ? `

    ${t('topn.title', {n: 66})}

    ${esc(t('conv.sub'))}${spanMs() > 216e5 ? ' · ' + esc(t('conv.slow')) : ''}` - : `

    ${t('topn.title', {n: 66})}

    ${DIMS.map(k => ``).join('')}
    `}
    - ${heads.map(h => ``).join('')}${showPeers ? `` : ''} +

    ${t('topn.title', {n: 66})}

    ${esc(t('topn.hint'))}${slow} +
    +
    ${h ? t(h) : ''}${t('col.traffic')}%${t('col.share')}${t('col.peers')}${t('col.flows')}
    ${heads.map(groupTh).join('')}${sortTh('wire', t('col.traffic'))}${showPeers ? sortTh('peers', t('col.peers')) : ''}${sortTh('flows', t('col.flows'))} ${rows.map((r, i) => `${cell(r).map(c => ``).join('')}${showPeers ? `` : ''}`).join('') || ``}
    %${t('col.share')}
    ${i + 1}${c}${fmtBytes(r.wire)}${nf(r.wire / Math.max(1, total) * 100, 1)}${bar(r.wire, max)}${nf(r.peers)}${nf(r.flows)}
    ${t('empty.nodata')}
    `; - el.querySelectorAll('[data-dim]').forEach(b => b.onclick = () => { state.dim = b.dataset.dim; render(); }); + $('#dimSel').onchange = e => { state.dim = e.target.value; state.sort = {k: 'wire', asc: false}; render(); }; + el.querySelectorAll('th[data-g]').forEach(h => h.onclick = () => { state.dim = h.dataset.g; state.sort = {k: 'wire', asc: false}; render(); }); + el.querySelectorAll('th[data-s]').forEach(h => h.onclick = () => { + const k = h.dataset.s; state.sort = {k, asc: state.sort.k === k ? !state.sort.asc : false}; render(); + }); } views.sankey = async (el) => { @@ -453,29 +505,31 @@ views.sankey = async (el) => { const nodeW = 12, pad = 14, colX = rtl ? [W - 130, Math.round(W / 2 - 6), 118] : [118, Math.round(W / 2 - 6), W - 130]; const nv = n => Math.max(n.in, n.out); const k = Math.min(...cols.map(c => (H - pad * (c.length - 1)) / Math.max(1, c.reduce((s, n) => s + nv(n), 0)))); - cols.forEach((c, ci) => { let yy = 0; c.forEach((n, i) => { n.x = colX[ci]; n.y = yy; n.h = nv(n) * k; n.o = 0; n.i = 0; n.color = ci === 0 ? (n.k === '__other__' ? OTHER : COLORS[i % 6]) : 'var(--ink-2)'; yy += n.h + pad; }); }); + cols.forEach((c, ci) => { let yy = 0; c.forEach((n, i) => { n.ci = ci; n.x = colX[ci]; n.y = yy; n.h = nv(n) * k; n.o = 0; n.i = 0; n.color = ci === 0 ? (n.k === '__other__' ? OTHER : COLORS[i % 6]) : 'var(--ink-2)'; yy += n.h + pad; }); }); let s = ``; const link = (A, B, v, col, lab) => { const w = v * k, y0 = A.y + A.o + w / 2, y1 = B.y + B.i + w / 2; A.o += w; B.i += w; const x0 = rtl ? A.x : A.x + nodeW, x1 = rtl ? B.x + nodeW : B.x, mx = (x0 + x1) / 2; - return ``; + return ``; }; const segColor = {}; cols[0].forEach(n => segColor[n.k] = n.color); [...d.seg_app].sort((a, b) => ids[0][a.S] - ids[0][b.S] || ids[1][a.T] - ids[1][b.T]).forEach(l => s += link(cols[0][ids[0][l.S]], cols[1][ids[1][l.T]], l.V, segColor[l.S], label(0, l.S) + ' → ' + label(1, l.T))); [...d.app_cc].sort((a, b) => ids[1][a.S] - ids[1][b.S] || ids[2][a.T] - ids[2][b.T]).forEach(l => s += link(cols[1][ids[1][l.S]], cols[2][ids[2][l.T]], l.V, 'var(--ink-3)', label(1, l.S) + ' → ' + label(2, l.T))); cols.forEach((c, ci) => c.forEach(n => { - s += `${esc(label(ci, n.k))} ${fmtBytes(nv(n))}`; + const dk = `data-c="${ci}" data-k="${esc(n.k)}"`; + s += `${esc(label(ci, n.k))} ${fmtBytes(nv(n))}`; + s += ``; const right = (ci === 2) !== rtl, tx = right ? n.x + nodeW + 8 : n.x - 8, anc = right ? 'start' : 'end'; if (n.h >= 26) { - s += `${esc(label(ci, n.k))}`; - s += `${fmtBytes(nv(n))}`; + s += `${esc(label(ci, n.k))}`; + s += `${fmtBytes(nv(n))}`; } else if (n.h >= 9 || ci !== 1) { - s += `${esc(label(ci, n.k))} ${fmtBytes(nv(n))}`; + s += `${esc(label(ci, n.k))} ${fmtBytes(nv(n))}`; } })); sEl.innerHTML = s + ''; sEl.querySelectorAll('path[data-l]').forEach(p => { p.onmousemove = e => showTip(e, `
    ${esc(p.dataset.l)}
    ${fmtBytes(+p.dataset.v)}`); p.onmouseleave = hideTip; }); - sEl.querySelectorAll('rect[data-k]').forEach(r => r.onclick = e => { + sEl.querySelectorAll('[data-k]').forEach(r => r.onclick = e => { e.stopPropagation(); const ci = +r.dataset.c, kk = r.dataset.k; if (kk === '__other__') return; openPop({k: field[ci], v: kk, label: label(ci, kk)}, r.getBoundingClientRect()); }); @@ -596,7 +650,8 @@ views.ifaces = async (el) => { }; views.sources = async (el) => { - const [d, inv] = await Promise.all([api('sources'), api('inventory').catch(() => ({text: ''}))]); + const [d, inv, gd] = await Promise.all([api('sources'), api('inventory').catch(() => ({text: ''})), api('geo').catch(() => ({sources: []}))]); + const geoRows = (gd.sources || []).map(g => `
  • ${status('ok', t('geo.kind_' + g.kind))} ${esc(g.file)}${g.kind !== 'table' ? ' · ' + esc(g.type) : ''}${g.entries ? ' · ' + esc(t('geo.entries', {n: nf(g.entries)})) : ''}${g.built && !g.built.startsWith('0001') ? ' · ' + esc(t('geo.built', {d: new Intl.DateTimeFormat(LANG, {dateStyle: 'medium'}).format(new Date(g.built))})) : ''}
  • `).join(''); const srcs = d.sources || []; const issueText = (code, s) => t('issue.' + code, {n: nf(s.pending), p: nf(s.lost_pct, 1) + '%', s: Math.round(Math.abs(s.clock_skew_ns) / 1e9) + ' s'}); const rows = srcs.map(s => { @@ -618,9 +673,27 @@ views.sources = async (el) => { ${rows || `${t('empty.first')}`}

    ${t('src.listeners')} ${lis || '—'} ${caps}

    ${snmp ? `

    ${t('src.snmp')} ${snmp}

    ` : ''}`)} - ${panel('c12', t('src.names'), t('src.names_sub'), ` + ${panel('c12', t('geo.title'), t('geo.sub'), `
      ${geoRows || `
    • ${esc(t('geo.none'))}
    • `}
    +
    +

    ${t('geo.where')}

    `)} + ${panel('c12', t('src.names'), t('src.names_sub'), `
    ${esc(t('src.names_help'))}
    host   192.168.3.28    ${esc(t('src.ex_host'))}
    +net    192.168.3.0/24  ${esc(t('src.ex_net'))}
    +device 192.168.1.1     ${esc(t('src.ex_device'))}
    +iface  192.168.1.1 3   ${esc(t('src.ex_iface'))}
    +snmp   192.168.1.1     public
    `)}
    `; + $('#geoFile').onchange = async e => { + const f = e.target.files[0]; if (!f) return; + const msg = $('#geoMsg'); msg.style.color = ''; msg.textContent = t('geo.uploading'); + try { + const res = await fetch('/api/geo', {method: 'POST', body: f}); + const j = await res.json().catch(() => ({})); + if (!res.ok) { msg.style.color = 'var(--crit)'; msg.textContent = j.error || res.statusText; return; } + msg.style.color = 'var(--good)'; msg.textContent = t('geo.installed', {f: j.installed}); + setTimeout(render, 1500); + } catch (err) { msg.style.color = 'var(--crit)'; msg.textContent = String(err.message || err); } + }; $('#invSave').onclick = async () => { const res = await fetch('/api/inventory', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({text: $('#inv').value})}); const j = await res.json().catch(() => ({})); @@ -630,11 +703,52 @@ views.sources = async (el) => { }; }; +// Drill-down: one host or one service, everything about it on one page. +// Every value on it opens the same menu, so one can keep drilling. +views.detail = async (el) => { + const det = state.det; + if (!det) { go('overview'); return; } + const fs = [...state.f.filter(x => !(x.f === det.f && x.v === det.v)), {f: det.f, v: det.v, neg: false}]; + const isIP = det.f === 'ip'; + const [d, conv, side1, side2, rec] = await Promise.all([ + api('overview', {}, fs), api('topn', {dim: 'conv', limit: 30}, fs), + api('topn', {dim: isIP ? 'port' : 'client', limit: 15}, fs), api('topn', {dim: isIP ? 'country' : 'server', limit: 15}, fs), + api('records', {limit: 50}, fs)]); + const title = isIP ? (names.get(det.v) ? `${esc(names.get(det.v))} ${esc(det.v)}` : esc(det.v)) : esc(det.v); + $('#title').textContent = t('nav.detail') + ': ' + (isIP ? (names.get(det.v) || det.v) : det.v); + const tot = d.totals, series = d.series || {times: [], names: [], values: []}; + const areas = series.names.map((n, i) => ({name: appLabel(n), color: n === '__other__' ? OTHER : COLORS[i % 6], data: series.values[i]})); + const lines = d.baseline ? [{name: t(d.basis === 'week' ? 'last_week' : 'prev_period'), color: 'var(--base)', dash: true, data: d.baseline}] : []; + const table = (rows, heads, cell) => { const m = Math.max(1, ...rows.map(r => r.wire)); return `${heads.map(h => ``).join('')} + ${rows.map((r, i) => `${cell(r).map(c => ``).join('')}`).join('') || ``}
    ${t(h)}${t('col.traffic')}${t('col.share')}
    ${i + 1}${c}${fmtBytes(r.wire)}${bar(r.wire, m)}
    ${t('empty.nodata')}
    `; }; + const s1 = side1.rows || [], s2 = side2.rows || [], cv = conv.rows || [], rr = rec.rows || []; + el.innerHTML = `
    +

    ${title}

    +
    +
    +
    ${t('kpi.total', {r: t('range.' + state.r)})}
    ${fmtBytes(tot.wire)}
    ${d.peak_at ? esc(t('kpi.peak', {v: fmtBps(d.peak_bps), t: fmtTime(d.peak_at, spanMs())})) : ' '}
    +
    ${t('kpi.now')}
    ${fmtBps(d.now_bps)}
     
    +
    ${t(isIP ? 'det.peers' : 'det.clients')}
    ${nf(isIP ? tot.peers + tot.hosts - 1 : tot.hosts)}
     
    +
    ${t('kpi.peers')}
    ${nf(tot.peers)}
    ${esc(t('kpi.countries', {n: nf(tot.countries)}))}
    +
    +

    ${t('ov.bw_title')}

    ${lines.length ? t(d.basis === 'week' ? 'ov.bw_sub_week' : 'ov.bw_sub_prev') : ''}
    +
    +
    ${areas.map((a, i) => `${series.names[i] === '__other__' ? esc(a.name) : V('app', series.names[i], a.name)}`).join('')}
    + ${panel('c12', t('det.conv'), '', table(cv, ['col.client', 'col.server', 'col.service', 'col.country'], r => [ipCell(r.key), ipCell(r.key2), V('port', r.key3, r.key3), esc(country(r.extra))]))} + ${isIP + ? panel('c6', t('det.services'), '', table(s1, ['col.port', 'col.app'], r => [V('port', r.key, r.key), esc(r.extra)])) + panel('c6', t('ov.country'), '', table(s2, ['col.country'], r => [V('country', r.key, country(r.key))])) + : panel('c6', t('det.clients'), '', table(s1, ['col.client'], r => [ipCell(r.key)])) + panel('c6', t('det.servers'), '', table(s2, ['col.server', 'col.country'], r => [ipCell(r.key), r.extra ? esc(country(r.extra)) : '']))} + ${panel('c12', t('det.flows'), esc(t('rec.latest', {n: rr.length})), `
    + ${rr.map(r => ``).join('') || ``}
    ${t('rc.time')}${t('rc.client')}${t('rc.server')}${t('rc.port')}${t('rc.app')}${t('rc.country')}${t('rc.traffic')}
    ${new Intl.DateTimeFormat(LANG, {hour: '2-digit', minute: '2-digit', second: '2-digit'}).format(new Date(r.ts))}${ipCell(r.client)}${ipCell(r.server)}${r.port ? V('port', r.port + '/' + (r.proto === 17 ? 'udp' : 'tcp'), r.port) : '—'}${V('app', r.app, r.app)}${r.dir === 3 ? `${t('internal')}` : (r.cc ? V('country', r.cc, country(r.cc)) : '—')}${fmtBytes(r.wire)}
    ${t('empty.nodata')}
    `)} +
    `; + tsChart($('#chDet'), {times: series.times, areas, lines}); +}; + // ------------------------------------------------------------ shell -async function render() { +async function render(push) { renderFilters(); - writeHash(); - $('#title').textContent = t('nav.' + state.v); + writeHash(push); + $('#title').textContent = t('nav.' + state.v); // the detail view refines it document.title = t('nav.' + state.v) + ' · traffic66'; document.querySelectorAll('#nav button').forEach(b => b.dataset.v === state.v ? b.setAttribute('aria-current', 'page') : b.removeAttribute('aria-current')); document.querySelectorAll('#range button').forEach(b => b.setAttribute('aria-pressed', b.dataset.r === state.r)); @@ -647,7 +761,7 @@ async function render() { } if (seq === loadSeq) resolveNames(); } -function go(v) { state.v = v; render(); window.scrollTo(0, 0); } +function go(v) { state.v = v; render(true); window.scrollTo(0, 0); } async function loadStatus() { try { @@ -660,7 +774,8 @@ async function loadStatus() { $('#self').innerHTML = `
    ${t(live ? 'self.receiving' : 'self.idle')}
    ${t('self.ingest')}${esc(t('self.per_sec', {n: nf(s.records_per_sec, s.records_per_sec < 10 ? 1 : 0)}))} ${t('self.dropped')}${nf(s.dropped)} - ${t('self.disk')}${s.days_left >= 0 ? esc(t('self.days', {n: nf(Math.min(s.days_left, 9999))})) : fmtBytes(s.disk_bytes)} + ${t('self.disk')}${fmtBytes(s.disk_bytes)} + ${t('self.free')} s.disk_free ? 'style="color:var(--crit)"' : ''} title="${esc(s.disk_need >= 0 ? t('self.need', {d: nf(s.retention_days), n: fmtBytes(s.disk_need)}) : t('self.need_later', {d: nf(s.retention_days)}))}">${fmtBytes(s.disk_free)} ${s.write_errors ? `!${nf(s.write_errors)}` : ''}`; $('#srcBadge').hidden = !s.source_warnings; $('#srcBadge').textContent = s.source_warnings; } catch (e) {} @@ -697,6 +812,8 @@ async function init() { document.addEventListener('click', e => { if (!e.target.closest('.cols')) { const m = $('#colMenu'); if (m) m.hidden = true; } }); readHash(); window.addEventListener('hashchange', () => { const before = location.hash; readHash(); if (before) render(); }); + // Back and Forward return to the previous page, e.g. out of a drill-down + window.addEventListener('popstate', () => { state.f = []; state.det = null; readHash(); render(); }); const res = await fetch('/api/status'); if (res.status === 401) { showLogin(); return; } $('#app').hidden = false; diff --git a/internal/web/static/i18n/ar.json b/internal/web/static/i18n/ar.json index 3d92686..0800938 100644 --- a/internal/web/static/i18n/ar.json +++ b/internal/web/static/i18n/ar.json @@ -1,7 +1,7 @@ { "lang.name": "العربية", "nav.overview": "نظرة عامة", -"nav.conv": "المحادثات", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "مسارات الحركة", "nav.geo": "الجغرافيا والشبكات", "nav.threats": "معلومات التهديدات", @@ -14,7 +14,6 @@ "self.per_sec": "{n} تدفق/ث", "self.dropped": "المُسقَط", "self.disk": "القرص", -"self.days": "يكفي {n} يومًا", "top.search": "ابحث عن IP أو شبكة أو منفذ أو دولة (اختياري)", "top.refresh": "يتحدّث كل 30 ثانية", "top.copy": "نسخ الرابط", @@ -66,11 +65,6 @@ "ov.bw_title": "عرض النطاق حسب التطبيق", "ov.bw_sub_week": "الخط المتقطع: الوقت نفسه الأسبوع الماضي", "ov.bw_sub_prev": "الخط المتقطع: الفترة السابقة", -"ov.movers": "من زادت حركته", -"ov.movers_sub_week": "مقارنةً بالفترة نفسها الأسبوع الماضي", -"ov.movers_sub_prev": "مقارنةً بالفترة السابقة", -"ov.no_movers": "لا شيء غير معتاد", -"ov.mover_reason": "غالبًا مع {peer} على {port} ({cc})", "ov.mover_new": "جديد", "ov.dir": "اتجاه الحركة", "ov.proto": "البروتوكول", @@ -115,7 +109,7 @@ "col.status": "الحالة", "col.list": "القائمة", "col.direction": "الاتجاه", -"conv.sub": "من يتواصل مع من: العميل والخادم والخدمة، حسب حجم الحركة", "conv.slow": "الفترات الطويلة تقرأ كل تفاصيل التدفقات وقد تستغرق وقتًا؛ ساعة واحدة هي الأسرع", +"conv.slow": "الفترات الطويلة تقرأ كل تفاصيل التدفقات وقد تستغرق وقتًا؛ ساعة واحدة هي الأسرع", "topn.title": "أعلى {n}", "topn.rollup_note": "فترة طويلة: البيانات من ملخصات بالساعة، لذا لا تتوفر التصفية هنا.", "dim.client": "العملاء", @@ -136,7 +130,7 @@ "geo.countries": "دول الطرف الآخر", "geo.countries_sub": "الحركة الداخلية مستبعدة", "geo.as": "شبكات الطرف الآخر (AS)", -"geo.no_table": "لا توجد بيانات. تحتاج الدول والشبكات إلى جدول ASN في مجلد البيانات (asn.tsv.gz).", +"geo.no_table": "لا توجد بيانات. ارفع قاعدة بيانات الدول والشبكات في صفحة المصادر لعرض الدول والشبكات.", "thr.hosts": "مضيفون تواصلوا مع عناوين مدرجة", "thr.upload": "ما أُرسل إلى عناوين مدرجة", "thr.flows": "تدفقات مع عناوين مدرجة", @@ -211,14 +205,15 @@ "empty.nodata": "لا توجد بيانات في هذه الفترة.", "empty.first": "لم يصل شيء بعد. أرسل sFlow إلى المنفذ UDP 6343 أو NetFlow إلى 2055 أو IPFIX إلى 4739 على هذا الجهاز.", "err.load": "تعذّر التحميل: {e}", -"err.db_memory": "بلغت قاعدة البيانات حد الذاكرة ولم تتمكن من الإجابة. اختر مدة أقصر، أو شغّل traffic66 بقيمة ‎-memory أكبر.", "err.db_busy": "تعذّر على قاعدة البيانات الإجابة الآن؛ السبب مسجل في سجل traffic66. ستعيد الصفحة المحاولة بعد 30 ثانية.", +"err.db_memory": "بلغت قاعدة البيانات حد الذاكرة ولم تتمكن من الإجابة. اختر مدة أقصر، أو شغّل traffic66 بقيمة ‎-memory أكبر.", +"err.db_busy": "تعذّر على قاعدة البيانات الإجابة الآن؛ السبب مسجل في سجل traffic66. ستعيد الصفحة المحاولة بعد 30 ثانية.", "login.title": "تسجيل الدخول", "login.user": "المستخدم", "login.password": "كلمة المرور", "login.go": "دخول", "login.bad": "المستخدم أو كلمة المرور غير صحيحة", "demo.badge": "عرض تجريبي", -"tui.help": "↑↓ اختيار Enter إجراءات f فقط x استبعاد / بحث t الفترة 1-9 الصفحات c مسح w المتصفح q خروج", +"tui.help": "↑↓ اختيار Enter إجراءات f فقط x استبعاد / بحث t الفترة 1-8 الصفحات c مسح w المتصفح q خروج", "tui.connecting": "جارٍ الاتصال بـ {url}…", "tui.error": "تعذّر الوصول إلى traffic66 على {url}: {e}", "tui.search": "بحث", @@ -227,5 +222,40 @@ "tui.opened": "فُتح في المتصفح", "tui.open_failed": "افتح هذا العنوان: {url}", "tui.login": "يلزم تسجيل الدخول: شغّله مع -user و -password، أو شغّله على جهاز traffic66", -"tui.close": "Esc إغلاق" +"tui.close": "Esc إغلاق", +"pop.detail": "عرض التفاصيل", +"pop.name": "تسمية…", +"pop.name_ph": "الاسم، مثل: حاسوب المالية", +"pop.named": "تم الحفظ؛ يظهر الاسم الآن في كل مكان", +"pop.actions": "إجراءات", +"topn.group": "التجميع حسب", +"topn.hint": "انقر عنوان عمود أزرق للتجميع حسبه، أو عنوانًا رقميًا للفرز", +"topn.by_col": "جمّع الجدول حسب هذا العمود", +"nav.detail": "التفاصيل", +"det.peers": "المضيفون الذين يتواصل معهم", +"det.clients": "المضيفون الذين يستخدمونه", +"det.conv": "مع من يتواصل", +"det.services": "الخدمات والمنافذ", +"det.servers": "الخوادم", +"det.flows": "أحدث سجلات التدفق", +"self.free": "متاح", +"self.need": "الاحتفاظ بتفاصيل {d} يومًا يحتاج إلى نحو {n} بالمعدل الحالي", +"self.need_later": "الاحتفاظ بتفاصيل {d} يومًا: تُقدَّر المساحة اللازمة بعد توفر بيانات يوم كامل", +"geo.title": "قاعدة بيانات الدول والشبكات", +"geo.sub": "تعرض الدولة والشبكة (AS) لكل عنوان بعيد", +"geo.none": "لا توجد قاعدة بيانات بعد، لذا تظهر الدول والشبكات كـ«غير معروف». ارفع واحدة أدناه.", +"geo.upload": "رفع ملف قاعدة بيانات…", +"geo.uploading": "جارٍ الرفع والتحقق…", +"geo.installed": "ثُبّتت باسم {f}. تُطبَّق على الحركة الجديدة من الآن.", +"geo.where": "المقبول: ملفات .mmdb (MaxMind GeoLite2 أو DB-IP Lite، للدول أو ASN) وجداول IP-to-ASN (.tsv أو .tsv.gz، مثل ملفات iptoasn.com). تنزيلات مجانية: db-ip.com/db/lite.php و iptoasn.com؛ يتطلب GeoLite2 حسابًا مجانيًا لدى MaxMind. تحتفظ الحركة المخزنة مسبقًا بالدولة التي حُفظت بها.", +"geo.kind_table": "جدول IP-to-ASN", +"geo.kind_country": "الدول", +"geo.kind_asn": "الشبكات (AS)", +"geo.built": "أُنشئت {d}", +"geo.entries": "{n} نطاق", +"src.names_help": "الأسهل: انقر أي عنوان في أي صفحة واختر «تسمية». يمكنك أيضًا كتابة إدخال واحد في كل سطر هنا، مثلًا:", +"src.ex_host": "حاسوب المالية", +"src.ex_net": "شبكة المكتب", +"src.ex_device": "الموجّه الرئيسي", +"src.ex_iface": "وصلة مزوّد الخدمة" } diff --git a/internal/web/static/i18n/bn.json b/internal/web/static/i18n/bn.json index 4c876a2..025f782 100644 --- a/internal/web/static/i18n/bn.json +++ b/internal/web/static/i18n/bn.json @@ -1,7 +1,7 @@ { "lang.name": "বাংলা", "nav.overview": "সারসংক্ষেপ", -"nav.conv": "কথোপকথন", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "ট্রাফিকের পথ", "nav.geo": "ভূগোল ও নেটওয়ার্ক", "nav.threats": "হুমকির তথ্য", @@ -14,7 +14,6 @@ "self.per_sec": "{n} ফ্লো/সে.", "self.dropped": "বাদ পড়েছে", "self.disk": "ডিস্ক", -"self.days": "আর {n} দিন", "top.search": "IP, নেটওয়ার্ক, পোর্ট, দেশ খুঁজুন (ঐচ্ছিক)", "top.refresh": "প্রতি ৩০ সেকেন্ডে হালনাগাদ", "top.copy": "লিংক কপি করুন", @@ -66,11 +65,6 @@ "ov.bw_title": "অ্যাপ্লিকেশন অনুযায়ী ব্যান্ডউইথ", "ov.bw_sub_week": "ড্যাশ লাইন: গত সপ্তাহের একই সময়", "ov.bw_sub_prev": "ড্যাশ লাইন: আগের সময়", -"ov.movers": "কার ট্রাফিক বেড়েছে", -"ov.movers_sub_week": "গত সপ্তাহের একই সময়ের সঙ্গে তুলনা", -"ov.movers_sub_prev": "আগের সময়ের সঙ্গে তুলনা", -"ov.no_movers": "অস্বাভাবিক কিছু নেই", -"ov.mover_reason": "মূলত {peer}-এর সঙ্গে {port}-এ ({cc})", "ov.mover_new": "নতুন", "ov.dir": "ট্রাফিকের দিক", "ov.proto": "প্রোটোকল", @@ -115,7 +109,7 @@ "col.status": "অবস্থা", "col.list": "তালিকা", "col.direction": "দিক", -"conv.sub": "কে কার সঙ্গে কথা বলছে: ট্রাফিক অনুযায়ী ক্লায়েন্ট, সার্ভার ও সার্ভিস", "conv.slow": "দীর্ঘ সময়সীমায় সব ফ্লো বিবরণ পড়তে হয়, সময় লাগতে পারে; 1 ঘণ্টা সবচেয়ে দ্রুত", +"conv.slow": "দীর্ঘ সময়সীমায় সব ফ্লো বিবরণ পড়তে হয়, সময় লাগতে পারে; 1 ঘণ্টা সবচেয়ে দ্রুত", "topn.title": "শীর্ষ {n}", "topn.rollup_note": "দীর্ঘ সময়সীমা: ডেটা ঘণ্টাভিত্তিক সারাংশ থেকে আসে, তাই এখানে ফিল্টার ব্যবহার করা যাবে না।", "dim.client": "ক্লায়েন্ট", @@ -136,7 +130,7 @@ "geo.countries": "অপর পক্ষের দেশ", "geo.countries_sub": "অভ্যন্তরীণ ট্রাফিক বাদ", "geo.as": "অপর পক্ষের নেটওয়ার্ক (AS)", -"geo.no_table": "কোনো ডেটা নেই। দেশ ও নেটওয়ার্ক দেখতে ডেটা ফোল্ডারে ASN টেবিল (asn.tsv.gz) দরকার।", +"geo.no_table": "কোনো ডেটা নেই। দেশ ও নেটওয়ার্ক দেখতে উৎস পেজে দেশ ও নেটওয়ার্ক ডেটাবেস আপলোড করুন।", "thr.hosts": "তালিকাভুক্ত ঠিকানার সঙ্গে যোগাযোগকারী হোস্ট", "thr.upload": "তালিকাভুক্ত ঠিকানায় পাঠানো", "thr.flows": "তালিকাভুক্ত ঠিকানাসহ ফ্লো", @@ -211,14 +205,15 @@ "empty.nodata": "এই সময়ে কোনো ডেটা নেই।", "empty.first": "এখনো কিছু আসেনি। এই মেশিনে sFlow UDP 6343-এ, NetFlow 2055-এ বা IPFIX 4739-এ পাঠান।", "err.load": "লোড করা যায়নি: {e}", -"err.db_memory": "ডেটাবেস তার মেমরি সীমায় পৌঁছেছে এবং উত্তর দিতে পারেনি। ছোট সময়সীমা বেছে নিন, অথবা বড় -memory দিয়ে traffic66 চালু করুন।", "err.db_busy": "ডেটাবেস এই মুহূর্তে উত্তর দিতে পারেনি; কারণ traffic66-এর লগে আছে। পেজটি 30 সেকেন্ড পরে আবার চেষ্টা করবে।", +"err.db_memory": "ডেটাবেস তার মেমরি সীমায় পৌঁছেছে এবং উত্তর দিতে পারেনি। ছোট সময়সীমা বেছে নিন, অথবা বড় -memory দিয়ে traffic66 চালু করুন।", +"err.db_busy": "ডেটাবেস এই মুহূর্তে উত্তর দিতে পারেনি; কারণ traffic66-এর লগে আছে। পেজটি 30 সেকেন্ড পরে আবার চেষ্টা করবে।", "login.title": "সাইন ইন", "login.user": "ব্যবহারকারী", "login.password": "পাসওয়ার্ড", "login.go": "সাইন ইন", "login.bad": "ব্যবহারকারী বা পাসওয়ার্ড ভুল", "demo.badge": "ডেমো", -"tui.help": "↑↓ বাছাই Enter কাজ f শুধু x বাদ / খুঁজুন t সময় 1-9 পাতা c মুছুন w ব্রাউজার q বের হন", +"tui.help": "↑↓ বাছাই Enter কাজ f শুধু x বাদ / খুঁজুন t সময় 1-8 পাতা c মুছুন w ব্রাউজার q বের হন", "tui.connecting": "{url}-এ সংযোগ হচ্ছে…", "tui.error": "{url}-এ traffic66-এর সঙ্গে সংযোগ হয়নি: {e}", "tui.search": "খুঁজুন", @@ -227,5 +222,40 @@ "tui.opened": "ব্রাউজারে খোলা হয়েছে", "tui.open_failed": "এই ঠিকানা খুলুন: {url}", "tui.login": "লগ ইন দরকার: -user ও -password দিয়ে চালু করুন, অথবা traffic66 চলা মেশিনে চালান", -"tui.close": "Esc বন্ধ" +"tui.close": "Esc বন্ধ", +"pop.detail": "বিস্তারিত দেখুন", +"pop.name": "নাম দিন…", +"pop.name_ph": "নাম, যেমন হিসাব বিভাগের পিসি", +"pop.named": "সংরক্ষিত হয়েছে; নামটি এখন সব জায়গায় দেখাবে", +"pop.actions": "অ্যাকশন", +"topn.group": "গ্রুপের ভিত্তি", +"topn.hint": "নীল কলাম শিরোনামে ক্লিক করলে সেই কলাম অনুযায়ী গ্রুপ হবে, সংখ্যার শিরোনামে ক্লিক করলে সাজানো হবে", +"topn.by_col": "টেবিলটি এই কলাম অনুযায়ী গ্রুপ করুন", +"nav.detail": "বিস্তারিত", +"det.peers": "যেসব হোস্টের সঙ্গে এটি কথা বলে", +"det.clients": "যেসব হোস্ট এটি ব্যবহার করে", +"det.conv": "এটি কার সঙ্গে কথা বলে", +"det.services": "সেবা ও পোর্ট", +"det.servers": "সার্ভার", +"det.flows": "সর্বশেষ ফ্লো রেকর্ড", +"self.free": "খালি", +"self.need": "বর্তমান হারে {d} দিনের বিস্তারিত রাখতে প্রায় {n} লাগবে", +"self.need_later": "{d} দিনের বিস্তারিত রাখা: এক দিনের ডেটা জমলে প্রয়োজনীয় জায়গার অনুমান করা হবে", +"geo.title": "দেশ ও নেটওয়ার্ক ডেটাবেস", +"geo.sub": "প্রতিটি দূরবর্তী ঠিকানার দেশ ও নেটওয়ার্ক (AS) দেখায়", +"geo.none": "এখনো কোনো ডেটাবেস নেই, তাই দেশ ও নেটওয়ার্ক অজানা দেখাচ্ছে। নিচে একটি আপলোড করুন।", +"geo.upload": "ডেটাবেস ফাইল আপলোড করুন…", +"geo.uploading": "আপলোড ও যাচাই হচ্ছে…", +"geo.installed": "{f} নামে ইনস্টল হয়েছে। এখন থেকে নতুন ট্রাফিকে প্রযোজ্য।", +"geo.where": "গ্রহণযোগ্য: .mmdb ফাইল (MaxMind GeoLite2 বা DB-IP Lite, দেশ বা ASN) এবং IP-to-ASN টেবিল (.tsv বা .tsv.gz, যেমন iptoasn.com থেকে)। বিনামূল্যে ডাউনলোড: db-ip.com/db/lite.php ও iptoasn.com; GeoLite2-এর জন্য বিনামূল্যের MaxMind অ্যাকাউন্ট লাগে। আগে সংরক্ষিত ট্রাফিক সংরক্ষণের সময়ের দেশই রাখে।", +"geo.kind_table": "IP-to-ASN টেবিল", +"geo.kind_country": "দেশ", +"geo.kind_asn": "নেটওয়ার্ক (AS)", +"geo.built": "{d} তৈরি", +"geo.entries": "{n}টি রেঞ্জ", +"src.names_help": "সবচেয়ে সহজ: যেকোনো পেজে যেকোনো ঠিকানায় ক্লিক করে “নাম দিন” বেছে নিন। এখানে প্রতি লাইনে একটি করে এন্ট্রিও লিখতে পারেন, যেমন:", +"src.ex_host": "হিসাব বিভাগের পিসি", +"src.ex_net": "অফিস LAN", +"src.ex_device": "কোর রাউটার", +"src.ex_iface": "ISP আপলিংক" } diff --git a/internal/web/static/i18n/en.json b/internal/web/static/i18n/en.json index dda5ba5..917c376 100644 --- a/internal/web/static/i18n/en.json +++ b/internal/web/static/i18n/en.json @@ -1,7 +1,7 @@ { "lang.name": "English", "nav.overview": "Overview", -"nav.conv": "Conversations", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "Flow paths", "nav.geo": "Geo & networks", "nav.threats": "Threat intel", @@ -14,7 +14,6 @@ "self.per_sec": "{n} flows/s", "self.dropped": "Dropped", "self.disk": "Disk", -"self.days": "{n} days left", "top.search": "Search IP, network, port, country (optional)", "top.refresh": "Refreshes every 30 s", "top.copy": "Copy link", @@ -66,11 +65,6 @@ "ov.bw_title": "Bandwidth by application", "ov.bw_sub_week": "Dashed line: same time last week", "ov.bw_sub_prev": "Dashed line: previous period", -"ov.movers": "Who grew", -"ov.movers_sub_week": "Compared with the same time last week", -"ov.movers_sub_prev": "Compared with the previous period", -"ov.no_movers": "Nothing unusual", -"ov.mover_reason": "Mostly with {peer} on {port} ({cc})", "ov.mover_new": "new", "ov.dir": "Traffic direction", "ov.proto": "Protocol", @@ -115,7 +109,7 @@ "col.status": "Status", "col.list": "List", "col.direction": "Direction", -"conv.sub": "Who talks to whom: client, server and service, by traffic", "conv.slow": "long ranges read all flow detail and can take a while; 1 hour is fastest", +"conv.slow": "long ranges read all flow detail and can take a while; 1 hour is fastest", "topn.title": "Top {n}", "topn.rollup_note": "Long time range: served from hourly summaries, so filters are not available here.", "dim.client": "Clients", @@ -136,7 +130,7 @@ "geo.countries": "Remote countries", "geo.countries_sub": "Internal traffic is excluded", "geo.as": "Remote networks (AS)", -"geo.no_table": "No data. Countries and networks need an ASN table in the data directory (asn.tsv.gz).", +"geo.no_table": "No data. Upload a countries and networks database on the Sources page to see countries and networks.", "thr.hosts": "Hosts talking to listed addresses", "thr.upload": "Sent to listed addresses", "thr.flows": "Flows involving listed addresses", @@ -211,14 +205,15 @@ "empty.nodata": "No data in this period.", "empty.first": "Nothing received yet. Send sFlow to UDP 6343, NetFlow to 2055 or IPFIX to 4739 on this machine.", "err.load": "Could not load: {e}", -"err.db_memory": "The database reached its memory limit and could not answer. Choose a shorter time range, or start traffic66 with a larger -memory.", "err.db_busy": "The database could not answer just now; the reason is in the traffic66 log. The page tries again in 30 seconds.", +"err.db_memory": "The database reached its memory limit and could not answer. Choose a shorter time range, or start traffic66 with a larger -memory.", +"err.db_busy": "The database could not answer just now; the reason is in the traffic66 log. The page tries again in 30 seconds.", "login.title": "Sign in", "login.user": "User", "login.password": "Password", "login.go": "Sign in", "login.bad": "Wrong user or password", "demo.badge": "Demo", -"tui.help": "↑↓ select Enter actions f only x exclude / search t time 1-9 pages c clear w browser q quit", +"tui.help": "↑↓ select Enter actions f only x exclude / search t time 1-8 pages c clear w browser q quit", "tui.connecting": "Connecting to {url}…", "tui.error": "Cannot reach traffic66 at {url}: {e}", "tui.search": "Search", @@ -227,5 +222,40 @@ "tui.opened": "Opened in the browser", "tui.open_failed": "Open this address: {url}", "tui.login": "Login required: start with -user and -password, or run on the traffic66 machine", -"tui.close": "Esc close" +"tui.close": "Esc close", +"pop.detail": "Show details", +"pop.name": "Name it…", +"pop.name_ph": "Name, e.g. Finance PC", +"pop.named": "Saved; the name now shows everywhere", +"pop.actions": "Actions", +"topn.group": "Group by", +"topn.hint": "Click a blue column heading to group by it, a number heading to sort", +"topn.by_col": "Group the table by this column", +"nav.detail": "Details", +"det.peers": "Hosts it talks to", +"det.clients": "Hosts using it", +"det.conv": "Who it talks to", +"det.services": "Services and ports", +"det.servers": "Servers", +"det.flows": "Latest flow records", +"self.free": "Free", +"self.need": "Keeping {d} days of detail needs about {n} at the current rate", +"self.need_later": "Keeping {d} days of detail: the space needed is estimated once there is a day of data", +"geo.title": "Countries and networks database", +"geo.sub": "Shows the country and network (AS) of each remote address", +"geo.none": "No database yet, so countries and networks show as Unknown. Upload one below.", +"geo.upload": "Upload a database file…", +"geo.uploading": "Uploading and checking…", +"geo.installed": "Installed as {f}. It applies to new traffic from now on.", +"geo.where": "Accepted: .mmdb files (MaxMind GeoLite2 or DB-IP Lite, country or ASN) and IP-to-ASN tables (.tsv or .tsv.gz, as from iptoasn.com). Free downloads: db-ip.com/db/lite.php and iptoasn.com; GeoLite2 needs a free MaxMind account. Traffic already stored keeps the country it was saved with.", +"geo.kind_table": "IP-to-ASN table", +"geo.kind_country": "Countries", +"geo.kind_asn": "Networks (AS)", +"geo.built": "built {d}", +"geo.entries": "{n} ranges", +"src.names_help": "Easiest: click any address on any page and choose “Name it”. You can also write one entry per line here, for example:", +"src.ex_host": "Finance PC", +"src.ex_net": "Office LAN", +"src.ex_device": "Core router", +"src.ex_iface": "ISP uplink" } diff --git a/internal/web/static/i18n/es.json b/internal/web/static/i18n/es.json index d841995..219c04d 100644 --- a/internal/web/static/i18n/es.json +++ b/internal/web/static/i18n/es.json @@ -1,7 +1,7 @@ { "lang.name": "Español", "nav.overview": "Resumen", -"nav.conv": "Conversaciones", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "Rutas de tráfico", "nav.geo": "Geografía y redes", "nav.threats": "Inteligencia de amenazas", @@ -14,7 +14,6 @@ "self.per_sec": "{n} flujos/s", "self.dropped": "Descartados", "self.disk": "Disco", -"self.days": "quedan {n} días", "top.search": "Buscar IP, red, puerto, país (opcional)", "top.refresh": "Se actualiza cada 30 s", "top.copy": "Copiar enlace", @@ -66,11 +65,6 @@ "ov.bw_title": "Ancho de banda por aplicación", "ov.bw_sub_week": "Línea discontinua: misma hora la semana pasada", "ov.bw_sub_prev": "Línea discontinua: periodo anterior", -"ov.movers": "Quién ha crecido", -"ov.movers_sub_week": "Comparado con la misma franja de la semana pasada", -"ov.movers_sub_prev": "Comparado con el periodo anterior", -"ov.no_movers": "Nada fuera de lo normal", -"ov.mover_reason": "Sobre todo con {peer} en {port} ({cc})", "ov.mover_new": "nuevo", "ov.dir": "Dirección del tráfico", "ov.proto": "Protocolo", @@ -115,7 +109,7 @@ "col.status": "Estado", "col.list": "Lista", "col.direction": "Dirección", -"conv.sub": "Quién habla con quién: cliente, servidor y servicio, por tráfico", "conv.slow": "los rangos largos leen todo el detalle y pueden tardar; 1 hora es lo más rápido", +"conv.slow": "los rangos largos leen todo el detalle y pueden tardar; 1 hora es lo más rápido", "topn.title": "Top {n}", "topn.rollup_note": "Periodo largo: los datos salen de resúmenes por hora, por lo que aquí no se pueden aplicar filtros.", "dim.client": "Clientes", @@ -136,7 +130,7 @@ "geo.countries": "Países remotos", "geo.countries_sub": "Se excluye el tráfico interno", "geo.as": "Redes remotas (AS)", -"geo.no_table": "Sin datos. Para ver países y redes hace falta una tabla ASN en el directorio de datos (asn.tsv.gz).", +"geo.no_table": "Sin datos. Suba una base de datos de países y redes en la página Fuentes para ver países y redes.", "thr.hosts": "Equipos que hablan con direcciones listadas", "thr.upload": "Enviado a direcciones listadas", "thr.flows": "Flujos con direcciones listadas", @@ -211,14 +205,15 @@ "empty.nodata": "No hay datos en este periodo.", "empty.first": "Aún no se ha recibido nada. Envíe sFlow al puerto UDP 6343, NetFlow al 2055 o IPFIX al 4739 de esta máquina.", "err.load": "No se pudo cargar: {e}", -"err.db_memory": "La base de datos llegó a su límite de memoria y no pudo responder. Elija un intervalo más corto o inicie traffic66 con un -memory mayor.", "err.db_busy": "La base de datos no pudo responder ahora; el motivo está en el registro de traffic66. La página lo reintentará en 30 segundos.", +"err.db_memory": "La base de datos llegó a su límite de memoria y no pudo responder. Elija un intervalo más corto o inicie traffic66 con un -memory mayor.", +"err.db_busy": "La base de datos no pudo responder ahora; el motivo está en el registro de traffic66. La página lo reintentará en 30 segundos.", "login.title": "Iniciar sesión", "login.user": "Usuario", "login.password": "Contraseña", "login.go": "Entrar", "login.bad": "Usuario o contraseña incorrectos", "demo.badge": "Demo", -"tui.help": "↑↓ elegir Enter acciones f solo x excluir / buscar t periodo 1-9 páginas c quitar w navegador q salir", +"tui.help": "↑↓ elegir Enter acciones f solo x excluir / buscar t periodo 1-8 páginas c quitar w navegador q salir", "tui.connecting": "Conectando con {url}…", "tui.error": "No se puede conectar con traffic66 en {url}: {e}", "tui.search": "Buscar", @@ -227,5 +222,40 @@ "tui.opened": "Abierto en el navegador", "tui.open_failed": "Abra esta dirección: {url}", "tui.login": "Hace falta iniciar sesión: arranque con -user y -password, o ejecútelo en la máquina de traffic66", -"tui.close": "Esc cerrar" +"tui.close": "Esc cerrar", +"pop.detail": "Ver detalles", +"pop.name": "Ponerle nombre…", +"pop.name_ph": "Nombre, p. ej. PC de contabilidad", +"pop.named": "Guardado; el nombre ya aparece en todas partes", +"pop.actions": "Acciones", +"topn.group": "Agrupar por", +"topn.hint": "Haga clic en un encabezado de columna azul para agrupar por ella, o en uno numérico para ordenar", +"topn.by_col": "Agrupar la tabla por esta columna", +"nav.detail": "Detalles", +"det.peers": "Equipos con los que habla", +"det.clients": "Equipos que lo usan", +"det.conv": "Con quién habla", +"det.services": "Servicios y puertos", +"det.servers": "Servidores", +"det.flows": "Últimos registros de flujo", +"self.free": "Libre", +"self.need": "Conservar {d} días de detalle requiere unos {n} al ritmo actual", +"self.need_later": "Conservar {d} días de detalle: el espacio necesario se estimará cuando haya un día de datos", +"geo.title": "Base de datos de países y redes", +"geo.sub": "Muestra el país y la red (AS) de cada dirección remota", +"geo.none": "Aún no hay base de datos, así que países y redes aparecen como Desconocido. Suba una abajo.", +"geo.upload": "Subir un archivo de base de datos…", +"geo.uploading": "Subiendo y comprobando…", +"geo.installed": "Instalada como {f}. Se aplica al tráfico nuevo a partir de ahora.", +"geo.where": "Se aceptan: archivos .mmdb (MaxMind GeoLite2 o DB-IP Lite, de países o ASN) y tablas IP-to-ASN (.tsv o .tsv.gz, como las de iptoasn.com). Descargas gratuitas: db-ip.com/db/lite.php e iptoasn.com; GeoLite2 requiere una cuenta gratuita de MaxMind. El tráfico ya almacenado conserva el país con el que se guardó.", +"geo.kind_table": "Tabla IP-to-ASN", +"geo.kind_country": "Países", +"geo.kind_asn": "Redes (AS)", +"geo.built": "generada el {d}", +"geo.entries": "{n} rangos", +"src.names_help": "Lo más fácil: haga clic en cualquier dirección de cualquier página y elija «Ponerle nombre». También puede escribir aquí una entrada por línea, por ejemplo:", +"src.ex_host": "PC de contabilidad", +"src.ex_net": "LAN de oficina", +"src.ex_device": "Router de núcleo", +"src.ex_iface": "Enlace al ISP" } diff --git a/internal/web/static/i18n/fr.json b/internal/web/static/i18n/fr.json index b6bbc9a..fcf32de 100644 --- a/internal/web/static/i18n/fr.json +++ b/internal/web/static/i18n/fr.json @@ -1,7 +1,7 @@ { "lang.name": "Français", "nav.overview": "Vue d'ensemble", -"nav.conv": "Conversations", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "Chemins du trafic", "nav.geo": "Géographie et réseaux", "nav.threats": "Menaces", @@ -14,7 +14,6 @@ "self.per_sec": "{n} flux/s", "self.dropped": "Rejetés", "self.disk": "Disque", -"self.days": "{n} jours restants", "top.search": "Rechercher une IP, un réseau, un port, un pays (facultatif)", "top.refresh": "Actualisé toutes les 30 s", "top.copy": "Copier le lien", @@ -66,11 +65,6 @@ "ov.bw_title": "Débit par application", "ov.bw_sub_week": "Pointillés : même heure la semaine dernière", "ov.bw_sub_prev": "Pointillés : période précédente", -"ov.movers": "Qui a augmenté", -"ov.movers_sub_week": "Par rapport au même créneau la semaine dernière", -"ov.movers_sub_prev": "Par rapport à la période précédente", -"ov.no_movers": "Rien d'inhabituel", -"ov.mover_reason": "Surtout avec {peer} sur {port} ({cc})", "ov.mover_new": "nouveau", "ov.dir": "Sens du trafic", "ov.proto": "Protocole", @@ -115,7 +109,7 @@ "col.status": "État", "col.list": "Liste", "col.direction": "Sens", -"conv.sub": "Qui parle à qui : client, serveur et service, par trafic", "conv.slow": "les longues périodes lisent tout le détail et peuvent prendre du temps ; 1 heure est le plus rapide", +"conv.slow": "les longues périodes lisent tout le détail et peuvent prendre du temps ; 1 heure est le plus rapide", "topn.title": "Top {n}", "topn.rollup_note": "Période longue : les données proviennent des résumés horaires, les filtres ne sont donc pas disponibles ici.", "dim.client": "Clients", @@ -136,7 +130,7 @@ "geo.countries": "Pays distants", "geo.countries_sub": "Le trafic interne est exclu", "geo.as": "Réseaux distants (AS)", -"geo.no_table": "Aucune donnée. Les pays et réseaux nécessitent une table ASN dans le répertoire de données (asn.tsv.gz).", +"geo.no_table": "Aucune donnée. Importez une base de données pays et réseaux sur la page Sources pour voir les pays et réseaux.", "thr.hosts": "Hôtes en contact avec des adresses listées", "thr.upload": "Envoyé vers des adresses listées", "thr.flows": "Flux impliquant des adresses listées", @@ -211,14 +205,15 @@ "empty.nodata": "Aucune donnée sur cette période.", "empty.first": "Rien reçu pour l'instant. Envoyez le sFlow sur le port UDP 6343, le NetFlow sur 2055 ou l'IPFIX sur 4739 de cette machine.", "err.load": "Chargement impossible : {e}", -"err.db_memory": "La base de données a atteint sa limite de mémoire et n'a pas pu répondre. Choisissez une période plus courte, ou lancez traffic66 avec un -memory plus grand.", "err.db_busy": "La base de données n'a pas pu répondre pour l'instant ; la raison figure dans le journal de traffic66. La page réessaiera dans 30 secondes.", +"err.db_memory": "La base de données a atteint sa limite de mémoire et n'a pas pu répondre. Choisissez une période plus courte, ou lancez traffic66 avec un -memory plus grand.", +"err.db_busy": "La base de données n'a pas pu répondre pour l'instant ; la raison figure dans le journal de traffic66. La page réessaiera dans 30 secondes.", "login.title": "Connexion", "login.user": "Utilisateur", "login.password": "Mot de passe", "login.go": "Se connecter", "login.bad": "Utilisateur ou mot de passe incorrect", "demo.badge": "Démo", -"tui.help": "↑↓ choisir Entrée actions f seulement x exclure / rechercher t période 1-9 pages c effacer w navigateur q quitter", +"tui.help": "↑↓ choisir Entrée actions f seulement x exclure / rechercher t période 1-8 pages c effacer w navigateur q quitter", "tui.connecting": "Connexion à {url}…", "tui.error": "Impossible de joindre traffic66 à {url} : {e}", "tui.search": "Rechercher", @@ -227,5 +222,40 @@ "tui.opened": "Ouvert dans le navigateur", "tui.open_failed": "Ouvrez cette adresse : {url}", "tui.login": "Connexion requise : lancez avec -user et -password, ou exécutez sur la machine de traffic66", -"tui.close": "Échap fermer" +"tui.close": "Échap fermer", +"pop.detail": "Voir les détails", +"pop.name": "Nommer…", +"pop.name_ph": "Nom, p. ex. PC Compta", +"pop.named": "Enregistré ; le nom apparaît désormais partout", +"pop.actions": "Actions", +"topn.group": "Regrouper par", +"topn.hint": "Cliquez sur un en-tête de colonne bleu pour regrouper par cette colonne, sur un en-tête numérique pour trier", +"topn.by_col": "Regrouper le tableau par cette colonne", +"nav.detail": "Détails", +"det.peers": "Hôtes avec lesquels il communique", +"det.clients": "Hôtes qui l'utilisent", +"det.conv": "Avec qui il communique", +"det.services": "Services et ports", +"det.servers": "Serveurs", +"det.flows": "Derniers enregistrements de flux", +"self.free": "Libre", +"self.need": "Conserver {d} jours de détail nécessite environ {n} au rythme actuel", +"self.need_later": "Conserver {d} jours de détail : l'espace nécessaire sera estimé dès qu'il y aura une journée de données", +"geo.title": "Base de données pays et réseaux", +"geo.sub": "Affiche le pays et le réseau (AS) de chaque adresse distante", +"geo.none": "Pas encore de base de données : pays et réseaux s'affichent comme Inconnu. Importez-en une ci-dessous.", +"geo.upload": "Importer un fichier de base de données…", +"geo.uploading": "Import et vérification…", +"geo.installed": "Installée sous {f}. Elle s'applique au nouveau trafic à partir de maintenant.", +"geo.where": "Formats acceptés : fichiers .mmdb (MaxMind GeoLite2 ou DB-IP Lite, pays ou ASN) et tables IP-to-ASN (.tsv ou .tsv.gz, comme celles d'iptoasn.com). Téléchargements gratuits : db-ip.com/db/lite.php et iptoasn.com ; GeoLite2 nécessite un compte MaxMind gratuit. Le trafic déjà stocké conserve le pays avec lequel il a été enregistré.", +"geo.kind_table": "Table IP-to-ASN", +"geo.kind_country": "Pays", +"geo.kind_asn": "Réseaux (AS)", +"geo.built": "générée le {d}", +"geo.entries": "{n} plages", +"src.names_help": "Le plus simple : cliquez sur n'importe quelle adresse, sur n'importe quelle page, puis choisissez « Nommer ». Vous pouvez aussi écrire ici une entrée par ligne, par exemple :", +"src.ex_host": "PC Compta", +"src.ex_net": "LAN du bureau", +"src.ex_device": "Routeur de cœur", +"src.ex_iface": "Lien opérateur" } diff --git a/internal/web/static/i18n/hi.json b/internal/web/static/i18n/hi.json index 0aff427..985f512 100644 --- a/internal/web/static/i18n/hi.json +++ b/internal/web/static/i18n/hi.json @@ -1,7 +1,7 @@ { "lang.name": "हिन्दी", "nav.overview": "सारांश", -"nav.conv": "बातचीत", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "ट्रैफ़िक के रास्ते", "nav.geo": "भूगोल और नेटवर्क", "nav.threats": "ख़तरे की जानकारी", @@ -14,7 +14,6 @@ "self.per_sec": "{n} फ़्लो/से.", "self.dropped": "छोड़े गए", "self.disk": "डिस्क", -"self.days": "{n} दिन बाकी", "top.search": "IP, नेटवर्क, पोर्ट, देश खोजें (वैकल्पिक)", "top.refresh": "हर 30 सेकंड में ताज़ा", "top.copy": "लिंक कॉपी करें", @@ -66,11 +65,6 @@ "ov.bw_title": "ऐप्लिकेशन के हिसाब से बैंडविड्थ", "ov.bw_sub_week": "डैश वाली रेखा: पिछले हफ़्ते इसी समय", "ov.bw_sub_prev": "डैश वाली रेखा: पिछली अवधि", -"ov.movers": "किसका ट्रैफ़िक बढ़ा", -"ov.movers_sub_week": "पिछले हफ़्ते इसी समय से तुलना", -"ov.movers_sub_prev": "पिछली अवधि से तुलना", -"ov.no_movers": "कुछ भी असामान्य नहीं", -"ov.mover_reason": "ज़्यादातर {peer} के साथ {port} पर ({cc})", "ov.mover_new": "नया", "ov.dir": "ट्रैफ़िक की दिशा", "ov.proto": "प्रोटोकॉल", @@ -115,7 +109,7 @@ "col.status": "स्थिति", "col.list": "सूची", "col.direction": "दिशा", -"conv.sub": "कौन किससे बात कर रहा है: ट्रैफ़िक के क्रम में क्लाइंट, सर्वर और सेवा", "conv.slow": "लंबी अवधि में पूरा फ़्लो विवरण पढ़ा जाता है और समय लग सकता है; 1 घंटा सबसे तेज़ है", +"conv.slow": "लंबी अवधि में पूरा फ़्लो विवरण पढ़ा जाता है और समय लग सकता है; 1 घंटा सबसे तेज़ है", "topn.title": "शीर्ष {n}", "topn.rollup_note": "लंबी अवधि: डेटा घंटेवार सारांश से आता है, इसलिए यहाँ फ़िल्टर उपलब्ध नहीं हैं।", "dim.client": "क्लाइंट", @@ -136,7 +130,7 @@ "geo.countries": "दूसरे पक्ष के देश", "geo.countries_sub": "आंतरिक ट्रैफ़िक शामिल नहीं", "geo.as": "दूसरे पक्ष के नेटवर्क (AS)", -"geo.no_table": "कोई डेटा नहीं। देश और नेटवर्क देखने के लिए डेटा फ़ोल्डर में ASN तालिका (asn.tsv.gz) चाहिए।", +"geo.no_table": "कोई डेटा नहीं। देश और नेटवर्क देखने के लिए स्रोत पेज पर देश और नेटवर्क डेटाबेस अपलोड करें।", "thr.hosts": "सूचीबद्ध पतों से संपर्क करने वाले होस्ट", "thr.upload": "सूचीबद्ध पतों पर भेजा गया", "thr.flows": "सूचीबद्ध पतों वाले फ़्लो", @@ -211,14 +205,15 @@ "empty.nodata": "इस अवधि में कोई डेटा नहीं।", "empty.first": "अभी तक कुछ नहीं मिला। इस मशीन पर sFlow को UDP 6343, NetFlow को 2055 या IPFIX को 4739 पर भेजें।", "err.load": "लोड नहीं हो सका: {e}", -"err.db_memory": "डेटाबेस अपनी मेमोरी सीमा तक पहुँच गया और जवाब नहीं दे सका। छोटी समय सीमा चुनें, या traffic66 को बड़े -memory के साथ चलाएँ।", "err.db_busy": "डेटाबेस अभी जवाब नहीं दे सका; कारण traffic66 के लॉग में है। पेज 30 सेकंड में फिर कोशिश करेगा।", +"err.db_memory": "डेटाबेस अपनी मेमोरी सीमा तक पहुँच गया और जवाब नहीं दे सका। छोटी समय सीमा चुनें, या traffic66 को बड़े -memory के साथ चलाएँ।", +"err.db_busy": "डेटाबेस अभी जवाब नहीं दे सका; कारण traffic66 के लॉग में है। पेज 30 सेकंड में फिर कोशिश करेगा।", "login.title": "साइन इन", "login.user": "उपयोगकर्ता", "login.password": "पासवर्ड", "login.go": "साइन इन", "login.bad": "उपयोगकर्ता या पासवर्ड ग़लत है", "demo.badge": "डेमो", -"tui.help": "↑↓ चुनें Enter क्रियाएँ f केवल x हटाएँ / खोजें t अवधि 1-9 पेज c साफ़ w ब्राउज़र q बाहर", +"tui.help": "↑↓ चुनें Enter क्रियाएँ f केवल x हटाएँ / खोजें t अवधि 1-8 पेज c साफ़ w ब्राउज़र q बाहर", "tui.connecting": "{url} से जुड़ रहा है…", "tui.error": "{url} पर traffic66 तक नहीं पहुँच सके: {e}", "tui.search": "खोजें", @@ -227,5 +222,40 @@ "tui.opened": "ब्राउज़र में खोला गया", "tui.open_failed": "यह पता खोलें: {url}", "tui.login": "लॉग इन ज़रूरी है: -user और -password के साथ शुरू करें, या traffic66 वाली मशीन पर चलाएँ", -"tui.close": "Esc बंद करें" +"tui.close": "Esc बंद करें", +"pop.detail": "विवरण देखें", +"pop.name": "नाम दें…", +"pop.name_ph": "नाम, जैसे अकाउंट्स पीसी", +"pop.named": "सहेज लिया गया; यह नाम अब हर जगह दिखेगा", +"pop.actions": "कार्रवाइयाँ", +"topn.group": "इसके अनुसार समूह", +"topn.hint": "नीले कॉलम शीर्षक पर क्लिक करने से उसके अनुसार समूह बनेगा, संख्या वाले शीर्षक पर क्लिक करने से क्रम बदलेगा", +"topn.by_col": "तालिका को इस कॉलम के अनुसार समूहित करें", +"nav.detail": "विवरण", +"det.peers": "जिन होस्ट से यह बात करता है", +"det.clients": "जो होस्ट इसका उपयोग करते हैं", +"det.conv": "यह किससे बात करता है", +"det.services": "सेवाएँ और पोर्ट", +"det.servers": "सर्वर", +"det.flows": "नवीनतम फ़्लो रिकॉर्ड", +"self.free": "खाली", +"self.need": "मौजूदा दर पर {d} दिन का विवरण रखने के लिए लगभग {n} चाहिए", +"self.need_later": "{d} दिन का विवरण रखना: एक दिन का डेटा आने के बाद ज़रूरी जगह का अनुमान लगाया जाएगा", +"geo.title": "देश और नेटवर्क डेटाबेस", +"geo.sub": "हर दूरस्थ पते का देश और नेटवर्क (AS) दिखाता है", +"geo.none": "अभी कोई डेटाबेस नहीं है, इसलिए देश और नेटवर्क अज्ञात दिखते हैं। नीचे एक अपलोड करें।", +"geo.upload": "डेटाबेस फ़ाइल अपलोड करें…", +"geo.uploading": "अपलोड और जाँच हो रही है…", +"geo.installed": "{f} के रूप में इंस्टॉल हुआ। अब से नए ट्रैफ़िक पर लागू होगा।", +"geo.where": "स्वीकार्य: .mmdb फ़ाइलें (MaxMind GeoLite2 या DB-IP Lite, देश या ASN) और IP-to-ASN तालिकाएँ (.tsv या .tsv.gz, जैसे iptoasn.com से)। मुफ़्त डाउनलोड: db-ip.com/db/lite.php और iptoasn.com; GeoLite2 के लिए मुफ़्त MaxMind खाता चाहिए। पहले से सहेजा गया ट्रैफ़िक वही देश रखता है जिसके साथ वह सहेजा गया था।", +"geo.kind_table": "IP-to-ASN तालिका", +"geo.kind_country": "देश", +"geo.kind_asn": "नेटवर्क (AS)", +"geo.built": "{d} को बना", +"geo.entries": "{n} रेंज", +"src.names_help": "सबसे आसान: किसी भी पेज पर किसी भी पते पर क्लिक करें और “नाम दें” चुनें। आप यहाँ हर पंक्ति में एक प्रविष्टि भी लिख सकते हैं, उदाहरण:", +"src.ex_host": "अकाउंट्स पीसी", +"src.ex_net": "ऑफ़िस LAN", +"src.ex_device": "कोर राउटर", +"src.ex_iface": "ISP अपलिंक" } diff --git a/internal/web/static/i18n/id.json b/internal/web/static/i18n/id.json index f70ad4a..57eeb75 100644 --- a/internal/web/static/i18n/id.json +++ b/internal/web/static/i18n/id.json @@ -1,7 +1,7 @@ { "lang.name": "Bahasa Indonesia", "nav.overview": "Ringkasan", -"nav.conv": "Percakapan", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "Jalur trafik", "nav.geo": "Geografi & jaringan", "nav.threats": "Intel ancaman", @@ -14,7 +14,6 @@ "self.per_sec": "{n} flow/dtk", "self.dropped": "Dibuang", "self.disk": "Disk", -"self.days": "cukup {n} hari", "top.search": "Cari IP, jaringan, port, negara (opsional)", "top.refresh": "Diperbarui tiap 30 dtk", "top.copy": "Salin tautan", @@ -66,11 +65,6 @@ "ov.bw_title": "Bandwidth per aplikasi", "ov.bw_sub_week": "Garis putus-putus: jam yang sama minggu lalu", "ov.bw_sub_prev": "Garis putus-putus: periode sebelumnya", -"ov.movers": "Siapa yang naik", -"ov.movers_sub_week": "Dibanding waktu yang sama minggu lalu", -"ov.movers_sub_prev": "Dibanding periode sebelumnya", -"ov.no_movers": "Tidak ada yang aneh", -"ov.mover_reason": "Terutama dengan {peer} di {port} ({cc})", "ov.mover_new": "baru", "ov.dir": "Arah trafik", "ov.proto": "Protokol", @@ -115,7 +109,7 @@ "col.status": "Status", "col.list": "Daftar", "col.direction": "Arah", -"conv.sub": "Siapa berbicara dengan siapa: klien, server, dan layanan, menurut trafik", "conv.slow": "rentang panjang membaca semua detail flow dan bisa lama; 1 jam paling cepat", +"conv.slow": "rentang panjang membaca semua detail flow dan bisa lama; 1 jam paling cepat", "topn.title": "Top {n}", "topn.rollup_note": "Rentang waktu panjang: data diambil dari ringkasan per jam, jadi filter tidak tersedia di sini.", "dim.client": "Klien", @@ -136,7 +130,7 @@ "geo.countries": "Negara lawan", "geo.countries_sub": "Trafik internal tidak dihitung", "geo.as": "Jaringan lawan (AS)", -"geo.no_table": "Tidak ada data. Negara dan jaringan memerlukan tabel ASN di direktori data (asn.tsv.gz).", +"geo.no_table": "Tidak ada data. Unggah basis data negara dan jaringan di halaman Sumber untuk melihat negara dan jaringan.", "thr.hosts": "Host yang berhubungan dengan alamat terdaftar", "thr.upload": "Dikirim ke alamat terdaftar", "thr.flows": "Flow dengan alamat terdaftar", @@ -211,14 +205,15 @@ "empty.nodata": "Tidak ada data pada periode ini.", "empty.first": "Belum ada yang diterima. Kirim sFlow ke UDP 6343, NetFlow ke 2055, atau IPFIX ke 4739 di mesin ini.", "err.load": "Gagal memuat: {e}", -"err.db_memory": "Basis data mencapai batas memorinya dan tidak bisa menjawab. Pilih rentang waktu yang lebih pendek, atau jalankan traffic66 dengan -memory yang lebih besar.", "err.db_busy": "Basis data belum bisa menjawab saat ini; penyebabnya ada di log traffic66. Halaman akan mencoba lagi dalam 30 detik.", +"err.db_memory": "Basis data mencapai batas memorinya dan tidak bisa menjawab. Pilih rentang waktu yang lebih pendek, atau jalankan traffic66 dengan -memory yang lebih besar.", +"err.db_busy": "Basis data belum bisa menjawab saat ini; penyebabnya ada di log traffic66. Halaman akan mencoba lagi dalam 30 detik.", "login.title": "Masuk", "login.user": "Pengguna", "login.password": "Kata sandi", "login.go": "Masuk", "login.bad": "Pengguna atau kata sandi salah", "demo.badge": "Demo", -"tui.help": "↑↓ pilih Enter aksi f hanya x kecualikan / cari t periode 1-9 halaman c hapus w peramban q keluar", +"tui.help": "↑↓ pilih Enter aksi f hanya x kecualikan / cari t periode 1-8 halaman c hapus w peramban q keluar", "tui.connecting": "Menghubungkan ke {url}…", "tui.error": "Tidak bisa menghubungi traffic66 di {url}: {e}", "tui.search": "Cari", @@ -227,5 +222,40 @@ "tui.opened": "Dibuka di peramban", "tui.open_failed": "Buka alamat ini: {url}", "tui.login": "Perlu masuk: jalankan dengan -user dan -password, atau jalankan di mesin traffic66", -"tui.close": "Esc tutup" +"tui.close": "Esc tutup", +"pop.detail": "Lihat detail", +"pop.name": "Beri nama…", +"pop.name_ph": "Nama, mis. PC Keuangan", +"pop.named": "Tersimpan; nama ini kini tampil di semua tempat", +"pop.actions": "Tindakan", +"topn.group": "Kelompokkan menurut", +"topn.hint": "Klik judul kolom biru untuk mengelompokkan menurut kolom itu, judul kolom angka untuk mengurutkan", +"topn.by_col": "Kelompokkan tabel menurut kolom ini", +"nav.detail": "Detail", +"det.peers": "Host yang diajak berkomunikasi", +"det.clients": "Host yang menggunakannya", +"det.conv": "Berkomunikasi dengan siapa", +"det.services": "Layanan dan port", +"det.servers": "Server", +"det.flows": "Catatan flow terbaru", +"self.free": "Tersedia", +"self.need": "Menyimpan detail {d} hari butuh sekitar {n} pada laju saat ini", +"self.need_later": "Menyimpan detail {d} hari: ruang yang dibutuhkan diperkirakan setelah ada data satu hari", +"geo.title": "Basis data negara dan jaringan", +"geo.sub": "Menampilkan negara dan jaringan (AS) tiap alamat jarak jauh", +"geo.none": "Belum ada basis data, jadi negara dan jaringan tampil sebagai Tidak diketahui. Unggah di bawah.", +"geo.upload": "Unggah file basis data…", +"geo.uploading": "Mengunggah dan memeriksa…", +"geo.installed": "Terpasang sebagai {f}. Berlaku untuk trafik baru mulai sekarang.", +"geo.where": "Diterima: file .mmdb (MaxMind GeoLite2 atau DB-IP Lite, negara atau ASN) dan tabel IP-to-ASN (.tsv atau .tsv.gz, seperti dari iptoasn.com). Unduh gratis: db-ip.com/db/lite.php dan iptoasn.com; GeoLite2 memerlukan akun MaxMind gratis. Trafik yang sudah tersimpan tetap memakai negara saat disimpan.", +"geo.kind_table": "Tabel IP-to-ASN", +"geo.kind_country": "Negara", +"geo.kind_asn": "Jaringan (AS)", +"geo.built": "dibuat {d}", +"geo.entries": "{n} rentang", +"src.names_help": "Paling mudah: klik alamat mana pun di halaman mana pun lalu pilih “Beri nama”. Anda juga bisa menulis satu entri per baris di sini, misalnya:", +"src.ex_host": "PC Keuangan", +"src.ex_net": "LAN Kantor", +"src.ex_device": "Router inti", +"src.ex_iface": "Uplink ISP" } diff --git a/internal/web/static/i18n/ja.json b/internal/web/static/i18n/ja.json index 05e69a0..daa2277 100644 --- a/internal/web/static/i18n/ja.json +++ b/internal/web/static/i18n/ja.json @@ -1,7 +1,7 @@ { "lang.name": "日本語", "nav.overview": "概要", -"nav.conv": "会話", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "フローの流れ", "nav.geo": "地域とネットワーク", "nav.threats": "脅威インテル", @@ -14,7 +14,6 @@ "self.per_sec": "{n} 件/秒", "self.dropped": "破棄", "self.disk": "ディスク", -"self.days": "あと {n} 日分", "top.search": "IP・ネットワーク・ポート・国で検索(任意)", "top.refresh": "30 秒ごとに更新", "top.copy": "リンクをコピー", @@ -66,11 +65,6 @@ "ov.bw_title": "アプリケーション別の帯域", "ov.bw_sub_week": "破線:先週の同時刻", "ov.bw_sub_prev": "破線:前の期間", -"ov.movers": "いつもより増えたホスト", -"ov.movers_sub_week": "先週の同じ時間帯と比較", -"ov.movers_sub_prev": "前の期間と比較", -"ov.no_movers": "目立った変化はありません", -"ov.mover_reason": "主に {peer} との {port}({cc})", "ov.mover_new": "新規", "ov.dir": "トラフィックの方向", "ov.proto": "プロトコル", @@ -115,7 +109,7 @@ "col.status": "状態", "col.list": "リスト", "col.direction": "方向", -"conv.sub": "誰が誰と通信しているか:クライアント、サーバー、サービスを通信量順に", "conv.slow": "長い期間はすべての明細を読むため時間がかかることがあります。1 時間が最速です", +"conv.slow": "長い期間はすべての明細を読むため時間がかかることがあります。1 時間が最速です", "topn.title": "上位 {n}", "topn.rollup_note": "期間が長いため 1 時間ごとの集計から表示しています。ここでは絞り込みを使えません。", "dim.client": "クライアント", @@ -136,7 +130,7 @@ "geo.countries": "相手の国", "geo.countries_sub": "内部トラフィックは除外", "geo.as": "相手のネットワーク(AS)", -"geo.no_table": "データがありません。国とネットワークを表示するには、データディレクトリに ASN テーブル(asn.tsv.gz)が必要です。", +"geo.no_table": "データがありません。国とネットワークを表示するには、「受信状況」ページで国・ネットワークデータベースをアップロードしてください。", "thr.hosts": "リスト掲載アドレスと通信したホスト", "thr.upload": "リスト掲載アドレスへの送信量", "thr.flows": "リスト掲載アドレスが関わるフロー", @@ -211,14 +205,15 @@ "empty.nodata": "この期間のデータはありません。", "empty.first": "まだ何も受信していません。このマシンの UDP 6343 に sFlow、2055 に NetFlow、4739 に IPFIX を送ってください。", "err.load": "読み込めませんでした:{e}", -"err.db_memory": "データベースがメモリ上限に達したため、応答できませんでした。期間を短くするか、より大きな -memory で traffic66 を起動してください。", "err.db_busy": "データベースが今は応答できませんでした。理由は traffic66 のログに記録されています。30 秒後に自動で再試行します。", +"err.db_memory": "データベースがメモリ上限に達したため、応答できませんでした。期間を短くするか、より大きな -memory で traffic66 を起動してください。", +"err.db_busy": "データベースが今は応答できませんでした。理由は traffic66 のログに記録されています。30 秒後に自動で再試行します。", "login.title": "ログイン", "login.user": "ユーザー名", "login.password": "パスワード", "login.go": "ログイン", "login.bad": "ユーザー名またはパスワードが違います", "demo.badge": "デモ", -"tui.help": "↑↓ 選択 Enter 操作 f のみ x 除外 / 検索 t 期間 1-9 ページ c 解除 w ブラウザ q 終了", +"tui.help": "↑↓ 選択 Enter 操作 f のみ x 除外 / 検索 t 期間 1-8 ページ c 解除 w ブラウザ q 終了", "tui.connecting": "{url} に接続中…", "tui.error": "{url} の traffic66 に接続できません:{e}", "tui.search": "検索", @@ -227,5 +222,40 @@ "tui.opened": "ブラウザで開きました", "tui.open_failed": "このアドレスを開いてください:{url}", "tui.login": "ログインが必要です:-user と -password を付けて起動するか、traffic66 が動いているマシンで実行してください", -"tui.close": "Esc 閉じる" +"tui.close": "Esc 閉じる", +"pop.detail": "詳細を表示", +"pop.name": "名前を付ける…", +"pop.name_ph": "名前(例:経理PC)", +"pop.named": "保存しました。この名前がすべての画面に表示されます", +"pop.actions": "操作", +"topn.group": "グループ化", +"topn.hint": "青い列見出しをクリックするとその列でグループ化、数値の列見出しをクリックすると並べ替えます", +"topn.by_col": "この列で表をグループ化", +"nav.detail": "詳細", +"det.peers": "通信相手のホスト", +"det.clients": "利用しているホスト", +"det.conv": "通信相手", +"det.services": "サービスとポート", +"det.servers": "サーバー", +"det.flows": "最新のフローレコード", +"self.free": "空き", +"self.need": "現在のペースで {d} 日分の明細を保持するには約 {n} 必要です", +"self.need_later": "{d} 日分の明細の保持:必要な容量は 1 日分のデータがたまってから見積もります", +"geo.title": "国・ネットワークデータベース", +"geo.sub": "各外部アドレスの国とネットワーク(AS)を表示します", +"geo.none": "データベースがまだないため、国とネットワークは「不明」と表示されます。下からアップロードしてください。", +"geo.upload": "データベースファイルをアップロード…", +"geo.uploading": "アップロードして確認中…", +"geo.installed": "{f} としてインストールしました。今後の新しいトラフィックに適用されます。", +"geo.where": "対応形式:.mmdb ファイル(MaxMind GeoLite2 または DB-IP Lite の国または ASN 版)と IP-to-ASN テーブル(iptoasn.com などの .tsv / .tsv.gz)。無料ダウンロード:db-ip.com/db/lite.php と iptoasn.com。GeoLite2 には無料の MaxMind アカウントが必要です。保存済みのトラフィックは保存時の国のままです。", +"geo.kind_table": "IP-to-ASN テーブル", +"geo.kind_country": "国", +"geo.kind_asn": "ネットワーク(AS)", +"geo.built": "{d} 作成", +"geo.entries": "{n} 範囲", +"src.names_help": "いちばん簡単なのは、どのページでもアドレスをクリックして「名前を付ける」を選ぶ方法です。ここに 1 行 1 件で書くこともできます。例:", +"src.ex_host": "経理PC", +"src.ex_net": "社内LAN", +"src.ex_device": "コアルーター", +"src.ex_iface": "ISP上位回線" } diff --git a/internal/web/static/i18n/ko.json b/internal/web/static/i18n/ko.json index 8e36676..479ec38 100644 --- a/internal/web/static/i18n/ko.json +++ b/internal/web/static/i18n/ko.json @@ -1,67 +1,200 @@ { "lang.name": "한국어", -"nav.overview": "개요", "nav.conv": "대화", "nav.topn": "Top-N", "nav.sankey": "트래픽 경로", "nav.geo": "지역 및 네트워크", -"nav.threats": "위협 정보", "nav.records": "플로 레코드", "nav.ifaces": "인터페이스 대조", "nav.sources": "수집 상태", -"self.receiving": "수신 중", "self.idle": "들어오는 데이터 없음", "self.ingest": "수집", "self.per_sec": "초당 {n}개 플로", -"self.dropped": "누락", "self.disk": "디스크", "self.days": "{n}일 남음", -"top.search": "IP, 네트워크, 포트, 국가 검색 (선택)", "top.refresh": "30초마다 새로고침", "top.copy": "링크 복사", -"top.copied": "링크를 복사했습니다", "top.logout": "로그아웃", -"range.15m": "15분", "range.1h": "1시간", "range.6h": "6시간", "range.24h": "24시간", "range.7d": "7일", "range.30d": "30일", -"filters.hint": "화면의 값을 클릭하면 필터링됩니다 — 입력할 필요가 없습니다", "filters.only": "만 보기", "filters.not": "제외", -"filters.clear": "모두 지우기", "filters.remove": "이 필터 삭제", -"field.ip": "호스트", "field.client": "클라이언트", "field.server": "서버", "field.port": "포트", "field.app": "애플리케이션", -"field.country": "국가", "field.asn": "네트워크(AS)", "field.proto": "프로토콜", "field.dir": "방향", -"field.encap": "캡슐화", "field.exporter": "장비", "field.segment": "세그먼트", "field.threat": "위협 목록", -"field.vlan": "VLAN", "field.iface": "인터페이스", -"pop.only": "이것만 보기", "pop.not": "이것 제외", "pop.records": "플로 레코드 보기", "pop.lookup": "온라인에서 조회 ↗", "pop.copy": "복사", -"kpi.now": "현재 대역폭", "kpi.vs_week": "지난주 같은 시간 대비 {p}", "kpi.vs_prev": "이전 기간 대비 {p}", -"kpi.total": "{r} 동안의 트래픽", "kpi.peak": "최대 {v} ({t})", "kpi.hosts": "활성 내부 호스트", "kpi.peers": "외부 호스트", -"kpi.countries": "{n}개 국가", "kpi.accuracy": "장비 카운터와의 일치도", "kpi.trust": "수치 검증됨", -"kpi.no_counters": "아직 비교할 인터페이스 카운터가 없습니다", "kpi.check": "인터페이스 확인", -"ov.bw_title": "애플리케이션별 대역폭", "ov.bw_sub_week": "점선: 지난주 같은 시간", "ov.bw_sub_prev": "점선: 이전 기간", -"ov.movers": "평소보다 늘어난 곳", "ov.movers_sub_week": "지난주 같은 시간 대비", "ov.movers_sub_prev": "이전 기간 대비", -"ov.no_movers": "특이 사항 없음", "ov.mover_reason": "주로 {peer}의 {port}와 통신 ({cc})", "ov.mover_new": "신규", -"ov.dir": "트래픽 방향", "ov.proto": "프로토콜", "ov.country": "상대 위치", -"ov.top_clients": "상위 클라이언트", "ov.top_clients_sub": "연결을 시작한 쪽", -"ov.top_services": "상위 서비스", "ov.top_services_sub": "서버와 포트", -"dir.1": "아웃바운드", "dir.2": "인바운드", "dir.3": "내부", "dir.4": "통과", -"other": "기타", "internal": "내부 네트워크", "unknown": "알 수 없음", "total": "합계", -"last_week": "지난주 같은 시간", "prev_period": "이전 기간", -"col.client": "클라이언트", "col.server": "서버", "col.service": "서비스", "col.port": "포트", "col.app": "애플리케이션", -"col.traffic": "트래픽", "col.share": "비율", "col.vs_week": "지난주 대비", "col.vs_prev": "이전 대비", "col.peers": "상대 수", -"col.flows": "플로 수", "col.country": "국가", "col.asn": "AS", "col.org": "네트워크", "col.segment": "세그먼트", -"col.device": "장비", "col.encap": "캡슐화", "col.vlan": "VLAN", "col.proto": "프로토콜", "col.rate": "초당 플로", -"col.sampling": "샘플링", "col.lost": "손실", "col.last": "마지막 수신", "col.status": "상태", "col.list": "목록", "col.direction": "방향", -"conv.sub": "누가 누구와 통신하는지: 트래픽 순 클라이언트, 서버, 서비스", "conv.slow": "긴 기간은 모든 플로 상세를 읽어 시간이 걸릴 수 있습니다. 1시간이 가장 빠릅니다", -"topn.title": "Top {n}", "topn.rollup_note": "기간이 길어 시간별 요약에서 가져오므로 여기서는 필터를 쓸 수 없습니다.", -"dim.client": "클라이언트", "dim.server": "서버", "dim.conv": "대화", "dim.app": "애플리케이션", "dim.port": "포트", -"dim.country": "국가", "dim.asn": "네트워크(AS)", "dim.segment": "세그먼트", "dim.exporter": "장비", "dim.encap": "캡슐화", "dim.vlan": "VLAN", -"encap.none": "없음", "vlan.none": "태그 없음", -"sankey.title": "세그먼트 → 애플리케이션 → 상대 국가", "sankey.hint": "노드를 클릭하면 필터링됩니다", -"geo.countries": "상대 국가", "geo.countries_sub": "내부 트래픽은 제외", "geo.as": "상대 네트워크(AS)", -"geo.no_table": "데이터가 없습니다. 국가와 네트워크를 보려면 데이터 디렉터리에 ASN 테이블(asn.tsv.gz)이 필요합니다.", -"thr.hosts": "목록의 주소와 통신한 호스트", "thr.upload": "목록의 주소로 보낸 양", "thr.flows": "목록의 주소가 포함된 플로", -"thr.lists": "불러온 위협 목록", "thr.entries": "{n}개 항목", "thr.list": "위협 목록", "thr.table": "일치 항목", -"thr.sub": "빨간색: 내부 호스트가 목록의 주소로 데이터를 보냄", "thr.none": "이 기간에는 일치 항목이 없습니다", +"nav.overview": "개요", +"nav.topn": "Top-N", +"nav.sankey": "트래픽 경로", +"nav.geo": "지역 및 네트워크", +"nav.threats": "위협 정보", +"nav.records": "플로 레코드", +"nav.ifaces": "인터페이스 대조", +"nav.sources": "수집 상태", +"self.receiving": "수신 중", +"self.idle": "들어오는 데이터 없음", +"self.ingest": "수집", +"self.per_sec": "초당 {n}개 플로", +"self.dropped": "누락", +"self.disk": "디스크", +"top.search": "IP, 네트워크, 포트, 국가 검색 (선택)", +"top.refresh": "30초마다 새로고침", +"top.copy": "링크 복사", +"top.copied": "링크를 복사했습니다", +"top.logout": "로그아웃", +"range.15m": "15분", +"range.1h": "1시간", +"range.6h": "6시간", +"range.24h": "24시간", +"range.7d": "7일", +"range.30d": "30일", +"filters.hint": "화면의 값을 클릭하면 필터링됩니다 — 입력할 필요가 없습니다", +"filters.only": "만 보기", +"filters.not": "제외", +"filters.clear": "모두 지우기", +"filters.remove": "이 필터 삭제", +"field.ip": "호스트", +"field.client": "클라이언트", +"field.server": "서버", +"field.port": "포트", +"field.app": "애플리케이션", +"field.country": "국가", +"field.asn": "네트워크(AS)", +"field.proto": "프로토콜", +"field.dir": "방향", +"field.encap": "캡슐화", +"field.exporter": "장비", +"field.segment": "세그먼트", +"field.threat": "위협 목록", +"field.vlan": "VLAN", +"field.iface": "인터페이스", +"pop.only": "이것만 보기", +"pop.not": "이것 제외", +"pop.records": "플로 레코드 보기", +"pop.lookup": "온라인에서 조회 ↗", +"pop.copy": "복사", +"kpi.now": "현재 대역폭", +"kpi.vs_week": "지난주 같은 시간 대비 {p}", +"kpi.vs_prev": "이전 기간 대비 {p}", +"kpi.total": "{r} 동안의 트래픽", +"kpi.peak": "최대 {v} ({t})", +"kpi.hosts": "활성 내부 호스트", +"kpi.peers": "외부 호스트", +"kpi.countries": "{n}개 국가", +"kpi.accuracy": "장비 카운터와의 일치도", +"kpi.trust": "수치 검증됨", +"kpi.no_counters": "아직 비교할 인터페이스 카운터가 없습니다", +"kpi.check": "인터페이스 확인", +"ov.bw_title": "애플리케이션별 대역폭", +"ov.bw_sub_week": "점선: 지난주 같은 시간", +"ov.bw_sub_prev": "점선: 이전 기간", +"ov.mover_new": "신규", +"ov.dir": "트래픽 방향", +"ov.proto": "프로토콜", +"ov.country": "상대 위치", +"ov.top_clients": "상위 클라이언트", +"ov.top_clients_sub": "연결을 시작한 쪽", +"ov.top_services": "상위 서비스", +"ov.top_services_sub": "서버와 포트", +"dir.1": "아웃바운드", +"dir.2": "인바운드", +"dir.3": "내부", +"dir.4": "통과", +"other": "기타", +"internal": "내부 네트워크", +"unknown": "알 수 없음", +"total": "합계", +"last_week": "지난주 같은 시간", +"prev_period": "이전 기간", +"col.client": "클라이언트", +"col.server": "서버", +"col.service": "서비스", +"col.port": "포트", +"col.app": "애플리케이션", +"col.traffic": "트래픽", +"col.share": "비율", +"col.vs_week": "지난주 대비", +"col.vs_prev": "이전 대비", +"col.peers": "상대 수", +"col.flows": "플로 수", +"col.country": "국가", +"col.asn": "AS", +"col.org": "네트워크", +"col.segment": "세그먼트", +"col.device": "장비", +"col.encap": "캡슐화", +"col.vlan": "VLAN", +"col.proto": "프로토콜", +"col.rate": "초당 플로", +"col.sampling": "샘플링", +"col.lost": "손실", +"col.last": "마지막 수신", +"col.status": "상태", +"col.list": "목록", +"col.direction": "방향", +"conv.slow": "긴 기간은 모든 플로 상세를 읽어 시간이 걸릴 수 있습니다. 1시간이 가장 빠릅니다", +"topn.title": "Top {n}", +"topn.rollup_note": "기간이 길어 시간별 요약에서 가져오므로 여기서는 필터를 쓸 수 없습니다.", +"dim.client": "클라이언트", +"dim.server": "서버", +"dim.conv": "대화", +"dim.app": "애플리케이션", +"dim.port": "포트", +"dim.country": "국가", +"dim.asn": "네트워크(AS)", +"dim.segment": "세그먼트", +"dim.exporter": "장비", +"dim.encap": "캡슐화", +"dim.vlan": "VLAN", +"encap.none": "없음", +"vlan.none": "태그 없음", +"sankey.title": "세그먼트 → 애플리케이션 → 상대 국가", +"sankey.hint": "노드를 클릭하면 필터링됩니다", +"geo.countries": "상대 국가", +"geo.countries_sub": "내부 트래픽은 제외", +"geo.as": "상대 네트워크(AS)", +"geo.no_table": "데이터가 없습니다. 국가와 네트워크를 보려면 '수집 상태' 페이지에서 국가 및 네트워크 데이터베이스를 업로드하세요.", +"thr.hosts": "목록의 주소와 통신한 호스트", +"thr.upload": "목록의 주소로 보낸 양", +"thr.flows": "목록의 주소가 포함된 플로", +"thr.lists": "불러온 위협 목록", +"thr.entries": "{n}개 항목", +"thr.list": "위협 목록", +"thr.table": "일치 항목", +"thr.sub": "빨간색: 내부 호스트가 목록의 주소로 데이터를 보냄", +"thr.none": "이 기간에는 일치 항목이 없습니다", "thr.no_lists": "불러온 위협 목록이 없습니다. data/threats/.txt에 한 줄에 하나씩 주소나 네트워크를 적고 다시 시작하세요.", -"rec.title": "플로 레코드", "rec.latest": "최근 {n}건", "rec.columns": "열", -"rc.time": "시간", "rc.client": "클라이언트", "rc.cport": "클라이언트 포트", "rc.server": "서버", "rc.port": "포트", "rc.proto": "프로토콜", -"rc.app": "애플리케이션", "rc.country": "국가", "rc.encap": "캡슐화", "rc.traffic": "트래픽", "rc.packets": "패킷", -"rc.device": "장비", "rc.in_if": "입력 인터페이스", "rc.out_if": "출력 인터페이스", "rc.vlan": "VLAN", "rc.flags": "TCP 플래그", -"rc.sampling": "샘플링", "rc.asn": "AS", "rc.direction": "방향", "rc.list": "위협 목록", -"if.title": "인터페이스", "if.sub": "문제가 있는 것부터", "if.flow": "플로 추정치", "if.counter": "장비 카운터", -"if.dev": "편차 {v}", "if.stat": "통계 오차 ±{v}", "if.in": "인바운드", "if.out": "아웃바운드", "if.no_ctr_short": "카운터 없음", +"rec.title": "플로 레코드", +"rec.latest": "최근 {n}건", +"rec.columns": "열", +"rc.time": "시간", +"rc.client": "클라이언트", +"rc.cport": "클라이언트 포트", +"rc.server": "서버", +"rc.port": "포트", +"rc.proto": "프로토콜", +"rc.app": "애플리케이션", +"rc.country": "국가", +"rc.encap": "캡슐화", +"rc.traffic": "트래픽", +"rc.packets": "패킷", +"rc.device": "장비", +"rc.in_if": "입력 인터페이스", +"rc.out_if": "출력 인터페이스", +"rc.vlan": "VLAN", +"rc.flags": "TCP 플래그", +"rc.sampling": "샘플링", +"rc.asn": "AS", +"rc.direction": "방향", +"rc.list": "위협 목록", +"if.title": "인터페이스", +"if.sub": "문제가 있는 것부터", +"if.flow": "플로 추정치", +"if.counter": "장비 카운터", +"if.dev": "편차 {v}", +"if.stat": "통계 오차 ±{v}", +"if.in": "인바운드", +"if.out": "아웃바운드", +"if.no_ctr_short": "카운터 없음", "if.no_counters": "이 장비는 인터페이스 카운터를 보내지 않아 비교할 대상이 없습니다. 수집 상태 → 이름에 snmp 10.0.0.1 public 같은 줄을 추가하면 SNMP로 읽어 옵니다.", "if.ok": "일치합니다. 플로 추정치와 장비 카운터의 차이는 {dev}로, 샘플링 통계 오차({err}) 이내입니다.", "if.low": "플로 추정치가 장비 카운터보다 {dev} 낮습니다. 가능한 원인: {causes}.", "if.high": "플로 추정치가 장비 카운터보다 {dev} 높습니다. 가능한 원인: {causes}.", -"cause.loss": "이 장비가 보낸 export 패킷의 {p}가 도중에 손실됨", "cause.sampling": "샘플링 비율을 아직 모름", -"cause.unsampled_iface": "이 트래픽이 지나는 인터페이스 중 일부가 샘플링되지 않음", "cause.dup": "같은 트래픽이 여러 인터페이스에서 집계됨", -"src.title": "수집 상태", "src.sub": "각 장비가 보내고 있는지, 해석되는지, 샘플링 비율이 맞는지", -"src.listeners": "수신 포트", "src.names": "이름", "src.names_sub": "선택 사항. 네트워크, 장비, 인터페이스, 호스트의 이름을 한 줄에 하나씩 적습니다.", -"src.save": "저장", "src.saved": "저장했습니다", "src.effective": "실측 {v}", -"src.snmp": "SNMP", "src.snmp_ok": "인터페이스 {n}개", "src.snmp_fail": "응답 없음", -"st.ok": "수신 중", "st.warn": "확인 필요", "st.bad": "수신 안 됨", +"cause.loss": "이 장비가 보낸 export 패킷의 {p}가 도중에 손실됨", +"cause.sampling": "샘플링 비율을 아직 모름", +"cause.unsampled_iface": "이 트래픽이 지나는 인터페이스 중 일부가 샘플링되지 않음", +"cause.dup": "같은 트래픽이 여러 인터페이스에서 집계됨", +"src.title": "수집 상태", +"src.sub": "각 장비가 보내고 있는지, 해석되는지, 샘플링 비율이 맞는지", +"src.listeners": "수신 포트", +"src.names": "이름", +"src.names_sub": "선택 사항. 네트워크, 장비, 인터페이스, 호스트의 이름을 한 줄에 하나씩 적습니다.", +"src.save": "저장", +"src.saved": "저장했습니다", +"src.effective": "실측 {v}", +"src.snmp": "SNMP", +"src.snmp_ok": "인터페이스 {n}개", +"src.snmp_fail": "응답 없음", +"st.ok": "수신 중", +"st.warn": "확인 필요", +"st.bad": "수신 안 됨", "issue.silent": "2분 넘게 아무것도 받지 못했습니다. 장비의 export 대상 설정과 방화벽에서 UDP가 허용되는지 확인하세요.", "issue.waiting_sampling": "템플릿은 받았지만 샘플링 비율이 아직 없습니다. 레코드 {n}건을 보류 중이며, 비율이 도착하면 1:1이 아니라 올바른 배율로 집계합니다. 장비는 보통 몇 분 안에 다시 보냅니다.", "issue.assumed_unsampled": "이 장비는 샘플링 비율을 한 번도 알리지 않아 레코드를 1:1로 집계하고 있습니다. 샘플링을 한다면 sampler options를 export하도록 설정하거나, 이름에서 unsampled로 표시하세요.", @@ -69,13 +202,60 @@ "issue.no_template": "데이터는 오지만 템플릿이 아직 오지 않았습니다. 보류해 두었다가 템플릿이 도착하는 즉시 해석합니다.", "issue.clock_skew": "장비 시계가 {s} 어긋나 있습니다. 시간을 수신 시각으로 보정했습니다.", "issue.decode_errors": "일부 데이터그램을 해석하지 못했습니다. 장비가 올바른 포트와 프로토콜로 보내는지 확인하세요.", -"empty.nodata": "이 기간에는 데이터가 없습니다.", "empty.first": "아직 받은 것이 없습니다. 이 장비의 UDP 6343으로 sFlow, 2055로 NetFlow, 4739로 IPFIX를 보내세요.", +"empty.nodata": "이 기간에는 데이터가 없습니다.", +"empty.first": "아직 받은 것이 없습니다. 이 장비의 UDP 6343으로 sFlow, 2055로 NetFlow, 4739로 IPFIX를 보내세요.", "err.load": "불러오지 못했습니다: {e}", -"err.db_memory": "데이터베이스가 메모리 한도에 도달해 응답하지 못했습니다. 기간을 줄이거나 더 큰 -memory로 traffic66을 시작하세요.", "err.db_busy": "데이터베이스가 지금은 응답하지 못했습니다. 원인은 traffic66 로그에 있습니다. 30초 후에 다시 시도합니다.", -"login.title": "로그인", "login.user": "사용자", "login.password": "비밀번호", "login.go": "로그인", "login.bad": "사용자 또는 비밀번호가 틀립니다", +"err.db_memory": "데이터베이스가 메모리 한도에 도달해 응답하지 못했습니다. 기간을 줄이거나 더 큰 -memory로 traffic66을 시작하세요.", +"err.db_busy": "데이터베이스가 지금은 응답하지 못했습니다. 원인은 traffic66 로그에 있습니다. 30초 후에 다시 시도합니다.", +"login.title": "로그인", +"login.user": "사용자", +"login.password": "비밀번호", +"login.go": "로그인", +"login.bad": "사용자 또는 비밀번호가 틀립니다", "demo.badge": "데모", -"tui.help": "↑↓ 선택 Enter 동작 f 만 보기 x 제외 / 검색 t 시간 1-9 페이지 c 지우기 w 브라우저 q 종료", -"tui.connecting": "{url}에 연결 중…", "tui.error": "{url}의 traffic66에 연결할 수 없습니다: {e}", "tui.search": "검색", -"tui.updated": "{t} 갱신", "tui.nothing": "표시할 항목 없음", "tui.opened": "브라우저에서 열었습니다", "tui.open_failed": "이 주소를 여세요: {url}", -"tui.login": "로그인이 필요합니다: -user와 -password를 지정하거나 traffic66이 실행 중인 머신에서 실행하세요", "tui.close": "Esc 닫기" +"tui.help": "↑↓ 선택 Enter 동작 f 만 보기 x 제외 / 검색 t 시간 1-8 페이지 c 지우기 w 브라우저 q 종료", +"tui.connecting": "{url}에 연결 중…", +"tui.error": "{url}의 traffic66에 연결할 수 없습니다: {e}", +"tui.search": "검색", +"tui.updated": "{t} 갱신", +"tui.nothing": "표시할 항목 없음", +"tui.opened": "브라우저에서 열었습니다", +"tui.open_failed": "이 주소를 여세요: {url}", +"tui.login": "로그인이 필요합니다: -user와 -password를 지정하거나 traffic66이 실행 중인 머신에서 실행하세요", +"tui.close": "Esc 닫기", +"pop.detail": "상세 보기", +"pop.name": "이름 붙이기…", +"pop.name_ph": "이름, 예: 재무팀 PC", +"pop.named": "저장했습니다. 이제 모든 화면에 이 이름이 표시됩니다", +"pop.actions": "작업", +"topn.group": "그룹 기준", +"topn.hint": "파란색 열 제목을 클릭하면 그 열로 그룹화하고, 숫자 열 제목을 클릭하면 정렬합니다", +"topn.by_col": "이 열로 표 그룹화", +"nav.detail": "상세", +"det.peers": "통신하는 호스트", +"det.clients": "이를 사용하는 호스트", +"det.conv": "통신 상대", +"det.services": "서비스와 포트", +"det.servers": "서버", +"det.flows": "최근 플로 레코드", +"self.free": "여유", +"self.need": "현재 속도로 {d}일치 상세 데이터를 보관하려면 약 {n}이 필요합니다", +"self.need_later": "{d}일치 상세 데이터 보관: 하루치 데이터가 쌓이면 필요한 공간을 추정합니다", +"geo.title": "국가 및 네트워크 데이터베이스", +"geo.sub": "각 외부 주소의 국가와 네트워크(AS)를 표시합니다", +"geo.none": "아직 데이터베이스가 없어 국가와 네트워크가 '알 수 없음'으로 표시됩니다. 아래에서 업로드하세요.", +"geo.upload": "데이터베이스 파일 업로드…", +"geo.uploading": "업로드 및 확인 중…", +"geo.installed": "{f}(으)로 설치했습니다. 지금부터 새 트래픽에 적용됩니다.", +"geo.where": "지원 형식: .mmdb 파일(MaxMind GeoLite2 또는 DB-IP Lite, 국가 또는 ASN)과 IP-to-ASN 테이블(.tsv 또는 .tsv.gz, iptoasn.com 제공 형식). 무료 다운로드: db-ip.com/db/lite.php 및 iptoasn.com. GeoLite2는 무료 MaxMind 계정이 필요합니다. 이미 저장된 트래픽은 저장 당시의 국가를 유지합니다.", +"geo.kind_table": "IP-to-ASN 테이블", +"geo.kind_country": "국가", +"geo.kind_asn": "네트워크(AS)", +"geo.built": "{d} 생성", +"geo.entries": "{n}개 대역", +"src.names_help": "가장 쉬운 방법: 아무 페이지에서나 주소를 클릭하고 '이름 붙이기'를 선택하세요. 여기에 한 줄에 하나씩 적을 수도 있습니다. 예:", +"src.ex_host": "재무팀 PC", +"src.ex_net": "사무실 LAN", +"src.ex_device": "코어 라우터", +"src.ex_iface": "ISP 업링크" } diff --git a/internal/web/static/i18n/pt.json b/internal/web/static/i18n/pt.json index 942290c..687ed04 100644 --- a/internal/web/static/i18n/pt.json +++ b/internal/web/static/i18n/pt.json @@ -1,7 +1,7 @@ { "lang.name": "Português", "nav.overview": "Visão geral", -"nav.conv": "Conversas", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "Caminhos do tráfego", "nav.geo": "Geografia e redes", "nav.threats": "Ameaças", @@ -14,7 +14,6 @@ "self.per_sec": "{n} fluxos/s", "self.dropped": "Descartados", "self.disk": "Disco", -"self.days": "restam {n} dias", "top.search": "Buscar IP, rede, porta, país (opcional)", "top.refresh": "Atualiza a cada 30 s", "top.copy": "Copiar link", @@ -66,11 +65,6 @@ "ov.bw_title": "Banda por aplicação", "ov.bw_sub_week": "Linha tracejada: mesmo horário na semana passada", "ov.bw_sub_prev": "Linha tracejada: período anterior", -"ov.movers": "Quem cresceu", -"ov.movers_sub_week": "Comparado com o mesmo horário da semana passada", -"ov.movers_sub_prev": "Comparado com o período anterior", -"ov.no_movers": "Nada fora do normal", -"ov.mover_reason": "Principalmente com {peer} em {port} ({cc})", "ov.mover_new": "novo", "ov.dir": "Direção do tráfego", "ov.proto": "Protocolo", @@ -115,7 +109,7 @@ "col.status": "Situação", "col.list": "Lista", "col.direction": "Direção", -"conv.sub": "Quem fala com quem: cliente, servidor e serviço, por tráfego", "conv.slow": "períodos longos leem todo o detalhe e podem demorar; 1 hora é o mais rápido", +"conv.slow": "períodos longos leem todo o detalhe e podem demorar; 1 hora é o mais rápido", "topn.title": "Top {n}", "topn.rollup_note": "Período longo: os dados vêm de resumos por hora, por isso não é possível filtrar aqui.", "dim.client": "Clientes", @@ -136,7 +130,7 @@ "geo.countries": "Países remotos", "geo.countries_sub": "O tráfego interno foi excluído", "geo.as": "Redes remotas (AS)", -"geo.no_table": "Sem dados. Países e redes precisam de uma tabela ASN no diretório de dados (asn.tsv.gz).", +"geo.no_table": "Sem dados. Envie um banco de dados de países e redes na página Fontes para ver países e redes.", "thr.hosts": "Hosts que falaram com endereços listados", "thr.upload": "Enviado para endereços listados", "thr.flows": "Fluxos com endereços listados", @@ -211,14 +205,15 @@ "empty.nodata": "Não há dados neste período.", "empty.first": "Nada recebido ainda. Envie sFlow para a porta UDP 6343, NetFlow para 2055 ou IPFIX para 4739 desta máquina.", "err.load": "Não foi possível carregar: {e}", -"err.db_memory": "O banco de dados atingiu o limite de memória e não conseguiu responder. Escolha um período menor ou inicie o traffic66 com um -memory maior.", "err.db_busy": "O banco de dados não conseguiu responder agora; o motivo está no log do traffic66. A página tentará de novo em 30 segundos.", +"err.db_memory": "O banco de dados atingiu o limite de memória e não conseguiu responder. Escolha um período menor ou inicie o traffic66 com um -memory maior.", +"err.db_busy": "O banco de dados não conseguiu responder agora; o motivo está no log do traffic66. A página tentará de novo em 30 segundos.", "login.title": "Entrar", "login.user": "Usuário", "login.password": "Senha", "login.go": "Entrar", "login.bad": "Usuário ou senha incorretos", "demo.badge": "Demo", -"tui.help": "↑↓ escolher Enter ações f somente x excluir / buscar t período 1-9 páginas c limpar w navegador q sair", +"tui.help": "↑↓ escolher Enter ações f somente x excluir / buscar t período 1-8 páginas c limpar w navegador q sair", "tui.connecting": "Conectando a {url}…", "tui.error": "Não foi possível acessar o traffic66 em {url}: {e}", "tui.search": "Buscar", @@ -227,5 +222,40 @@ "tui.opened": "Aberto no navegador", "tui.open_failed": "Abra este endereço: {url}", "tui.login": "É preciso entrar: inicie com -user e -password, ou execute na máquina do traffic66", -"tui.close": "Esc fechar" +"tui.close": "Esc fechar", +"pop.detail": "Ver detalhes", +"pop.name": "Dar um nome…", +"pop.name_ph": "Nome, ex.: PC do Financeiro", +"pop.named": "Salvo; o nome agora aparece em todo lugar", +"pop.actions": "Ações", +"topn.group": "Agrupar por", +"topn.hint": "Clique em um título de coluna azul para agrupar por ela, ou em um título numérico para ordenar", +"topn.by_col": "Agrupar a tabela por esta coluna", +"nav.detail": "Detalhes", +"det.peers": "Hosts com quem conversa", +"det.clients": "Hosts que o usam", +"det.conv": "Com quem conversa", +"det.services": "Serviços e portas", +"det.servers": "Servidores", +"det.flows": "Últimos registros de fluxo", +"self.free": "Livre", +"self.need": "Manter {d} dias de detalhe exige cerca de {n} no ritmo atual", +"self.need_later": "Manter {d} dias de detalhe: o espaço necessário será estimado quando houver um dia de dados", +"geo.title": "Banco de dados de países e redes", +"geo.sub": "Mostra o país e a rede (AS) de cada endereço remoto", +"geo.none": "Ainda não há banco de dados, então países e redes aparecem como Desconhecido. Envie um abaixo.", +"geo.upload": "Enviar arquivo de banco de dados…", +"geo.uploading": "Enviando e verificando…", +"geo.installed": "Instalado como {f}. Vale para o tráfego novo a partir de agora.", +"geo.where": "Aceitos: arquivos .mmdb (MaxMind GeoLite2 ou DB-IP Lite, de países ou ASN) e tabelas IP-to-ASN (.tsv ou .tsv.gz, como as do iptoasn.com). Downloads gratuitos: db-ip.com/db/lite.php e iptoasn.com; o GeoLite2 exige uma conta gratuita da MaxMind. O tráfego já armazenado mantém o país com que foi salvo.", +"geo.kind_table": "Tabela IP-to-ASN", +"geo.kind_country": "Países", +"geo.kind_asn": "Redes (AS)", +"geo.built": "gerado em {d}", +"geo.entries": "{n} faixas", +"src.names_help": "O mais fácil: clique em qualquer endereço em qualquer página e escolha “Dar um nome”. Você também pode escrever aqui uma entrada por linha, por exemplo:", +"src.ex_host": "PC do Financeiro", +"src.ex_net": "Rede do escritório", +"src.ex_device": "Roteador de núcleo", +"src.ex_iface": "Link da operadora" } diff --git a/internal/web/static/i18n/ru.json b/internal/web/static/i18n/ru.json index a38f4fb..70cbf7f 100644 --- a/internal/web/static/i18n/ru.json +++ b/internal/web/static/i18n/ru.json @@ -1,7 +1,7 @@ { "lang.name": "Русский", "nav.overview": "Обзор", -"nav.conv": "Сеансы", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "Пути трафика", "nav.geo": "География и сети", "nav.threats": "Угрозы", @@ -14,7 +14,6 @@ "self.per_sec": "{n} потоков/с", "self.dropped": "Отброшено", "self.disk": "Диск", -"self.days": "хватит на {n} дн.", "top.search": "Поиск по IP, сети, порту, стране (необязательно)", "top.refresh": "Обновление каждые 30 с", "top.copy": "Скопировать ссылку", @@ -66,11 +65,6 @@ "ov.bw_title": "Загрузка по приложениям", "ov.bw_sub_week": "Пунктир: то же время неделю назад", "ov.bw_sub_prev": "Пунктир: предыдущий период", -"ov.movers": "Кто вырос", -"ov.movers_sub_week": "По сравнению с тем же временем неделю назад", -"ov.movers_sub_prev": "По сравнению с предыдущим периодом", -"ov.no_movers": "Ничего необычного", -"ov.mover_reason": "В основном с {peer} по {port} ({cc})", "ov.mover_new": "новый", "ov.dir": "Направление трафика", "ov.proto": "Протокол", @@ -115,7 +109,7 @@ "col.status": "Состояние", "col.list": "Список", "col.direction": "Направление", -"conv.sub": "Кто с кем общается: клиент, сервер и сервис по объёму трафика", "conv.slow": "длинные периоды читают все детальные записи и могут занять время; быстрее всего 1 час", +"conv.slow": "длинные периоды читают все детальные записи и могут занять время; быстрее всего 1 час", "topn.title": "Топ {n}", "topn.rollup_note": "Длинный период: данные берутся из почасовых сводок, поэтому фильтры здесь недоступны.", "dim.client": "Клиенты", @@ -136,7 +130,7 @@ "geo.countries": "Страны собеседников", "geo.countries_sub": "Внутренний трафик исключён", "geo.as": "Сети собеседников (AS)", -"geo.no_table": "Нет данных. Для стран и сетей нужна таблица ASN в каталоге данных (asn.tsv.gz).", +"geo.no_table": "Нет данных. Загрузите базу стран и сетей на странице «Источники», чтобы видеть страны и сети.", "thr.hosts": "Узлы, связывавшиеся с адресами из списков", "thr.upload": "Отправлено на адреса из списков", "thr.flows": "Потоки с адресами из списков", @@ -211,14 +205,15 @@ "empty.nodata": "За этот период данных нет.", "empty.first": "Пока ничего не получено. Отправляйте sFlow на UDP 6343, NetFlow на 2055 или IPFIX на 4739 этой машины.", "err.load": "Не удалось загрузить: {e}", -"err.db_memory": "База данных упёрлась в лимит памяти и не смогла ответить. Выберите период покороче или запустите traffic66 с бо́льшим -memory.", "err.db_busy": "База данных сейчас не смогла ответить; причина записана в журнал traffic66. Страница повторит попытку через 30 секунд.", +"err.db_memory": "База данных упёрлась в лимит памяти и не смогла ответить. Выберите период покороче или запустите traffic66 с бо́льшим -memory.", +"err.db_busy": "База данных сейчас не смогла ответить; причина записана в журнал traffic66. Страница повторит попытку через 30 секунд.", "login.title": "Вход", "login.user": "Пользователь", "login.password": "Пароль", "login.go": "Войти", "login.bad": "Неверный пользователь или пароль", "demo.badge": "Демо", -"tui.help": "↑↓ выбор Enter действия f только x исключить / поиск t период 1-9 страницы c сброс w браузер q выход", +"tui.help": "↑↓ выбор Enter действия f только x исключить / поиск t период 1-8 страницы c сброс w браузер q выход", "tui.connecting": "Подключение к {url}…", "tui.error": "Нет связи с traffic66 по адресу {url}: {e}", "tui.search": "Поиск", @@ -227,5 +222,40 @@ "tui.opened": "Открыто в браузере", "tui.open_failed": "Откройте этот адрес: {url}", "tui.login": "Нужен вход: запустите с -user и -password или на машине, где работает traffic66", -"tui.close": "Esc закрыть" +"tui.close": "Esc закрыть", +"pop.detail": "Подробнее", +"pop.name": "Дать название…", +"pop.name_ph": "Название, например ПК бухгалтерии", +"pop.named": "Сохранено; теперь это название показывается везде", +"pop.actions": "Действия", +"topn.group": "Группировать по", +"topn.hint": "Щёлкните синий заголовок столбца, чтобы сгруппировать по нему, или числовой — чтобы отсортировать", +"topn.by_col": "Сгруппировать таблицу по этому столбцу", +"nav.detail": "Подробности", +"det.peers": "С какими узлами общается", +"det.clients": "Узлы, которые его используют", +"det.conv": "С кем общается", +"det.services": "Сервисы и порты", +"det.servers": "Серверы", +"det.flows": "Последние записи потоков", +"self.free": "Свободно", +"self.need": "Для хранения детальных данных за {d} дн. при текущем темпе нужно около {n}", +"self.need_later": "Хранение детальных данных за {d} дн.: нужный объём будет оценён, когда накопятся данные за сутки", +"geo.title": "База стран и сетей", +"geo.sub": "Показывает страну и сеть (AS) каждого внешнего адреса", +"geo.none": "Базы пока нет, поэтому страны и сети показаны как «Неизвестно». Загрузите её ниже.", +"geo.upload": "Загрузить файл базы…", +"geo.uploading": "Загрузка и проверка…", +"geo.installed": "Установлено как {f}. Применяется к новому трафику с этого момента.", +"geo.where": "Принимаются: файлы .mmdb (MaxMind GeoLite2 или DB-IP Lite, страны или ASN) и таблицы IP-to-ASN (.tsv или .tsv.gz, как на iptoasn.com). Бесплатная загрузка: db-ip.com/db/lite.php и iptoasn.com; для GeoLite2 нужна бесплатная учётная запись MaxMind. Для уже сохранённого трафика остаётся страна, определённая при сохранении.", +"geo.kind_table": "Таблица IP-to-ASN", +"geo.kind_country": "Страны", +"geo.kind_asn": "Сети (AS)", +"geo.built": "собрана {d}", +"geo.entries": "диапазонов: {n}", +"src.names_help": "Проще всего: щёлкните любой адрес на любой странице и выберите «Дать название». Можно также писать здесь по одной записи в строке, например:", +"src.ex_host": "ПК бухгалтерии", +"src.ex_net": "Офисная сеть", +"src.ex_device": "Ядровой маршрутизатор", +"src.ex_iface": "Аплинк провайдера" } diff --git a/internal/web/static/i18n/ur.json b/internal/web/static/i18n/ur.json index d3b5196..c7088d4 100644 --- a/internal/web/static/i18n/ur.json +++ b/internal/web/static/i18n/ur.json @@ -1,7 +1,7 @@ { "lang.name": "اردو", "nav.overview": "جائزہ", -"nav.conv": "گفتگو", "nav.topn": "ٹاپ N", +"nav.topn": "ٹاپ N", "nav.sankey": "ٹریفک کے راستے", "nav.geo": "مقامات اور نیٹ ورک", "nav.threats": "خطرے کی معلومات", @@ -14,7 +14,6 @@ "self.per_sec": "{n} فلو فی سیکنڈ", "self.dropped": "ضائع شدہ", "self.disk": "ڈسک", -"self.days": "{n} دن باقی", "top.search": "IP، نیٹ ورک، پورٹ یا ملک تلاش کریں (اختیاری)", "top.refresh": "ہر 30 سیکنڈ میں تازہ", "top.copy": "لنک کاپی کریں", @@ -66,11 +65,6 @@ "ov.bw_title": "ایپلیکیشن کے لحاظ سے بینڈوڈتھ", "ov.bw_sub_week": "ڈیشڈ لائن: گزشتہ ہفتے اسی وقت", "ov.bw_sub_prev": "ڈیشڈ لائن: پچھلی مدت", -"ov.movers": "کس میں اضافہ ہوا", -"ov.movers_sub_week": "گزشتہ ہفتے اسی وقت کے مقابلے میں", -"ov.movers_sub_prev": "پچھلی مدت کے مقابلے میں", -"ov.no_movers": "کوئی غیر معمولی بات نہیں", -"ov.mover_reason": "زیادہ تر {peer} کے ساتھ {port} پر ({cc})", "ov.mover_new": "نیا", "ov.dir": "ٹریفک کی سمت", "ov.proto": "پروٹوکول", @@ -115,7 +109,7 @@ "col.status": "حالت", "col.list": "فہرست", "col.direction": "سمت", -"conv.sub": "کون کس سے بات کر رہا ہے: ٹریفک کے لحاظ سے کلائنٹ، سرور اور سروس", "conv.slow": "لمبی مدت میں تمام فلو تفصیل پڑھی جاتی ہے اور وقت لگ سکتا ہے؛ 1 گھنٹہ سب سے تیز ہے", +"conv.slow": "لمبی مدت میں تمام فلو تفصیل پڑھی جاتی ہے اور وقت لگ سکتا ہے؛ 1 گھنٹہ سب سے تیز ہے", "topn.title": "ٹاپ {n}", "topn.rollup_note": "طویل مدت: ڈیٹا گھنٹہ وار خلاصوں سے لیا گیا ہے، اس لیے یہاں فلٹر دستیاب نہیں۔", "dim.client": "کلائنٹس", @@ -136,7 +130,7 @@ "geo.countries": "بیرونی ممالک", "geo.countries_sub": "اندرونی ٹریفک شامل نہیں", "geo.as": "بیرونی نیٹ ورکس (AS)", -"geo.no_table": "کوئی ڈیٹا نہیں۔ ممالک اور نیٹ ورکس کے لیے ڈیٹا فولڈر میں ASN جدول (asn.tsv.gz) درکار ہے۔", +"geo.no_table": "کوئی ڈیٹا نہیں۔ ممالک اور نیٹ ورکس دیکھنے کے لیے ذرائع صفحے پر ممالک اور نیٹ ورکس کا ڈیٹابیس اپ لوڈ کریں۔", "thr.hosts": "فہرست شدہ پتوں سے رابطے والے ہوسٹ", "thr.upload": "فہرست شدہ پتوں کو بھیجا گیا", "thr.flows": "فہرست شدہ پتوں والے فلو", @@ -211,14 +205,15 @@ "empty.nodata": "اس مدت میں کوئی ڈیٹا نہیں۔", "empty.first": "ابھی تک کچھ وصول نہیں ہوا۔ اس مشین پر sFlow کو UDP 6343، NetFlow کو 2055 یا IPFIX کو 4739 پر بھیجیں۔", "err.load": "لوڈ نہیں ہو سکا: {e}", -"err.db_memory": "ڈیٹا بیس اپنی میموری کی حد تک پہنچ گیا اور جواب نہیں دے سکا۔ کم وقت کی حد چنیں، یا traffic66 کو بڑے ‎-memory کے ساتھ چلائیں۔", "err.db_busy": "ڈیٹا بیس ابھی جواب نہیں دے سکا؛ وجہ traffic66 کے لاگ میں درج ہے۔ صفحہ 30 سیکنڈ میں دوبارہ کوشش کرے گا۔", +"err.db_memory": "ڈیٹا بیس اپنی میموری کی حد تک پہنچ گیا اور جواب نہیں دے سکا۔ کم وقت کی حد چنیں، یا traffic66 کو بڑے ‎-memory کے ساتھ چلائیں۔", +"err.db_busy": "ڈیٹا بیس ابھی جواب نہیں دے سکا؛ وجہ traffic66 کے لاگ میں درج ہے۔ صفحہ 30 سیکنڈ میں دوبارہ کوشش کرے گا۔", "login.title": "سائن ان", "login.user": "صارف", "login.password": "پاس ورڈ", "login.go": "سائن ان", "login.bad": "صارف یا پاس ورڈ غلط ہے", "demo.badge": "ڈیمو", -"tui.help": "↑↓ منتخب Enter اعمال f صرف x خارج / تلاش t وقت 1-9 صفحات c صاف w براؤزر q باہر", +"tui.help": "↑↓ منتخب Enter اعمال f صرف x خارج / تلاش t وقت 1-8 صفحات c صاف w براؤزر q باہر", "tui.connecting": "{url} سے رابطہ ہو رہا ہے…", "tui.error": "{url} پر traffic66 تک رسائی نہیں: {e}", "tui.search": "تلاش", @@ -227,5 +222,40 @@ "tui.opened": "براؤزر میں کھول دیا گیا", "tui.open_failed": "یہ پتہ کھولیں: {url}", "tui.login": "لاگ ان درکار ہے: -user اور -password کے ساتھ شروع کریں، یا traffic66 والی مشین پر چلائیں", -"tui.close": "Esc بند کریں" +"tui.close": "Esc بند کریں", +"pop.detail": "تفصیل دیکھیں", +"pop.name": "نام دیں…", +"pop.name_ph": "نام، مثلاً اکاؤنٹس پی سی", +"pop.named": "محفوظ ہو گیا؛ یہ نام اب ہر جگہ دکھائی دے گا", +"pop.actions": "کارروائیاں", +"topn.group": "گروپ بندی بلحاظ", +"topn.hint": "کسی نیلے کالم عنوان پر کلک کریں تو اس کے لحاظ سے گروپ ہو گا، عددی عنوان پر کلک کریں تو ترتیب بدلے گی", +"topn.by_col": "جدول کو اس کالم کے لحاظ سے گروپ کریں", +"nav.detail": "تفصیل", +"det.peers": "جن ہوسٹس سے یہ بات کرتا ہے", +"det.clients": "جو ہوسٹس اسے استعمال کرتے ہیں", +"det.conv": "یہ کس سے بات کرتا ہے", +"det.services": "سروسز اور پورٹس", +"det.servers": "سرورز", +"det.flows": "تازہ ترین فلو ریکارڈ", +"self.free": "خالی", +"self.need": "موجودہ رفتار پر {d} دن کی تفصیل رکھنے کے لیے تقریباً {n} درکار ہے", +"self.need_later": "{d} دن کی تفصیل رکھنا: ایک دن کا ڈیٹا جمع ہونے پر درکار جگہ کا اندازہ لگایا جائے گا", +"geo.title": "ممالک اور نیٹ ورکس کا ڈیٹابیس", +"geo.sub": "ہر بیرونی پتے کا ملک اور نیٹ ورک (AS) دکھاتا ہے", +"geo.none": "ابھی کوئی ڈیٹابیس نہیں، اس لیے ممالک اور نیٹ ورکس نامعلوم دکھائی دیتے ہیں۔ نیچے ایک اپ لوڈ کریں۔", +"geo.upload": "ڈیٹابیس فائل اپ لوڈ کریں…", +"geo.uploading": "اپ لوڈ اور جانچ ہو رہی ہے…", +"geo.installed": "{f} کے طور پر انسٹال ہو گیا۔ اب سے نئے ٹریفک پر لاگو ہو گا۔", +"geo.where": "قابلِ قبول: .mmdb فائلیں (MaxMind GeoLite2 یا DB-IP Lite، ملک یا ASN) اور IP-to-ASN جدول (.tsv یا .tsv.gz، جیسے iptoasn.com سے)۔ مفت ڈاؤن لوڈ: db-ip.com/db/lite.php اور iptoasn.com؛ GeoLite2 کے لیے مفت MaxMind اکاؤنٹ درکار ہے۔ پہلے سے محفوظ ٹریفک وہی ملک رکھتا ہے جس کے ساتھ وہ محفوظ ہوا تھا۔", +"geo.kind_table": "IP-to-ASN جدول", +"geo.kind_country": "ممالک", +"geo.kind_asn": "نیٹ ورکس (AS)", +"geo.built": "{d} کو بنا", +"geo.entries": "{n} رینجز", +"src.names_help": "سب سے آسان: کسی بھی صفحے پر کسی بھی پتے پر کلک کریں اور “نام دیں” منتخب کریں۔ آپ یہاں ہر سطر میں ایک اندراج بھی لکھ سکتے ہیں، مثلاً:", +"src.ex_host": "اکاؤنٹس پی سی", +"src.ex_net": "دفتر LAN", +"src.ex_device": "کور راؤٹر", +"src.ex_iface": "ISP اپ لنک" } diff --git a/internal/web/static/i18n/zh.json b/internal/web/static/i18n/zh.json index 300d178..aff098c 100644 --- a/internal/web/static/i18n/zh.json +++ b/internal/web/static/i18n/zh.json @@ -1,7 +1,7 @@ { "lang.name": "中文", "nav.overview": "概览", -"nav.conv": "会话", "nav.topn": "Top-N", +"nav.topn": "Top-N", "nav.sankey": "流向", "nav.geo": "地理与运营商", "nav.threats": "威胁情报", @@ -14,7 +14,6 @@ "self.per_sec": "{n} 条/秒", "self.dropped": "丢弃", "self.disk": "磁盘", -"self.days": "可存 {n} 天", "top.search": "搜索 IP、网段、端口、国家(不用也行)", "top.refresh": "每 30 秒刷新", "top.copy": "复制链接", @@ -66,11 +65,6 @@ "ov.bw_title": "带宽,按应用", "ov.bw_sub_week": "虚线是上周同期", "ov.bw_sub_prev": "虚线是上一时段", -"ov.movers": "比平时多了谁", -"ov.movers_sub_week": "对比上周同一时段", -"ov.movers_sub_prev": "对比上一时段", -"ov.no_movers": "没有明显变化", -"ov.mover_reason": "主要是和 {peer} 的 {port}({cc})", "ov.mover_new": "新出现", "ov.dir": "流量方向", "ov.proto": "协议", @@ -115,7 +109,7 @@ "col.status": "状态", "col.list": "名单", "col.direction": "方向", -"conv.sub": "谁在和谁通信:按流量排列的客户端、服务端和服务", "conv.slow": "长时间范围需要读取全部明细,可能较慢;1 小时最快", +"conv.slow": "长时间范围需要读取全部明细,可能较慢;1 小时最快", "topn.title": "Top {n}", "topn.rollup_note": "时间跨度较长,数据来自小时汇总,这里不能再加筛选。", "dim.client": "客户端", @@ -136,7 +130,7 @@ "geo.countries": "对端国家", "geo.countries_sub": "内网流量已排除", "geo.as": "对端运营商(AS)", -"geo.no_table": "没有数据。国家和运营商需要数据目录里有 ASN 表(asn.tsv.gz)。", +"geo.no_table": "没有数据。请在“接入”页面上传国家和运营商数据库,即可查看国家和运营商。", "thr.hosts": "和名单地址通信的主机", "thr.upload": "发往名单地址的流量", "thr.flows": "涉及名单地址的流", @@ -211,14 +205,15 @@ "empty.nodata": "这段时间没有数据。", "empty.first": "还没有收到任何数据。把 sFlow 发到本机 UDP 6343,NetFlow 发到 2055,IPFIX 发到 4739。", "err.load": "加载失败:{e}", -"err.db_memory": "数据库达到内存上限,无法完成这次查询。请缩短时间范围,或用更大的 -memory 启动 traffic66。", "err.db_busy": "数据库暂时无法回答,原因已写入 traffic66 日志。页面 30 秒后会自动重试。", +"err.db_memory": "数据库达到内存上限,无法完成这次查询。请缩短时间范围,或用更大的 -memory 启动 traffic66。", +"err.db_busy": "数据库暂时无法回答,原因已写入 traffic66 日志。页面 30 秒后会自动重试。", "login.title": "登录", "login.user": "用户名", "login.password": "密码", "login.go": "登录", "login.bad": "用户名或密码不对", "demo.badge": "演示", -"tui.help": "↑↓ 选择 Enter 操作 f 只看 x 排除 / 搜索 t 时间 1-9 切页 c 清除 w 浏览器 q 退出", +"tui.help": "↑↓ 选择 Enter 操作 f 只看 x 排除 / 搜索 t 时间 1-8 切页 c 清除 w 浏览器 q 退出", "tui.connecting": "正在连接 {url}…", "tui.error": "连不上 {url} 上的 traffic66:{e}", "tui.search": "搜索", @@ -227,5 +222,40 @@ "tui.opened": "已在浏览器打开", "tui.open_failed": "请打开这个地址:{url}", "tui.login": "需要登录:用 -user 和 -password 启动,或者在 traffic66 所在的机器上运行", -"tui.close": "Esc 关闭" +"tui.close": "Esc 关闭", +"pop.detail": "查看详情", +"pop.name": "起个名字…", +"pop.name_ph": "名称,例如 财务电脑", +"pop.named": "已保存,各处都会显示这个名称", +"pop.actions": "操作", +"topn.group": "分组方式", +"topn.hint": "点击蓝色列标题按该列分组,点击数字列标题排序", +"topn.by_col": "按此列对表格分组", +"nav.detail": "详情", +"det.peers": "与它通信的主机", +"det.clients": "使用它的主机", +"det.conv": "它在和谁通信", +"det.services": "服务和端口", +"det.servers": "服务端", +"det.flows": "最新流记录", +"self.free": "可用", +"self.need": "按当前速率,保留 {d} 天明细约需 {n}", +"self.need_later": "保留 {d} 天明细:有一天的数据后再估算所需空间", +"geo.title": "国家和运营商数据库", +"geo.sub": "显示每个外部地址所属的国家和运营商(AS)", +"geo.none": "还没有数据库,国家和运营商会显示为“未知”。请在下方上传。", +"geo.upload": "上传数据库文件…", +"geo.uploading": "正在上传并校验…", +"geo.installed": "已安装为 {f},从现在起对新流量生效。", +"geo.where": "支持:.mmdb 文件(MaxMind GeoLite2 或 DB-IP Lite,国家库或 ASN 库),以及 IP-to-ASN 表(.tsv 或 .tsv.gz,如 iptoasn.com 提供的)。免费下载:db-ip.com/db/lite.php 和 iptoasn.com;GeoLite2 需要注册免费的 MaxMind 账号。已入库的流量保留入库时的国家。", +"geo.kind_table": "IP-to-ASN 表", +"geo.kind_country": "国家", +"geo.kind_asn": "运营商(AS)", +"geo.built": "生成于 {d}", +"geo.entries": "{n} 个地址段", +"src.names_help": "最简单的方法:在任意页面点击任意地址,选择“起个名字”。也可以在这里每行写一条,例如:", +"src.ex_host": "财务电脑", +"src.ex_net": "办公网", +"src.ex_device": "核心路由器", +"src.ex_iface": "运营商上联" } diff --git a/internal/web/static/index.html b/internal/web/static/index.html index 64ecfca..c065f72 100644 --- a/internal/web/static/index.html +++ b/internal/web/static/index.html @@ -15,7 +15,6 @@