diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbda52..af78f33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ Offline analysis - The detection rules run over the capture: scans, port scans and password guessing are found in it. - The demo includes an example capture with an attack. +- `traffic66 capture.pcap` (up to 3 files, 3 GB in all) starts a private + traffic66 on 127.0.0.1 with a free port, prints the address, a password + and a one-time sign-in link, opens the browser on the capture, and + deletes the imported data on Ctrl+C. Files are read in place; nothing is + collected or sent and host names are not looked up unless `-dns` is + given. A 1 GB capture takes about 5 s (1.2 million full-size packets) to + 30 s (14 million small packets) on 2 cores. Pages - Traffic details opens on servers only; tabs switch to clients, both ends diff --git a/README.md b/README.md index 2e52026..d75c6c9 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ in a web UI and in a terminal UI. - Top 66 lists, traffic over time by client, server, service, interface and network (AS), flow paths, countries, threat list matches, flow records, encapsulation (GRE, IPIP, VXLAN, GENEVE, MPLS). +- `traffic66 capture.pcap` opens up to 3 packet captures (3 GB in all) in the web UI: flows, findings, countries and flow records over the whole capture, with nothing to set up. - 13 languages in the web UI and the terminal UI. ![Overview: open findings, bandwidth by application compared with last week, top clients and services](docs/images/overview.png) @@ -737,7 +738,30 @@ The same overview in Chinese; every page is available in 13 languages: ### Offline analysis -**Offline analysis** looks at packet captures from Wireshark or tcpdump with the same pages as the live data, without mixing them in: +**Offline analysis** looks at packet captures from Wireshark or tcpdump with the same pages as the live data, without mixing them in. + +It summarizes all the packets into flows: who talked to whom, how much, when, and what looks like an attack. It does not decode protocols or show packet contents; for one packet or one TCP stream, use Wireshark. + +From the command line, without setting anything up: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 starts on this computer only (127.0.0.1, a free port), prints the address, the password and a one-time sign-in link, and opens the browser on the capture. Up to 3 files, 3 GB in all; they are read where they are and never changed. Nothing is collected or sent, and host names are not looked up (`-dns` turns that on). Ctrl+C stops and deletes the imported data. On a 2-core machine a 1 GB capture is ready in about 5 seconds (1.2 million full-size packets) to 30 seconds (14 million small packets). + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +In the web UI of a running traffic66: 1. **Upload capture files…**: `.pcap` or `.pcapng`, not compressed. Up to 3 files, each at most 50 MB. The files are turned into flows in a database of their own (`/sandbox/`); the live data, its numbers and findings are not touched. 2. **Analyse**: every page (overview, Top 66, traffic details, findings, flow paths, map, flow records) now shows the capture files over their whole time. An orange bar names the files; **Back to live data** returns. Each file appears as a device, so the **Device** box shows one file at a time. diff --git a/cmd/traffic66/main.go b/cmd/traffic66/main.go index 9236e1f..ddbc035 100644 --- a/cmd/traffic66/main.go +++ b/cmd/traffic66/main.go @@ -18,6 +18,7 @@ import ( "os/signal" "path/filepath" "runtime/debug" + "slices" "strings" "syscall" "time" @@ -47,6 +48,7 @@ const usage = `traffic66 %s — flow analytics for sFlow, NetFlow and IPFIX Usage: traffic66 [serve] [flags] collect flows and serve the web UI (default) + traffic66 FILE.pcap [...] analyse up to 3 capture files (pcap, pcapng) in the web UI traffic66 demo [flags] run with a built-in simulated network traffic66 tui [flags] terminal UI (connects to a running traffic66) traffic66 simulate -to HOST send simulated exports to another collector @@ -60,6 +62,10 @@ Run "traffic66 -h" for the flags of a command. func main() { log.SetFlags(log.LstdFlags) args := cleanArgs(os.Args[1:]) + if len(args) > 0 && !strings.HasPrefix(args[0], "-") && !slices.Contains(commands, strings.ToLower(args[0])) && looksLikeCapture(args[0]) { + runOffline(args) + return + } cmd := "serve" if len(args) > 0 && !strings.HasPrefix(args[0], "-") { cmd, args = strings.ToLower(args[0]), args[1:] @@ -389,7 +395,18 @@ func serve(args []string, demo bool) { srv := &api.Server{Store: st, Pipe: pipe, Col: col, Inv: inv, ASN: asn, Thr: thr, DNS: dns, Det: det, Static: web.FS(), Version: version, Demo: demo, Check: checker.Check, Exists: checker.Exists, LocalTok: tok, DataDir: f.data, Started: time.Now()} srv.SNMP = poller.Status - srv.SB = sandbox.New(filepath.Join(f.data, "sandbox"), inv, asn, thr) + if offline != nil { + srv.SB = sandbox.NewWith(filepath.Join(f.data, "sandbox"), inv, asn, thr, sandbox.LocalLimits, 0.25) + srv.Offline = true + srv.AutoLogin = randomHex(16) + for _, p := range offline.files { + if _, err := srv.SB.AddPath(p); err != nil { + fatalf("%v", err) + } + } + } else { + srv.SB = sandbox.New(filepath.Join(f.data, "sandbox"), inv, asn, thr) + } defer srv.SB.Close() if demo { demoSample(f.data, srv.SB) @@ -408,6 +425,18 @@ func serve(args []string, demo bool) { hs := &http.Server{Handler: srv.Handler(), ReadHeaderTimeout: 10 * time.Second} go hs.Serve(ln) log.Printf("web UI: http://%s", displayAddr(ln.Addr())) + if offline != nil { + u := "http://" + displayAddr(ln.Addr()) + _, port, _ := net.SplitHostPort(ln.Addr().String()) + fmt.Printf("\ntraffic66 %s: analysing %d capture file(s); nothing is collected or sent\n", version, len(offline.files)) + fmt.Printf(" Web UI %s (port %s, this computer only)\n", u, port) + fmt.Printf(" Sign in user %s, password %s\n", f.user, f.password) + fmt.Printf(" Open %s/auto?t=%s (signs in once)\n", u, srv.AutoLogin) + fmt.Printf(" Stop Ctrl+C; the imported data is deleted, your files are kept\n\n") + if offline.browser { + tui.OpenBrowser(u + "/auto?t=" + srv.AutoLogin) + } + } if doubleClick { log.Printf("opening the web UI in your browser; close this window to stop traffic66") tui.OpenBrowser("http://" + displayAddr(ln.Addr())) diff --git a/cmd/traffic66/offline.go b/cmd/traffic66/offline.go new file mode 100644 index 0000000..3924edc --- /dev/null +++ b/cmd/traffic66/offline.go @@ -0,0 +1,124 @@ +package main + +import ( + "crypto/rand" + "flag" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/githubflyideas/traffic66/internal/pcapfile" + "github.com/githubflyideas/traffic66/internal/sandbox" +) + +// offlineRun holds what "traffic66 file.pcap" passes to serve. +type offlineRun struct { + files []string + browser bool +} + +var offline *offlineRun + +// looksLikeCapture tells "traffic66 file.pcap" from a mistyped command. +func looksLikeCapture(arg string) bool { + ext := strings.ToLower(filepath.Ext(arg)) + if ext == ".pcap" || ext == ".pcapng" || ext == ".cap" { + return true + } + fi, err := os.Stat(arg) + return err == nil && fi.Mode().IsRegular() +} + +// checkCaptures checks the files before anything starts: at most the local +// limits, and each a pcap or pcapng file. +func checkCaptures(files []string, lim sandbox.Limits) error { + if len(files) > lim.Files { + return fmt.Errorf("at most %d files at a time (got %d)", lim.Files, len(files)) + } + var total int64 + for _, p := range files { + fi, err := os.Stat(p) + if err != nil { + return err + } + if !fi.Mode().IsRegular() { + return fmt.Errorf("%s is not a file", p) + } + f, err := os.Open(p) + if err != nil { + return err + } + head := make([]byte, 4) + _, err = io.ReadFull(f, head) + f.Close() + if err != nil || pcapfile.Format(head) == "" { + return fmt.Errorf("%s is not a capture file: use .pcap or .pcapng as saved by Wireshark or tcpdump (not compressed)", p) + } + total += fi.Size() + } + if total > lim.TotalSize { + return fmt.Errorf("the files have %s; at most %s in all", humanBytes(total), humanBytes(lim.TotalSize)) + } + return nil +} + +func humanBytes(n int64) string { + switch { + case n >= 1e9: + return fmt.Sprintf("%.1f GB", float64(n)/1e9) + case n >= 1e6: + return fmt.Sprintf("%.1f MB", float64(n)/1e6) + } + return fmt.Sprintf("%d bytes", n) +} + +// runOffline analyses capture files: a private traffic66 on 127.0.0.1 with +// a temporary database that is deleted on exit; the files stay untouched. +func runOffline(args []string) { + var files, rest []string + for i, a := range args { + if strings.HasPrefix(a, "-") { + rest = args[i:] + break + } + files = append(files, a) + } + fs := flag.NewFlagSet("traffic66 FILE.pcap", flag.ExitOnError) + addr := fs.String("addr", "127.0.0.1:0", "web UI address (default: a free port on this computer only)") + noBrowser := fs.Bool("no-browser", false, "do not open the browser") + dns := fs.Bool("dns", false, "look up host names of the addresses (off: a capture's addresses are not sent to DNS)") + fs.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: traffic66 FILE.pcap [FILE2.pcapng FILE3.pcap] [flags]\n\nAnalyse capture files (at most %d, %s in all) in the web UI.\n\n", sandbox.LocalLimits.Files, humanBytes(sandbox.LocalLimits.TotalSize)) + fs.PrintDefaults() + } + fs.Parse(rest) + files = append(files, fs.Args()...) + if err := checkCaptures(files, sandbox.LocalLimits); err != nil { + fmt.Fprintln(os.Stderr, "traffic66:", err) + os.Exit(2) + } + tmp, err := os.MkdirTemp("", "traffic66-offline-") + if err != nil { + fatalf("temporary directory: %v", err) + } + defer os.RemoveAll(tmp) + offline = &offlineRun{files: files, browser: !*noBrowser} + sargs := []string{"-data", tmp, "-listen", "", "-addr", *addr, "-password", readablePassword(), "-memory", "0.15"} + if !*dns { + sargs = append(sargs, "-no-dns") + } + serve(sargs, false) +} + +// readablePassword is 12 letters and digits without look-alikes. +func readablePassword() string { + const set = "abcdefghjkmnpqrstuvwxyz23456789" + b := make([]byte, 12) + rand.Read(b) + for i := range b { + b[i] = set[int(b[i])%len(set)] + } + return string(b) +} diff --git a/cmd/traffic66/offline_test.go b/cmd/traffic66/offline_test.go new file mode 100644 index 0000000..7d348c4 --- /dev/null +++ b/cmd/traffic66/offline_test.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/githubflyideas/traffic66/internal/sandbox" +) + +func TestCheckCaptures(t *testing.T) { + dir := t.TempDir() + pcap := []byte{0xd4, 0xc3, 0xb2, 0xa1, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 1, 0, 0, 0} + write := func(name string, b []byte) string { + p := filepath.Join(dir, name) + os.WriteFile(p, b, 0o644) + return p + } + a, b, c, d := write("a.pcap", pcap), write("b.pcapng", append([]byte{0x0a, 0x0d, 0x0d, 0x0a}, make([]byte, 100)...)), write("c.cap", pcap), write("d.pcap", pcap) + txt := write("notes.txt", []byte("hello world")) + lim := sandbox.Limits{Files: 3, FileSize: 1000, TotalSize: 140} + for _, tc := range []struct { + files []string + err string + }{ + {[]string{a, b}, ""}, + {[]string{a, b, c, d}, "at most 3 files"}, + {[]string{txt}, "not a capture file"}, + {[]string{a, b, c}, "in all"}, + } { + err := checkCaptures(tc.files, lim) + if (tc.err == "") != (err == nil) || err != nil && !strings.Contains(err.Error(), tc.err) { + t.Errorf("%v: %v, want %q", tc.files, err, tc.err) + } + } + // the message differs between systems + if err := checkCaptures([]string{filepath.Join(dir, "missing.pcap")}, lim); !errors.Is(err, fs.ErrNotExist) { + t.Errorf("missing file: %v", err) + } + if !looksLikeCapture("x.PCAPNG") || looksLikeCapture("serv") || !looksLikeCapture(txt) { + t.Error("looksLikeCapture") + } +} diff --git a/docs/README.ar.md b/docs/README.ar.md index b606783..b3888ad 100644 --- a/docs/README.ar.md +++ b/docs/README.ar.md @@ -22,6 +22,7 @@ - قوائم أعلى 66، والحركة عبر الزمن حسب العميل والخادم والخدمة والواجهة والشبكة (AS)، ومسارات الحركة، والدول، والتطابقات مع قوائم التهديدات، وسجلات التدفق، والتغليف (GRE وIPIP وVXLAN وGENEVE وMPLS). +- يفتح `traffic66 capture.pcap` حتى 3 ملفات التقاط (3 GB إجمالاً) في واجهة الويب: التدفقات والاكتشافات والدول وسجلات التدفق لكامل الالتقاط، دون أي إعداد. - 13 لغة في واجهة الويب والواجهة الطرفية. ![نظرة عامة: الاكتشافات المفتوحة، واستهلاك عرض النطاق حسب التطبيق مقارنةً بالأسبوع الماضي، وأبرز العملاء والخدمات](images/overview.png) @@ -750,7 +751,30 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh ### التحليل دون اتصال -يعرض **التحليل دون اتصال** ملفات التقاط Wireshark أو tcpdump بالصفحات نفسها المستخدمة للبيانات الحية، دون خلطها بها: +يعرض **التحليل دون اتصال** ملفات التقاط Wireshark أو tcpdump بالصفحات نفسها المستخدمة للبيانات الحية، دون خلطها بها. + +يلخّص كل الحزم في تدفقات: من تحدث مع من، وكم، ومتى، وما الذي يبدو هجوماً. لا يفك ترميز البروتوكولات ولا يعرض محتوى الحزم؛ لحزمة واحدة أو تدفق TCP واحد استخدم Wireshark. + +من سطر الأوامر، دون أي إعداد: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +يبدأ traffic66 على هذا الحاسوب فقط (127.0.0.1، منفذ متاح)، ويطبع العنوان وكلمة المرور ورابط دخول لمرة واحدة، ويفتح الالتقاط في المتصفح. حتى 3 ملفات، 3 GB إجمالاً؛ تُقرأ الملفات في مكانها ولا تُعدَّل أبداً. لا يُجمع شيء ولا يُرسل، ولا يُبحث عن أسماء المضيفين (يفعّلها `-dns`). يوقف Ctrl+C البرنامج ويحذف البيانات المستوردة. على جهاز ثنائي النواة يجهز التقاط بحجم 1 GB في نحو 5 ثوانٍ (1.2 مليون حزمة كاملة الحجم) إلى 30 ثانية (14 مليون حزمة صغيرة). + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +في واجهة الويب لـ traffic66 قيد التشغيل: 1. **رفع ملفات التقاط…**: ‎`.pcap` أو ‎`.pcapng` غير مضغوطة. حتى 3 ملفات، كل منها 50 MB كحد أقصى. تتحول الملفات إلى تدفقات في قاعدة بيانات خاصة بها (`/sandbox/`)، فلا تتأثر البيانات الحية وأرقامها واكتشافاتها. 2. **تحليل**: تعرض كل الصفحات (نظرة عامة، أعلى 66، تفاصيل الحركة، الاكتشافات، مسارات الحركة، الخريطة، سجلات التدفق) الملفات على امتداد وقتها كله. يذكر شريط برتقالي أسماء الملفات، و**العودة إلى البيانات الحية** يعيدك. يظهر كل ملف كجهاز، فيعرض مربع **الجهاز** ملفاً واحداً في كل مرة. diff --git a/docs/README.bn.md b/docs/README.bn.md index 673140c..16de1c7 100644 --- a/docs/README.bn.md +++ b/docs/README.bn.md @@ -20,6 +20,7 @@ database-এ রাখে, এবং দেখায় কে bandwidth ব্ - Top 66 তালিকা, client, server, service, interface ও নেটওয়ার্ক (AS) অনুযায়ী সময়ের সাথে ট্রাফিক, flow-এর পথ, দেশ, threat list-এর match, flow record, encapsulation (GRE, IPIP, VXLAN, GENEVE, MPLS)। +- `traffic66 capture.pcap` সর্বোচ্চ 3টি প্যাকেট ক্যাপচার (মোট 3 GB) ওয়েব UI-তে খোলে: পুরো ক্যাপচারের ফ্লো, ফলাফল, দেশ ও ফ্লো রেকর্ড, কোনো সেটআপ ছাড়াই। - web UI ও terminal UI-তে 13টি ভাষা। ![সারসংক্ষেপ: খোলা সন্দেহজনক কার্যকলাপ, গত সপ্তাহের তুলনায় application অনুযায়ী bandwidth, শীর্ষ client ও service](images/overview.png) @@ -758,7 +759,30 @@ packet পাঠায় যে দেখা যায় না। ডেম ### অফলাইন বিশ্লেষণ -**অফলাইন বিশ্লেষণ** Wireshark বা tcpdump-এর ক্যাপচার লাইভ ডেটার মতো একই পেজে দেখায়, তবে মেশায় না: +**অফলাইন বিশ্লেষণ** Wireshark বা tcpdump-এর ক্যাপচার লাইভ ডেটার মতো একই পেজে দেখায়, তবে মেশায় না। + +এটি সব প্যাকেটকে ফ্লো-তে সংক্ষেপ করে: কে কার সঙ্গে, কতটা, কখন কথা বলেছে, আর কোনটা আক্রমণের মতো দেখায়। এটি প্রোটোকল ডিকোড করে না বা প্যাকেটের বিষয়বস্তু দেখায় না; একটি প্যাকেট বা একটি TCP স্ট্রিম দেখতে Wireshark ব্যবহার করুন। + +কমান্ড লাইন থেকে, কোনো সেটআপ ছাড়াই: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 শুধু এই কম্পিউটারে (127.0.0.1, একটি ফাঁকা পোর্ট) চালু হয়, ঠিকানা, পাসওয়ার্ড ও একবার ব্যবহারযোগ্য সাইন-ইন লিংক দেখায় এবং ব্রাউজারে ক্যাপচার খোলে। সর্বোচ্চ 3টি ফাইল, মোট 3 GB; ফাইলগুলো যেখানে আছে সেখান থেকেই পড়া হয়, কখনো বদলানো হয় না। কিছুই সংগ্রহ বা পাঠানো হয় না, আর হোস্ট নামও খোঁজা হয় না (`-dns` দিয়ে চালু করুন)। Ctrl+C বন্ধ করে এবং ইমপোর্ট করা ডেটা মুছে দেয়। 2-কোর মেশিনে 1 GB ক্যাপচার প্রায় 5 সেকেন্ড (12 লাখ পূর্ণ আকারের প্যাকেট) থেকে 30 সেকেন্ডে (1.4 কোটি ছোট প্যাকেট) তৈরি হয়। + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +চলমান traffic66-এর ওয়েব UI-তে: 1. **ক্যাপচার ফাইল আপলোড করুন…**: `.pcap` বা `.pcapng`, সংকুচিত নয়। সর্বোচ্চ 3টি ফাইল, প্রতিটি 50 MB পর্যন্ত। ফাইলগুলো ফ্লো-তে রূপান্তরিত হয়ে আলাদা ডেটাবেসে (`/sandbox/`) যায়; লাইভ ডেটা, তার হিসাব ও ফলাফল বদলায় না। 2. **বিশ্লেষণ**: সব পেজ (সংক্ষেপ, Top 66, ট্রাফিকের বিস্তারিত, ফলাফল, ফ্লো পথ, মানচিত্র, ফ্লো রেকর্ড) ক্যাপচারের পুরো সময় দেখায়। কমলা ব্যানারে ফাইলের নাম থাকে; **লাইভ ডেটায় ফিরুন** দিয়ে ফিরুন। প্রতিটি ফাইল একটি ডিভাইস হিসেবে দেখায়, তাই **ডিভাইস** বক্সে একটি করে ফাইল দেখা যায়। diff --git a/docs/README.es.md b/docs/README.es.md index 12ba861..feb2991 100644 --- a/docs/README.es.md +++ b/docs/README.es.md @@ -21,6 +21,7 @@ propios equipos, tanto en una interfaz web como en una interfaz de terminal. interfaz y red (AS), rutas de tráfico, países, coincidencias con listas de amenazas, registros de flujo, encapsulación (GRE, IPIP, VXLAN, GENEVE, MPLS). +- `traffic66 captura.pcap` abre hasta 3 capturas de paquetes (3 GB en total) en la interfaz web: flujos, hallazgos, países y registros de toda la captura, sin configurar nada. - 13 idiomas en la interfaz web y en la de terminal. ![Resumen: hallazgos abiertos, ancho de banda por aplicación frente a la semana pasada, principales clientes y servicios](images/overview.png) @@ -778,7 +779,30 @@ El mismo resumen en chino; todas las páginas están disponibles en 13 idiomas: ### Análisis offline -**Análisis offline** muestra capturas de Wireshark o tcpdump con las mismas páginas que los datos en vivo, sin mezclarlas: +**Análisis offline** muestra capturas de Wireshark o tcpdump con las mismas páginas que los datos en vivo, sin mezclarlas. + +Resume todos los paquetes en flujos: quién habló con quién, cuánto, cuándo y qué parece un ataque. No decodifica protocolos ni muestra el contenido de los paquetes; para un paquete o un flujo TCP, use Wireshark. + +Desde la línea de comandos, sin configurar nada: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 arranca solo en este equipo (127.0.0.1, un puerto libre), muestra la dirección, la contraseña y un enlace de acceso de un solo uso, y abre el navegador en la captura. Hasta 3 archivos, 3 GB en total; se leen donde están y nunca se modifican. No se recoge ni se envía nada, y no se resuelven nombres de host (`-dns` lo activa). Ctrl+C detiene y borra los datos importados. En una máquina de 2 núcleos, una captura de 1 GB está lista en unos 5 segundos (1,2 millones de paquetes grandes) a 30 segundos (14 millones de paquetes pequeños). + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +En la interfaz web de un traffic66 en marcha: 1. **Subir archivos de captura…**: `.pcap` o `.pcapng`, sin comprimir. Hasta 3 archivos, cada uno de 50 MB como máximo. Los archivos se convierten en flujos en una base de datos propia (`/sandbox/`); los datos en vivo, sus cifras y hallazgos no se tocan. 2. **Analizar**: todas las páginas (resumen, Top 66, detalles del tráfico, hallazgos, rutas, mapa, registros de flujo) muestran los archivos durante todo su tiempo. Una barra naranja nombra los archivos; **Volver a los datos en vivo** vuelve. Cada archivo aparece como un dispositivo, así que el cuadro **Dispositivo** muestra un archivo cada vez. diff --git a/docs/README.fr.md b/docs/README.fr.md index 1e74798..1fa995a 100644 --- a/docs/README.fr.md +++ b/docs/README.fr.md @@ -24,6 +24,7 @@ terminal. interface et réseau (AS), chemins du trafic, pays, correspondances avec des listes de menaces, enregistrements de flux, encapsulation (GRE, IPIP, VXLAN, GENEVE, MPLS). +- `traffic66 capture.pcap` ouvre jusqu'à 3 captures de paquets (3 Go au total) dans l'interface web : flux, détections, pays et enregistrements sur toute la capture, sans rien configurer. - 13 langues dans l'interface web et dans l'interface terminal. ![Vue d'ensemble : détections ouvertes, bande passante par application comparée à la semaine dernière, principaux clients et services](images/overview.png) @@ -803,7 +804,30 @@ La même vue d'ensemble en chinois ; toutes les pages sont disponibles en 13 lan ### Analyse hors ligne -**Analyse hors ligne** affiche des captures Wireshark ou tcpdump avec les mêmes pages que les données en direct, sans les mélanger : +**Analyse hors ligne** affiche des captures Wireshark ou tcpdump avec les mêmes pages que les données en direct, sans les mélanger. + +Il résume tous les paquets en flux : qui a parlé à qui, combien, quand, et ce qui ressemble à une attaque. Il ne décode pas les protocoles et ne montre pas le contenu des paquets ; pour un paquet ou un flux TCP, utilisez Wireshark. + +En ligne de commande, sans rien configurer : + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 démarre sur cet ordinateur seulement (127.0.0.1, un port libre), affiche l'adresse, le mot de passe et un lien de connexion à usage unique, et ouvre le navigateur sur la capture. Jusqu'à 3 fichiers, 3 Go au total ; ils sont lus là où ils sont et jamais modifiés. Rien n'est collecté ni envoyé, et les noms d'hôte ne sont pas résolus (`-dns` l'active). Ctrl+C arrête et efface les données importées. Sur une machine à 2 cœurs, une capture de 1 Go est prête en 5 secondes environ (1,2 million de paquets pleine taille) à 30 secondes (14 millions de petits paquets). + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +Dans l'interface web d'un traffic66 en marche : 1. **Importer des fichiers de capture…** : `.pcap` ou `.pcapng`, non compressés. Jusqu'à 3 fichiers de 50 Mo au plus chacun. Les fichiers sont transformés en flux dans une base à part (`/sandbox/`) ; les données en direct, leurs chiffres et détections ne sont pas touchés. 2. **Analyser** : toutes les pages (vue d'ensemble, Top 66, détails du trafic, détections, chemins, carte, enregistrements de flux) montrent les fichiers sur toute leur durée. Une barre orange nomme les fichiers ; **Retour aux données en direct** revient. Chaque fichier apparaît comme un équipement : le champ **Équipement** montre un fichier à la fois. diff --git a/docs/README.hi.md b/docs/README.hi.md index f6c679d..cf256fd 100644 --- a/docs/README.hi.md +++ b/docs/README.hi.md @@ -20,6 +20,7 @@ counters से मेल खाते हैं — web UI में भी औ - Top 66 सूचियाँ, client, server, service, interface और नेटवर्क (AS) के हिसाब से समय के साथ ट्रैफ़िक, flow के रास्ते, देश, threat list के matches, flow records, encapsulation (GRE, IPIP, VXLAN, GENEVE, MPLS)। +- `traffic66 capture.pcap` अधिकतम 3 पैकेट कैप्चर (कुल 3 GB) वेब UI में खोलता है: पूरे कैप्चर के फ़्लो, निष्कर्ष, देश और फ़्लो रिकॉर्ड, बिना किसी सेटअप के। - web UI और terminal UI में 13 भाषाएँ। ![सारांश: खुली संदिग्ध गतिविधियाँ, पिछले हफ़्ते की तुलना में application के हिसाब से bandwidth, top clients और services](images/overview.png) @@ -761,7 +762,30 @@ internet scanner के। ### ऑफ़लाइन विश्लेषण -**ऑफ़लाइन विश्लेषण** Wireshark या tcpdump के कैप्चर को लाइव डेटा वाले पेजों पर ही दिखाता है, बिना उन्हें मिलाए: +**ऑफ़लाइन विश्लेषण** Wireshark या tcpdump के कैप्चर को लाइव डेटा वाले पेजों पर ही दिखाता है, बिना उन्हें मिलाए। + +यह सभी पैकेटों को फ़्लो में समेटता है: किसने किससे, कितनी, कब बात की, और क्या हमले जैसा दिखता है। यह प्रोटोकॉल डिकोड नहीं करता और पैकेट की सामग्री नहीं दिखाता; एक पैकेट या एक TCP स्ट्रीम देखने के लिए Wireshark इस्तेमाल करें। + +कमांड लाइन से, बिना कोई सेटअप किए: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 सिर्फ़ इसी कंप्यूटर पर (127.0.0.1, कोई खाली पोर्ट) शुरू होता है, पता, पासवर्ड और एक बार चलने वाला साइन-इन लिंक छापता है, और ब्राउज़र में कैप्चर खोल देता है। अधिकतम 3 फ़ाइलें, कुल 3 GB; फ़ाइलें अपनी जगह से पढ़ी जाती हैं और कभी बदली नहीं जातीं। कुछ भी इकट्ठा या भेजा नहीं जाता, और होस्ट नाम नहीं खोजे जाते (`-dns` से चालू करें)। Ctrl+C रोकता है और आयात किया डेटा मिटा देता है। 2-कोर मशीन पर 1 GB का कैप्चर लगभग 5 सेकंड (12 लाख पूरे आकार के पैकेट) से 30 सेकंड (1.4 करोड़ छोटे पैकेट) में तैयार होता है। + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +चल रहे traffic66 के वेब UI में: 1. **कैप्चर फ़ाइलें अपलोड करें…**: `.pcap` या `.pcapng`, संपीड़ित नहीं। अधिकतम 3 फ़ाइलें, हर एक 50 MB तक। फ़ाइलें फ़्लो में बदलकर अलग डेटाबेस (`/sandbox/`) में जाती हैं; लाइव डेटा, उसके आँकड़े और निष्कर्ष नहीं बदलते। 2. **विश्लेषण करें**: सभी पेज (अवलोकन, Top 66, ट्रैफ़िक विवरण, निष्कर्ष, फ़्लो पथ, मानचित्र, फ़्लो रिकॉर्ड) कैप्चर फ़ाइलों का पूरा समय दिखाते हैं। नारंगी पट्टी फ़ाइलों के नाम बताती है; **लाइव डेटा पर लौटें** से लौटें। हर फ़ाइल एक डिवाइस की तरह दिखती है, इसलिए **डिवाइस** बॉक्स से एक-एक फ़ाइल देखी जा सकती है। diff --git a/docs/README.id.md b/docs/README.id.md index dacbb57..75f9a99 100644 --- a/docs/README.id.md +++ b/docs/README.id.md @@ -21,6 +21,7 @@ antarmuka terminal. - Daftar Top 66, trafik dari waktu ke waktu per klien, server, layanan, interface, dan jaringan (AS), jalur trafik, negara, kecocokan dengan daftar ancaman, catatan flow, enkapsulasi (GRE, IPIP, VXLAN, GENEVE, MPLS). +- `traffic66 capture.pcap` membuka hingga 3 tangkapan paket (total 3 GB) di UI web: flow, temuan, negara, dan catatan flow untuk seluruh tangkapan, tanpa pengaturan. - 13 bahasa di antarmuka web dan antarmuka terminal. ![Ringkasan: temuan terbuka, bandwidth per aplikasi dibanding minggu lalu, klien dan layanan teratas](images/overview.png) @@ -780,7 +781,30 @@ Ringkasan yang sama dalam bahasa Tionghoa; setiap halaman tersedia dalam 13 baha ### Analisis offline -**Analisis offline** menampilkan tangkapan Wireshark atau tcpdump dengan halaman yang sama seperti data langsung, tanpa mencampurnya: +**Analisis offline** menampilkan tangkapan Wireshark atau tcpdump dengan halaman yang sama seperti data langsung, tanpa mencampurnya. + +traffic66 merangkum semua paket menjadi flow: siapa bicara dengan siapa, berapa banyak, kapan, dan apa yang tampak seperti serangan. Ia tidak mendekode protokol atau menampilkan isi paket; untuk satu paket atau satu aliran TCP, gunakan Wireshark. + +Dari baris perintah, tanpa pengaturan apa pun: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 berjalan hanya di komputer ini (127.0.0.1, port kosong), mencetak alamat, kata sandi, dan tautan masuk sekali pakai, lalu membuka tangkapan di browser. Maksimal 3 file, total 3 GB; file dibaca di tempatnya dan tidak pernah diubah. Tidak ada yang dikumpulkan atau dikirim, dan nama host tidak dicari (`-dns` menyalakannya). Ctrl+C menghentikan dan menghapus data yang diimpor. Di mesin 2 inti, tangkapan 1 GB siap dalam sekitar 5 detik (1,2 juta paket ukuran penuh) hingga 30 detik (14 juta paket kecil). + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +Di UI web traffic66 yang sedang berjalan: 1. **Unggah file tangkapan…**: `.pcap` atau `.pcapng`, tidak dikompresi. Maksimal 3 file, masing-masing paling besar 50 MB. File diubah menjadi flow di basis data tersendiri (`/sandbox/`); data langsung, angka, dan temuannya tidak tersentuh. 2. **Analisis**: semua halaman (ringkasan, Top 66, detail trafik, temuan, jalur, peta, catatan flow) menampilkan file untuk seluruh waktunya. Bilah oranye menyebut nama file; **Kembali ke data langsung** kembali. Tiap file tampil sebagai perangkat, jadi kotak **Perangkat** menampilkan satu file sekaligus. diff --git a/docs/README.ja.md b/docs/README.ja.md index 63fa012..981a4b7 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -9,6 +9,7 @@ sFlow・NetFlow・IPFIX のフロー分析を 1 つのプログラムで行い - 自身の集計値をインターフェースカウンター(sFlow カウンターまたは SNMP)と照合し、ずれがあればその理由を示します。 - フローからスキャン、パスワード総当たり、横展開、不審なアップロード、フラッド、脅威リストとの通信を見つけ出し(サンプリングされたフローでも可能)、対応すべき検知として一覧にします。 - Top 66 ランキング、クライアント・サーバー・サービス・インターフェース・ネットワーク(AS)別のトラフィックの推移、フローの流れ、国、脅威リストとの一致、フローレコード、カプセル化(GRE、IPIP、VXLAN、GENEVE、MPLS)。 +- `traffic66 capture.pcap` で最大 3 個(合計 3 GB)のキャプチャを Web UI で開き、キャプチャ全体のフロー、検出、国、フローレコードを見られます。設定は不要です。 - Web UI とターミナル UI は 13 言語に対応しています。 ![概要:未対応の検知、アプリケーション別の帯域と先週との比較、上位のクライアントとサービス](images/overview.png) @@ -616,7 +617,30 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh ### オフライン分析 -**オフライン分析** では Wireshark や tcpdump のキャプチャを、ライブデータと混ぜずに同じページで見られます: +**オフライン分析** では Wireshark や tcpdump のキャプチャを、ライブデータと混ぜずに同じページで見られます。 + +すべてのパケットをフローにまとめます:誰が誰と、どれだけ、いつ通信し、何が攻撃らしいか。プロトコルのデコードやパケットの中身の表示はしません。1 パケットや 1 本の TCP ストリームを見るには Wireshark を使ってください。 + +コマンドラインから、設定なしで開けます: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 はこのコンピューターだけで起動し(127.0.0.1、空いているポート)、アドレス、パスワード、1 回限りのサインインリンクを表示して、ブラウザでキャプチャを開きます。最大 3 ファイル、合計 3 GB。ファイルはその場で読み、変更しません。何も収集・送信せず、ホスト名の逆引きもしません(`-dns` で有効)。Ctrl+C で停止し、取り込んだデータを削除します。2 コアのマシンで 1 GB のキャプチャは約 5 秒(フルサイズ 120 万パケット)から 30 秒(小さな 1,400 万パケット)で見られます。 + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +動作中の traffic66 の Web UI では: 1. **キャプチャファイルをアップロード…**:`.pcap` または `.pcapng`(圧縮不可)。最大 3 ファイル、各 50 MB まで。ファイルはフローに変換されて専用のデータベース(`/sandbox/`)に入り、ライブデータや集計、検出結果には影響しません。 2. **分析**:全ページ(概要、Top 66、トラフィック詳細、検出、フローパス、地図、フローレコード)がキャプチャの全期間を表示します。オレンジの帯にファイル名が出て、**ライブデータに戻る** で戻ります。各ファイルは 1 台の機器として扱われるので、**機器** 欄で 1 ファイルずつ見られます。 diff --git a/docs/README.ko.md b/docs/README.ko.md index e817d9e..4992255 100644 --- a/docs/README.ko.md +++ b/docs/README.ko.md @@ -17,6 +17,7 @@ sFlow, NetFlow, IPFIX 플로 분석을 하나의 프로그램으로 처리합니 - Top 66 목록, 클라이언트, 서버, 서비스, 인터페이스, 네트워크(AS)별 시간에 따른 트래픽, 트래픽 경로, 국가, 위협 목록 일치 항목, 플로 레코드, 캡슐화(GRE, IPIP, VXLAN, GENEVE, MPLS). +- `traffic66 capture.pcap`로 최대 3개(합계 3 GB)의 캡처를 웹 UI에서 열어 캡처 전체의 플로, 탐지, 국가, 플로 레코드를 봅니다. 설정할 것이 없습니다. - 웹 UI와 터미널 UI 모두 13개 언어를 지원합니다. ![개요: 미처리 탐지, 지난주와 비교한 애플리케이션별 대역폭, 상위 클라이언트와 서비스](images/overview.png) @@ -729,7 +730,30 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh ### 오프라인 분석 -**오프라인 분석**는 Wireshark나 tcpdump 캡처를 실시간 데이터와 섞지 않고 같은 페이지로 보여 줍니다: +**오프라인 분석**는 Wireshark나 tcpdump 캡처를 실시간 데이터와 섞지 않고 같은 페이지로 보여 줍니다. + +모든 패킷을 플로로 요약합니다: 누가 누구와, 얼마나, 언제 통신했고 무엇이 공격처럼 보이는지. 프로토콜을 해석하거나 패킷 내용을 보여 주지는 않습니다. 패킷 하나나 TCP 스트림 하나를 보려면 Wireshark를 쓰세요. + +명령줄에서 설정 없이 바로 엽니다: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66은 이 컴퓨터에서만(127.0.0.1, 빈 포트) 실행되며, 주소와 비밀번호, 한 번만 쓰는 로그인 링크를 출력하고 브라우저에서 캡처를 엽니다. 최대 3개 파일, 합계 3 GB. 파일은 그 자리에서 읽으며 바꾸지 않습니다. 아무것도 수집하거나 보내지 않고, 호스트 이름도 조회하지 않습니다(`-dns`로 켬). Ctrl+C로 멈추면 가져온 데이터를 지웁니다. 2코어 컴퓨터에서 1 GB 캡처는 약 5초(최대 크기 패킷 120만 개)에서 30초(작은 패킷 1,400만 개)면 준비됩니다. + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +실행 중인 traffic66의 웹 UI에서는: 1. **캡처 파일 업로드…**: `.pcap` 또는 `.pcapng`(압축 불가). 최대 3개 파일, 각각 50 MB 이하. 파일은 플로로 바뀌어 별도 데이터베이스(`/sandbox/`)에 들어가며, 실시간 데이터와 집계, 탐지 결과에는 영향이 없습니다. 2. **분석**: 모든 페이지(개요, Top 66, 트래픽 상세, 탐지, 플로 경로, 지도, 플로 레코드)가 캡처의 전체 기간을 보여 줍니다. 주황색 막대에 파일 이름이 나오고, **실시간 데이터로 돌아가기**로 돌아갑니다. 각 파일은 장비 하나로 나타나므로 **장비** 칸으로 파일을 하나씩 볼 수 있습니다. diff --git a/docs/README.pt.md b/docs/README.pt.md index cb38982..91ac1ce 100644 --- a/docs/README.pt.md +++ b/docs/README.pt.md @@ -21,6 +21,7 @@ equipamentos, em uma interface web e em uma interface de terminal. interface e rede (AS), caminhos do tráfego, países, ocorrências em listas de ameaças, registros de fluxo, encapsulamento (GRE, IPIP, VXLAN, GENEVE, MPLS). +- `traffic66 captura.pcap` abre até 3 capturas de pacotes (3 GB no total) na interface web: fluxos, detecções, países e registros de toda a captura, sem configurar nada. - 13 idiomas na interface web e na interface de terminal. ![Visão geral: detecções abertas, banda por aplicação em comparação com a semana passada, principais clientes e serviços](images/overview.png) @@ -779,7 +780,30 @@ A mesma visão geral em chinês; todas as páginas estão disponíveis em 13 idi ### Análise offline -**Análise offline** mostra capturas do Wireshark ou tcpdump com as mesmas páginas dos dados ao vivo, sem misturá-las: +**Análise offline** mostra capturas do Wireshark ou tcpdump com as mesmas páginas dos dados ao vivo, sem misturá-las. + +Ele resume todos os pacotes em fluxos: quem falou com quem, quanto, quando e o que parece um ataque. Não decodifica protocolos nem mostra o conteúdo dos pacotes; para um pacote ou um fluxo TCP, use o Wireshark. + +Pela linha de comando, sem configurar nada: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +O traffic66 inicia só neste computador (127.0.0.1, uma porta livre), mostra o endereço, a senha e um link de acesso de uso único, e abre o navegador na captura. Até 3 arquivos, 3 GB no total; eles são lidos onde estão e nunca alterados. Nada é coletado nem enviado, e nomes de host não são consultados (`-dns` liga isso). Ctrl+C para e apaga os dados importados. Numa máquina de 2 núcleos, uma captura de 1 GB fica pronta em cerca de 5 segundos (1,2 milhão de pacotes grandes) a 30 segundos (14 milhões de pacotes pequenos). + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +Na interface web de um traffic66 em execução: 1. **Enviar arquivos de captura…**: `.pcap` ou `.pcapng`, sem compressão. Até 3 arquivos, cada um com no máximo 50 MB. Os arquivos viram fluxos num banco próprio (`/sandbox/`); os dados ao vivo, seus números e detecções não são afetados. 2. **Analisar**: todas as páginas (visão geral, Top 66, detalhes do tráfego, detecções, caminhos, mapa, registros de fluxo) mostram os arquivos durante todo o seu período. Uma barra laranja nomeia os arquivos; **Voltar aos dados ao vivo** volta. Cada arquivo aparece como um dispositivo, então o campo **Equipamento** mostra um arquivo por vez. diff --git a/docs/README.ru.md b/docs/README.ru.md index 457b38f..cab5990 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -20,6 +20,7 @@ - Списки Top 66, трафик во времени по клиентам, серверам, сервисам, интерфейсам и сетям (AS), пути трафика, страны, совпадения со списками угроз, записи потоков, инкапсуляция (GRE, IPIP, VXLAN, GENEVE, MPLS). +- `traffic66 capture.pcap` открывает до 3 захватов (всего до 3 ГБ) в веб-интерфейсе: потоки, находки, страны и записи за весь захват, без настройки. - 13 языков в веб-интерфейсе и в терминальном интерфейсе. ![Обзор: открытые обнаружения, полоса по приложениям в сравнении с прошлой неделей, основные клиенты и сервисы](images/overview.png) @@ -773,7 +774,30 @@ traffic66 учится на уже имеющейся истории. ### Офлайн-анализ -**Офлайн-анализ** показывает захваты Wireshark или tcpdump на тех же страницах, что и живые данные, не смешивая их: +**Офлайн-анализ** показывает захваты Wireshark или tcpdump на тех же страницах, что и живые данные, не смешивая их. + +Он сводит все пакеты в потоки: кто с кем общался, сколько, когда и что похоже на атаку. Он не разбирает протоколы и не показывает содержимое пакетов; для одного пакета или одного TCP-потока используйте Wireshark. + +Из командной строки, без настройки: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 запускается только на этом компьютере (127.0.0.1, свободный порт), выводит адрес, пароль и одноразовую ссылку для входа и открывает захват в браузере. До 3 файлов, всего до 3 ГБ; файлы читаются на месте и не изменяются. Ничего не собирается и не отправляется, имена хостов не запрашиваются (`-dns` включает это). Ctrl+C останавливает и удаляет импортированные данные. На 2-ядерной машине захват в 1 ГБ готов примерно за 5 секунд (1,2 млн полноразмерных пакетов) — 30 секунд (14 млн маленьких пакетов). + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +В веб-интерфейсе работающего traffic66: 1. **Загрузить файлы захвата…**: `.pcap` или `.pcapng`, без сжатия. До 3 файлов, каждый не больше 50 МБ. Файлы превращаются в потоки в отдельной базе (`/sandbox/`); живые данные, их статистика и находки не затрагиваются. 2. **Анализ**: все страницы (обзор, Top 66, детали трафика, находки, пути, карта, записи потоков) показывают файлы за всё их время. Оранжевая полоса называет файлы; **Вернуться к живым данным** возвращает обратно. Каждый файл выглядит как устройство, поэтому поле **Устройство** показывает по одному файлу. diff --git a/docs/README.ur.md b/docs/README.ur.md index c12ae9e..c8b1da3 100644 --- a/docs/README.ur.md +++ b/docs/README.ur.md @@ -22,6 +22,7 @@ embedded database میں رکھتا ہے، اور دکھاتا ہے کہ bandwid - Top 66 فہرستیں، client، server، service، interface اور نیٹ ورک (AS) کے لحاظ سے وقت کے ساتھ ٹریفک، flow کے راستے، ممالک، threat list کے matches، flow records، encapsulation (GRE، IPIP، VXLAN، GENEVE، MPLS)۔ +- `traffic66 capture.pcap` زیادہ سے زیادہ 3 پیکٹ کیپچر (کل 3 GB) ویب UI میں کھولتا ہے: پورے کیپچر کے فلو، نتائج، ممالک اور فلو ریکارڈز، بغیر کسی سیٹ اپ کے۔ - web UI اور terminal UI میں 13 زبانیں۔ ![جائزہ: کھلی مشتبہ سرگرمیاں، پچھلے ہفتے کے مقابلے میں application کے حساب سے bandwidth، سرفہرست clients اور services](images/overview.png) @@ -762,7 +763,30 @@ packets بھیجتا ہے کہ نظر نہیں آتا۔ ڈیمو کا حملہ ### آف لائن تجزیہ -**آف لائن تجزیہ** Wireshark یا tcpdump کے کیپچر کو لائیو ڈیٹا والے صفحات پر ہی دکھاتا ہے، انہیں ملائے بغیر: +**آف لائن تجزیہ** Wireshark یا tcpdump کے کیپچر کو لائیو ڈیٹا والے صفحات پر ہی دکھاتا ہے، انہیں ملائے بغیر۔ + +یہ تمام پیکٹوں کو فلو میں سمیٹتا ہے: کس نے کس سے، کتنی، کب بات کی، اور کیا حملے جیسا لگتا ہے۔ یہ پروٹوکول ڈی کوڈ نہیں کرتا اور پیکٹ کا مواد نہیں دکھاتا؛ ایک پیکٹ یا ایک TCP اسٹریم دیکھنے کے لیے Wireshark استعمال کریں۔ + +کمانڈ لائن سے، بغیر کسی سیٹ اپ کے: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 صرف اسی کمپیوٹر پر (127.0.0.1، کوئی خالی پورٹ) شروع ہوتا ہے، پتا، پاس ورڈ اور ایک بار چلنے والا سائن اِن لنک دکھاتا ہے، اور براؤزر میں کیپچر کھول دیتا ہے۔ زیادہ سے زیادہ 3 فائلیں، کل 3 GB؛ فائلیں اپنی جگہ سے پڑھی جاتی ہیں اور کبھی بدلی نہیں جاتیں۔ کچھ بھی جمع یا بھیجا نہیں جاتا، اور ہوسٹ نام نہیں ڈھونڈے جاتے (`-dns` سے چالو کریں)۔ Ctrl+C روکتا ہے اور امپورٹ کیا گیا ڈیٹا مٹا دیتا ہے۔ 2 کور مشین پر 1 GB کا کیپچر تقریباً 5 سیکنڈ (12 لاکھ پورے سائز کے پیکٹ) سے 30 سیکنڈ (1.4 کروڑ چھوٹے پیکٹ) میں تیار ہوتا ہے۔ + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +چلتے ہوئے traffic66 کے ویب UI میں: 1. **کیپچر فائلیں اپ لوڈ کریں…**: ‎`.pcap` یا ‎`.pcapng`، کمپریس شدہ نہیں۔ زیادہ سے زیادہ 3 فائلیں، ہر ایک 50 MB تک۔ فائلیں فلو میں بدل کر الگ ڈیٹابیس (`/sandbox/`) میں جاتی ہیں؛ لائیو ڈیٹا، اس کے اعداد اور نتائج متاثر نہیں ہوتے۔ 2. **تجزیہ کریں**: تمام صفحات (جائزہ، Top 66، ٹریفک کی تفصیل، نتائج، فلو راستے، نقشہ، فلو ریکارڈز) کیپچر فائلوں کا پورا وقت دکھاتے ہیں۔ نارنجی پٹی فائلوں کے نام بتاتی ہے؛ **لائیو ڈیٹا پر واپس** سے واپس جائیں۔ ہر فائل ایک ڈیوائس کی طرح دکھتی ہے، اس لیے **آلہ** خانے سے ایک ایک فائل دیکھی جا سکتی ہے۔ diff --git a/docs/README.zh.md b/docs/README.zh.md index fe2cdb5..b6778d3 100644 --- a/docs/README.zh.md +++ b/docs/README.zh.md @@ -9,6 +9,7 @@ - 用接口计数器(sFlow 计数器或 SNMP)校验自己的统计结果,对不上时会说明原因。 - 从流数据中找出扫描、暴力破解、横向移动、异常上传、泛洪和威胁情报流量(采样数据同样适用),并列为待处理的发现。 - Top 66 排行、按客户端、服务器、服务、接口和网络(AS)划分的流量随时间变化、流向、国家、威胁情报命中、流记录、封装(GRE、IPIP、VXLAN、GENEVE、MPLS)。 +- `traffic66 capture.pcap` 直接打开最多 3 个抓包文件(总共 3 GB),在 Web 界面里看整个抓包的流、发现、国家和流记录,无需任何配置。 - Web 界面和终端界面均支持 13 种语言。 ![概览:未处理的发现、按应用划分的带宽与上周对比、主要客户端和服务](images/overview.png) @@ -616,7 +617,30 @@ curl -L https://www.spamhaus.org/drop/drop.txt -o /threats/spamh ### 离线分析 -**离线分析** 用和实时数据相同的页面查看 Wireshark 或 tcpdump 的抓包文件,但不混进实时数据: +**离线分析** 用和实时数据相同的页面查看 Wireshark 或 tcpdump 的抓包文件,但不混进实时数据。 + +它把所有包汇总成流:谁和谁通信、多少、什么时候,以及哪些像是攻击。它不解码协议,也不显示包内容;要看单个包或单条 TCP 流,请用 Wireshark。 + +在命令行里直接打开,不需要任何配置: + +``` +traffic66 office.pcap +traffic66 a.pcap b.pcapng c.pcap +``` + +traffic66 只在本机启动(127.0.0.1,随机空闲端口),打印地址、密码和一次性登录链接,并自动在浏览器里打开抓包。最多 3 个文件,总共 3 GB;文件在原处读取,不会被修改。不采集也不发送任何数据,也不做主机名反查(加 `-dns` 开启)。按 Ctrl+C 停止并删除导入的数据。在 2 核机器上,1 GB 的抓包约 5 秒(120 万个满长包)到 30 秒(1400 万个小包)可以看结果。 + +``` +$ traffic66 office.pcap + +traffic66 0.3.1: analysing 1 capture file(s); nothing is collected or sent + Web UI http://127.0.0.1:38217 (port 38217, this computer only) + Sign in user admin, password gfhfhbuutz2e + Open http://127.0.0.1:38217/auto?t=b9388f… (signs in once) + Stop Ctrl+C; the imported data is deleted, your files are kept +``` + +在运行中的 traffic66 的 Web 界面里: 1. **上传抓包文件…**:`.pcap` 或 `.pcapng`,不支持压缩包。最多 3 个文件,每个不超过 50 MB。文件被转换成流,存进单独的数据库(`/sandbox/`),实时数据、统计和发现都不受影响。 2. **分析**:所有页面(概览、Top 66、流量明细、发现、流向、地图、流记录)都改为显示抓包文件的整个时间段。橙色横条列出文件名,点 **返回实时数据** 返回。每个文件显示为一台设备,用 **设备** 筛选框可以只看一个文件。 diff --git a/internal/api/handlers.go b/internal/api/handlers.go index c8a7c6f..827d0b1 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -570,7 +570,7 @@ func (s *Server) status(w http.ResponseWriter, r *http.Request) { "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, "findings_open": s.Store.OpenFindings(2, time.Now().Add(-24*time.Hour)), "hosts": names, "skewed": s.Col.Skewed.Load(), "go": runtime.Version(), + "source_warnings": warn, "offline": s.Offline, "findings_open": s.Store.OpenFindings(2, time.Now().Add(-24*time.Hour)), "hosts": names, "skewed": s.Col.Skewed.Load(), "go": runtime.Version(), }) } diff --git a/internal/api/sandbox.go b/internal/api/sandbox.go index 02009ba..bf58ebc 100644 --- a/internal/api/sandbox.go +++ b/internal/api/sandbox.go @@ -46,13 +46,13 @@ func (s *Server) putSandboxFile(w http.ResponseWriter, r *http.Request) { fail(w, errors.New("offline analysis is not available")) return } - if r.ContentLength > sandbox.MaxFileSize { - writeJSON(w, http.StatusRequestEntityTooLarge, map[string]any{"error": "too_big", "max": sandbox.MaxFileSize}) + if r.ContentLength > s.SB.Lim.FileSize { + writeJSON(w, http.StatusRequestEntityTooLarge, map[string]any{"error": "too_big", "max": s.SB.Lim.FileSize}) return } f, err := s.SB.Add(r.URL.Query().Get("name"), r.Body, false) if errors.Is(err, sandbox.ErrLimit) { - writeJSON(w, http.StatusRequestEntityTooLarge, map[string]any{"error": err.Error(), "max": sandbox.MaxFileSize, "max_files": sandbox.MaxFiles}) + writeJSON(w, http.StatusRequestEntityTooLarge, map[string]any{"error": err.Error(), "max": s.SB.Lim.FileSize, "max_files": s.SB.Lim.Files}) return } if errors.Is(err, sandbox.ErrNotCapture) { diff --git a/internal/api/server.go b/internal/api/server.go index 0c33375..f11008a 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -27,26 +27,29 @@ import ( // Server wires the API together. type Server struct { - Store *store.Store - Pipe *pipeline.Pipeline - Col *collector.Collector - Inv *enrich.Inventory - ASN *enrich.ASNDB - Thr *enrich.Threats - DNS *dnsres.Resolver - Det *detect.Detector - SB *sandbox.Sandbox // offline analysis of capture files - Static fs.FS - Version string - Demo bool - Users map[string]string // fixed passwords (tests) - Check func(user, pw string) bool // login check; replaces Users when set - Exists func(user string) bool // whether a user still exists; signed-in sessions of deleted users end - LocalTok string // token for the TUI on this machine - Capture func() []CaptureInfo - SNMP func() []snmp.Status - Started time.Time - DataDir string + Store *store.Store + Pipe *pipeline.Pipeline + Col *collector.Collector + Inv *enrich.Inventory + ASN *enrich.ASNDB + Thr *enrich.Threats + DNS *dnsres.Resolver + Det *detect.Detector + SB *sandbox.Sandbox // offline analysis of capture files + Offline bool // started on capture files (traffic66 file.pcap): no live data + // AutoLogin is a one-time token: /auto?t= signs the browser in once. + AutoLogin string + Static fs.FS + Version string + Demo bool + Users map[string]string // fixed passwords (tests) + Check func(user, pw string) bool // login check; replaces Users when set + Exists func(user string) bool // whether a user still exists; signed-in sessions of deleted users end + LocalTok string // token for the TUI on this machine + Capture func() []CaptureInfo + SNMP func() []snmp.Status + Started time.Time + DataDir string mu sync.Mutex sessions map[string]session @@ -81,6 +84,7 @@ func (s *Server) Handler() http.Handler { mux := http.NewServeMux() mux.HandleFunc("POST /api/login", s.login) mux.HandleFunc("POST /api/logout", s.logout) + mux.HandleFunc("GET /auto", s.autoLogin) mux.HandleFunc("GET /logo", s.logo) api := func(pattern string, h http.HandlerFunc) { mux.Handle(pattern, s.auth(h)) } api("GET /api/status", s.status) @@ -282,6 +286,24 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"user": in.User}) } +// autoLogin signs in with the one-time token printed for traffic66 file.pcap +// and opens the offline analysis. +func (s *Server) autoLogin(w http.ResponseWriter, r *http.Request) { + t := r.URL.Query().Get("t") + s.mu.Lock() + ok := s.AutoLogin != "" && subtle.ConstantTimeCompare([]byte(t), []byte(s.AutoLogin)) == 1 + if ok { + s.AutoLogin = "" // once + b := make([]byte, 24) + rand.Read(b) + tok := hex.EncodeToString(b) + s.sessions[tok] = session{"admin", time.Now().Add(12 * time.Hour)} + http.SetCookie(w, &http.Cookie{Name: cookieName, Value: tok, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode}) + } + s.mu.Unlock() + http.Redirect(w, r, "/#v=sandbox", http.StatusSeeOther) +} + func (s *Server) logout(w http.ResponseWriter, r *http.Request) { if c, err := r.Cookie(cookieName); err == nil { s.mu.Lock() diff --git a/internal/sandbox/sandbox.go b/internal/sandbox/sandbox.go index 461aa35..3121c8f 100644 --- a/internal/sandbox/sandbox.go +++ b/internal/sandbox/sandbox.go @@ -27,10 +27,18 @@ import ( "github.com/githubflyideas/traffic66/internal/store" ) -// Limits of the free edition. +// Limits bound what a sandbox takes in. +type Limits struct { + Files int `json:"max_files"` + FileSize int64 `json:"max_file_size"` + TotalSize int64 `json:"max_total"` +} + +// UploadLimits apply to files uploaded in the web UI; LocalLimits to files +// opened from the command line (traffic66 capture.pcap). var ( - MaxFiles = 3 - MaxFileSize = int64(50 << 20) + UploadLimits = Limits{Files: 3, FileSize: 50 << 20, TotalSize: 150 << 20} + LocalLimits = Limits{Files: 3, FileSize: 3e9, TotalSize: 3e9} ) // File is one capture file in the sandbox. @@ -46,7 +54,8 @@ type File struct { Last time.Time `json:"last"` Status string `json:"status"` // waiting, importing, done, error Error string `json:"error,omitempty"` - Exporter string `json:"exporter"` // the "device" the file's flows appear under + Exporter string `json:"exporter"` // the "device" the file's flows appear under + Path string `json:"path,omitempty"` // a file opened in place (never deleted); else it is in Dir/files } // Sandbox holds the uploaded files and their database. @@ -55,6 +64,13 @@ type Sandbox struct { Inv *enrich.Inventory // the live inventory: names of networks and hosts ASN *enrich.ASNDB Thr *enrich.Threats + Lim Limits + Mem float64 // share of memory for the sandbox database + + // work is held by an import for its whole run, and by a rebuild or + // close while it replaces the database, so the two never overlap + work sync.Mutex + closed bool mu sync.Mutex files []*File @@ -75,7 +91,12 @@ var ErrNotCapture = errors.New("not a capture file: use .pcap or .pcapng as save // New opens the sandbox in dir (creating nothing until a file is added) and // starts its import worker. func New(dir string, inv *enrich.Inventory, asn *enrich.ASNDB, thr *enrich.Threats) *Sandbox { - sb := &Sandbox{Dir: dir, Inv: inv, ASN: asn, Thr: thr, wake: make(chan struct{}, 1)} + return NewWith(dir, inv, asn, thr, UploadLimits, 0.05) +} + +// NewWith is New with other limits and a share of memory for the database. +func NewWith(dir string, inv *enrich.Inventory, asn *enrich.ASNDB, thr *enrich.Threats, lim Limits, mem float64) *Sandbox { + sb := &Sandbox{Dir: dir, Inv: inv, ASN: asn, Thr: thr, Lim: lim, Mem: mem, wake: make(chan struct{}, 1)} sb.load() go sb.worker() if len(sb.files) > 0 { @@ -92,7 +113,15 @@ func New(dir string, inv *enrich.Inventory, asn *enrich.ASNDB, thr *enrich.Threa } func (sb *Sandbox) pcapDir() string { return filepath.Join(sb.Dir, "files") } -func (sb *Sandbox) dbDir() string { return filepath.Join(sb.Dir, "db") } + +// filePath is where a file's packets are read from. +func (sb *Sandbox) filePath(f *File) string { + if f.Path != "" { + return f.Path + } + return filepath.Join(sb.pcapDir(), f.Name) +} +func (sb *Sandbox) dbDir() string { return filepath.Join(sb.Dir, "db") } func (sb *Sandbox) indexPath() string { return filepath.Join(sb.Dir, "files.json") } @@ -107,7 +136,7 @@ func (sb *Sandbox) load() { return } for _, f := range fs { - if _, err := os.Stat(filepath.Join(sb.pcapDir(), f.Name)); err == nil { + if _, err := os.Stat(sb.filePath(f)); err == nil { sb.files = append(sb.files, f) } } @@ -134,7 +163,11 @@ func (sb *Sandbox) save() { // open opens (or creates) the database; sb.mu held or not yet shared. func (sb *Sandbox) open() error { - st, err := store.Open(store.Options{Dir: sb.dbDir(), MemoryFraction: 0.05, Threads: 2}) + threads := 2 // beside live collection + if sb.Mem > 0.1 { + threads = 0 // on its own: all but one core + } + st, err := store.Open(store.Options{Dir: sb.dbDir(), MemoryFraction: sb.Mem, Threads: threads}) if err != nil { return err } @@ -173,19 +206,18 @@ func (sb *Sandbox) Store() (*store.Store, *detect.Detector, *enrich.Inventory) { // Info describes the sandbox for the UI. type Info struct { - Files []File `json:"files"` - Busy bool `json:"busy"` - First time.Time `json:"first"` - Last time.Time `json:"last"` - MaxFiles int `json:"max_files"` - MaxFileSize int64 `json:"max_file_size"` - Ready bool `json:"ready"` // has data to look at + Files []File `json:"files"` + Busy bool `json:"busy"` + First time.Time `json:"first"` + Last time.Time `json:"last"` + Limits + Ready bool `json:"ready"` // has data to look at } func (sb *Sandbox) Info() Info { sb.mu.Lock() defer sb.mu.Unlock() - in := Info{Files: []File{}, Busy: sb.busy, MaxFiles: MaxFiles, MaxFileSize: MaxFileSize} + in := Info{Files: []File{}, Busy: sb.busy, Limits: sb.Lim} for _, f := range sb.files { in.Files = append(in.Files, *f) if f.Flows == 0 { @@ -235,15 +267,10 @@ func (sb *Sandbox) cleanName(name string) string { // start like a pcap or pcapng file. func (sb *Sandbox) Add(name string, r io.Reader, sample bool) (*File, error) { sb.mu.Lock() - n := 0 - for _, f := range sb.files { - if !f.Sample { - n++ - } - } - if !sample && n >= MaxFiles { + room, err := sb.room(sample) + if err != nil { sb.mu.Unlock() - return nil, fmt.Errorf("%w: at most %d files; delete one first", ErrLimit, MaxFiles) + return nil, err } name = sb.cleanName(name) // reserve the name while the upload runs @@ -270,17 +297,13 @@ func (sb *Sandbox) Add(name string, r io.Reader, sample bool) (*File, error) { return fail(err) } out.Write(head) - limit := MaxFileSize - if sample { - limit = 1 << 30 - } - written, err := io.Copy(out, io.LimitReader(r, limit-4+1)) + written, err := io.Copy(out, io.LimitReader(r, room-4+1)) out.Close() if err != nil { return fail(err) } - if written+4 > limit { - return fail(fmt.Errorf("%w: a file may be at most %d MB", ErrLimit, MaxFileSize>>20)) + if written+4 > room { + return fail(sb.sizeErr()) } sb.mu.Lock() f.Size = written + 4 @@ -293,6 +316,74 @@ func (sb *Sandbox) Add(name string, r io.Reader, sample bool) (*File, error) { return f, nil } +// room returns how many bytes the next file may have; sb.mu held. +func (sb *Sandbox) room(sample bool) (int64, error) { + if sample { + return 1 << 30, nil + } + n, used := 0, int64(0) + for _, f := range sb.files { + if !f.Sample { + n++ + used += f.Size + } + } + if n >= sb.Lim.Files { + return 0, fmt.Errorf("%w: at most %d files; delete one first", ErrLimit, sb.Lim.Files) + } + room := min(sb.Lim.FileSize, sb.Lim.TotalSize-used) + if room <= 0 { + return 0, sb.sizeErr() + } + return room, nil +} + +func (sb *Sandbox) sizeErr() error { + return fmt.Errorf("%w: each file at most %d MB, %d MB in all", ErrLimit, sb.Lim.FileSize>>20, sb.Lim.TotalSize>>20) +} + +// AddPath imports a capture file where it is, without copying it; the file +// is never deleted. +func (sb *Sandbox) AddPath(path string) (*File, error) { + abs, err := filepath.Abs(path) + if err != nil { + return nil, err + } + fi, err := os.Stat(abs) + if err != nil { + return nil, err + } + if !fi.Mode().IsRegular() { + return nil, fmt.Errorf("%s is not a file", path) + } + in, err := os.Open(abs) + if err != nil { + return nil, err + } + head := make([]byte, 4) + _, err = io.ReadFull(in, head) + in.Close() + if err != nil || pcapfile.Format(head) == "" { + return nil, fmt.Errorf("%s: %w", path, ErrNotCapture) + } + sb.mu.Lock() + defer sb.mu.Unlock() + room, err := sb.room(false) + if err != nil { + return nil, err + } + if fi.Size() > room { + return nil, sb.sizeErr() + } + f := &File{Name: sb.cleanName(filepath.Base(abs)), Path: abs, Size: fi.Size(), Added: time.Now().UTC(), Status: "waiting"} + sb.files = append(sb.files, f) + f.Exporter = sb.exporterFor(f) + sb.save() + sb.refreshInventory() + sb.kick() + return f, nil +} + // exporterFor picks a free address 127.0.1.n for a file's flows. func (sb *Sandbox) exporterFor(f *File) string { used := map[string]bool{} @@ -321,26 +412,35 @@ func slicesDelete(fs []*File, f *File) []*File { // rebuilt from the remaining files. func (sb *Sandbox) Delete(name string) error { sb.mu.Lock() - var keep []*File found := name == "" + for _, f := range sb.files { + found = found || f.Name == name + } + if !found { + sb.mu.Unlock() + return errors.New("no such file") + } + sb.gen++ // a running import stops at its next check + sb.mu.Unlock() + + sb.work.Lock() + defer sb.work.Unlock() + sb.mu.Lock() + var keep []*File for _, f := range sb.files { if name == "" || f.Name == name { - found = true if f.Status == "uploading" { keep = append(keep, f) continue } - os.Remove(filepath.Join(sb.pcapDir(), f.Name)) + if f.Path == "" { + os.Remove(filepath.Join(sb.pcapDir(), f.Name)) + } continue } keep = append(keep, f) } - if !found { - sb.mu.Unlock() - return errors.New("no such file") - } sb.files = keep - sb.gen++ st := sb.st sb.st, sb.det = nil, nil for _, f := range sb.files { @@ -364,6 +464,12 @@ func (sb *Sandbox) Delete(name string) error { return nil } +func (sb *Sandbox) stale(gen int) bool { + sb.mu.Lock() + defer sb.mu.Unlock() + return gen != sb.gen +} + func (sb *Sandbox) kick() { select { case sb.wake <- struct{}{}: @@ -380,7 +486,13 @@ func (sb *Sandbox) worker() { // importNext imports one waiting file; it reports whether it did. func (sb *Sandbox) importNext() bool { + sb.work.Lock() + defer sb.work.Unlock() sb.mu.Lock() + if sb.closed { + sb.mu.Unlock() + return false + } var f *File for _, x := range sb.files { if x.Status == "waiting" { @@ -410,6 +522,9 @@ func (sb *Sandbox) importNext() bool { // the rules over the whole capture, window by window every := det.Cfg.Every for t := res.first.Truncate(every).Add(every); !t.After(res.last.Add(every)); t = t.Add(every) { + if sb.stale(gen) { + break + } if _, err := det.Run(t); err != nil { log.Printf("sandbox: detect: %v", err) break @@ -444,7 +559,7 @@ type result struct { func (sb *Sandbox) importFile(gen int, f *File, st *store.Store, inv *enrich.Inventory) (result, error) { var res result - in, err := os.Open(filepath.Join(sb.pcapDir(), f.Name)) + in, err := os.Open(sb.filePath(f)) if err != nil { return res, err } @@ -506,6 +621,12 @@ func (sb *Sandbox) importFile(gen int, f *File, st *store.Store, inv *enrich.Inv // Close closes the database. func (sb *Sandbox) Close() { + sb.mu.Lock() + sb.closed = true + sb.gen++ + sb.mu.Unlock() + sb.work.Lock() // wait for a running import to stop + defer sb.work.Unlock() sb.mu.Lock() st := sb.st sb.st = nil diff --git a/internal/sandbox/sandbox_test.go b/internal/sandbox/sandbox_test.go index c9d5545..4f9e1d2 100644 --- a/internal/sandbox/sandbox_test.go +++ b/internal/sandbox/sandbox_test.go @@ -89,10 +89,7 @@ func TestSampleImport(t *testing.T) { } func TestLimits(t *testing.T) { - old := MaxFileSize - MaxFileSize = 1000 - defer func() { MaxFileSize = old }() - sb := New(t.TempDir(), enrich.NewInventory(), enrich.NewASNDB(), enrich.NewThreats()) + sb := NewWith(t.TempDir(), enrich.NewInventory(), enrich.NewASNDB(), enrich.NewThreats(), Limits{Files: 3, FileSize: 1000, TotalSize: 3000}, 0.05) defer sb.Close() var small bytes.Buffer Sample(&small, time.Now()) @@ -132,3 +129,27 @@ func TestLimits(t *testing.T) { t.Errorf("name not cleaned: %v", err) } } + +// Files opened in place count against the total and are never deleted. +func TestAddPath(t *testing.T) { + dir := t.TempDir() + var buf bytes.Buffer + Sample(&buf, time.Now()) + p := filepath.Join(dir, "x.pcap") + os.WriteFile(p, buf.Bytes()[:800], 0o644) + sb := NewWith(filepath.Join(dir, "sb"), enrich.NewInventory(), enrich.NewASNDB(), enrich.NewThreats(), Limits{Files: 3, FileSize: 1000, TotalSize: 1500}, 0.05) + defer sb.Close() + if _, err := sb.AddPath(p); err != nil { + t.Fatal(err) + } + if _, err := sb.AddPath(p); !errors.Is(err, ErrLimit) { + t.Fatalf("over the total: %v", err) + } + wait(t, sb) + if err := sb.Delete(""); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(p); err != nil { + t.Errorf("the original file was deleted: %v", err) + } +} diff --git a/internal/web/static/app.js b/internal/web/static/app.js index 8671162..c198750 100644 --- a/internal/web/static/app.js +++ b/internal/web/static/app.js @@ -174,6 +174,8 @@ function writeHash(push) { const SB_DATA = new Set(['overview', 'topn', 'sankey', 'series', 'records', 'threats', 'findings']); const inSB = () => state.ds === 'sb' && sbInfo.ready; let sbInfo = {files: [], ready: false}; +// started as "traffic66 file.pcap": there is no live data, only the files +let offlineMode = false; async function api(path, extra = {}, filters = state.f) { if (inSB() && (path === 'ifaces' || path === 'recon')) return {ifaces: []}; const p = new URLSearchParams(inSB() && SB_DATA.has(path) ? {ds: 'sb', ...sbRange(), ...extra} : {range: state.r, ...extra}); @@ -468,7 +470,8 @@ views.overview = async (el) => { const sv = d.top_services || [], smax = Math.max(1, ...sv.map(r => r.wire)); el.innerHTML = `
-
${t('kpi.now')}
${fmtBps(d.now_bps)}
${change == null ? ' ' : esc(t(basis === 'week' ? 'kpi.vs_week' : 'kpi.vs_prev', {p: fmtPct(change, 0)}))}
+ ${inSB() ? `
${t('sb.avg')}
${fmtBps(tot.wire * 8 / (spanMs() / 1000))}
 
` + : `
${t('kpi.now')}
${fmtBps(d.now_bps)}
${change == null ? ' ' : esc(t(basis === 'week' ? 'kpi.vs_week' : 'kpi.vs_prev', {p: fmtPct(change, 0)}))}
`}
${t('kpi.total', {r: rangeLabel()})}
${fmtBytes(tot.wire)}
${esc(peakTxt)}
${t('kpi.hosts')}
${nf(tot.hosts)}
 
${t('kpi.peers')}
${nf(tot.peers)}
${esc(t('kpi.countries', {n: nf(tot.countries)}))}
@@ -921,7 +924,7 @@ views.sandbox = async (el) => {
${fs.length ? `` : ''}
-

${esc(t('sb.limits', {n: sbInfo.max_files, mb}))} ${esc(t('sb.formats'))}

`)}
`; +

${esc(sbInfo.max_total >= sbInfo.max_file_size * sbInfo.max_files ? t('sb.limits', {n: sbInfo.max_files, mb}) : sbInfo.max_total <= sbInfo.max_file_size ? t('sb.limits_total', {n: sbInfo.max_files, gb: fmtBytes(sbInfo.max_total)}) : t('sb.limits', {n: sbInfo.max_files, mb}) + ' ' + t('sb.limits_total', {n: sbInfo.max_files, gb: fmtBytes(sbInfo.max_total)}))} ${esc(t('sb.formats'))}

`)}
`; // the last upload problem stays shown after the page refreshes const msg = (ok, text) => { sbMsg = ok === false ? text : ''; const m = $('#sbMsg'); m.style.color = ok === null ? 'var(--ink-3)' : ok ? 'var(--good)' : 'var(--crit)'; m.textContent = text; }; if (sbMsg) msg(false, sbMsg); @@ -959,8 +962,11 @@ views.sandbox = async (el) => { } render(); }; - if (pending) setTimeout(() => { if (state.v === 'sandbox') render(); }, 1500); + if (pending) { setTimeout(() => { if (state.v === 'sandbox') render(); }, 1500); sbWaited = true; } + else if (offlineMode && sbInfo.ready && sbWaited) { sbWaited = false; state.ds = 'sb'; go('overview'); } }; +// in offline mode the first visit goes on to the overview once the files are in +let sbWaited = true; views.threats = async (el) => { const d = await api('threats', {limit: 66}); @@ -1201,7 +1207,7 @@ views.detail = async (el) => {
${t('kpi.total', {r: rangeLabel()})}
${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(inSB() ? 'sb.avg' : 'kpi.now')}
${fmtBps(inSB() ? tot.wire * 8 / (spanMs() / 1000) : 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)}))}
@@ -1227,7 +1233,7 @@ function renderSB() { $('#sbbar').hidden = !on; $('#range').hidden = on; $('#refresh').hidden = on; - document.querySelectorAll('#nav [data-v=ifaces], #nav [data-v=sources]').forEach(b => b.hidden = inSB()); + document.querySelectorAll('#nav [data-v=ifaces], #nav [data-v=sources]').forEach(b => b.hidden = inSB() || offlineMode); if (!on) return; const r = sbRange(), span = r.to - r.from; const df = new Intl.DateTimeFormat(LANG, {dateStyle: 'medium', timeStyle: 'short'}), tf = new Intl.DateTimeFormat(LANG, {timeStyle: 'short'}); @@ -1235,8 +1241,8 @@ function renderSB() { const names = sbInfo.files.filter(f => f.status === 'done').map(f => f.name); $('#sbbar').innerHTML = `${t('sb.banner')}${names.map(esc).join(' · ')} ${esc(df.format(r.from))} – ${esc(sameDay ? tf.format(r.to) : df.format(r.to))} (${esc(fmtDur(span))}) - `; - $('#sbBack').onclick = () => { state.ds = ''; render(); }; + ${offlineMode ? '' : ``}`; + if ($('#sbBack')) $('#sbBack').onclick = () => { state.ds = ''; render(); }; } function fmtDur(ms) { const m = Math.round(ms / 6e4); @@ -1245,6 +1251,7 @@ function fmtDur(ms) { async function loadSB() { try { const res = await fetch('/api/sandbox'); if (res.ok) sbInfo = await res.json(); } catch (e) {} if (state.ds && !sbInfo.ready) state.ds = ''; + if (offlineMode && sbInfo.ready) state.ds = 'sb'; // nothing else to show } async function render(push) { @@ -1274,6 +1281,8 @@ async function loadStatus() { const s = await res.json(); for (const [ip, n] of Object.entries(s.hosts || {})) names.set(ip, n); $('#demo').hidden = !s.demo; $('#demo').textContent = t('demo.badge'); + offlineMode = !!s.offline; + $('#self').hidden = offlineMode; // nothing is collected const live = s.records_per_sec > 0.2; $('#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)}))} diff --git a/internal/web/static/i18n/ar.json b/internal/web/static/i18n/ar.json index 0b667ef..fe344d8 100644 --- a/internal/web/static/i18n/ar.json +++ b/internal/web/static/i18n/ar.json @@ -411,5 +411,7 @@ "sb.range": "الالتقاط كاملاً", "sb.min": "{n} دقيقة", "sb.hours": "{n} ساعة", -"sb.not_capture": "{f} ليس ملف التقاط. استخدم ‎.pcap أو ‎.pcapng محفوظاً من Wireshark أو tcpdump." +"sb.not_capture": "{f} ليس ملف التقاط. استخدم ‎.pcap أو ‎.pcapng محفوظاً من Wireshark أو tcpdump.", +"sb.avg": "المعدل المتوسط", +"sb.limits_total": "حتى {n} ملفات، {gb} إجمالاً." } diff --git a/internal/web/static/i18n/bn.json b/internal/web/static/i18n/bn.json index 2c46cf9..3ef27b7 100644 --- a/internal/web/static/i18n/bn.json +++ b/internal/web/static/i18n/bn.json @@ -411,5 +411,7 @@ "sb.range": "পুরো ক্যাপচার", "sb.min": "{n} মিনিট", "sb.hours": "{n} ঘণ্টা", -"sb.not_capture": "{f} ক্যাপচার ফাইল নয়। Wireshark বা tcpdump-এ সংরক্ষিত .pcap বা .pcapng ব্যবহার করুন।" +"sb.not_capture": "{f} ক্যাপচার ফাইল নয়। Wireshark বা tcpdump-এ সংরক্ষিত .pcap বা .pcapng ব্যবহার করুন।", +"sb.avg": "গড় হার", +"sb.limits_total": "সর্বোচ্চ {n}টি ফাইল, মোট {gb} পর্যন্ত।" } diff --git a/internal/web/static/i18n/en.json b/internal/web/static/i18n/en.json index d4e239b..fc4078c 100644 --- a/internal/web/static/i18n/en.json +++ b/internal/web/static/i18n/en.json @@ -411,5 +411,7 @@ "sb.range": "whole capture", "sb.min": "{n} min", "sb.hours": "{n} h", -"sb.not_capture": "{f} is not a capture file. Use .pcap or .pcapng as saved by Wireshark or tcpdump." +"sb.not_capture": "{f} is not a capture file. Use .pcap or .pcapng as saved by Wireshark or tcpdump.", +"sb.avg": "Average rate", +"sb.limits_total": "Up to {n} files, {gb} in all." } diff --git a/internal/web/static/i18n/es.json b/internal/web/static/i18n/es.json index 502e418..b3d0659 100644 --- a/internal/web/static/i18n/es.json +++ b/internal/web/static/i18n/es.json @@ -411,5 +411,7 @@ "sb.range": "toda la captura", "sb.min": "{n} min", "sb.hours": "{n} h", -"sb.not_capture": "{f} no es un archivo de captura. Use .pcap o .pcapng guardado con Wireshark o tcpdump." +"sb.not_capture": "{f} no es un archivo de captura. Use .pcap o .pcapng guardado con Wireshark o tcpdump.", +"sb.avg": "Tasa media", +"sb.limits_total": "Hasta {n} archivos, {gb} en total." } diff --git a/internal/web/static/i18n/fr.json b/internal/web/static/i18n/fr.json index 1e95fe1..7b260ef 100644 --- a/internal/web/static/i18n/fr.json +++ b/internal/web/static/i18n/fr.json @@ -411,5 +411,7 @@ "sb.range": "toute la capture", "sb.min": "{n} min", "sb.hours": "{n} h", -"sb.not_capture": "{f} n'est pas un fichier de capture. Utilisez un .pcap ou .pcapng enregistré par Wireshark ou tcpdump." +"sb.not_capture": "{f} n'est pas un fichier de capture. Utilisez un .pcap ou .pcapng enregistré par Wireshark ou tcpdump.", +"sb.avg": "Débit moyen", +"sb.limits_total": "Jusqu'à {n} fichiers, {gb} au total." } diff --git a/internal/web/static/i18n/hi.json b/internal/web/static/i18n/hi.json index d7f22d7..d986275 100644 --- a/internal/web/static/i18n/hi.json +++ b/internal/web/static/i18n/hi.json @@ -411,5 +411,7 @@ "sb.range": "पूरा कैप्चर", "sb.min": "{n} मिनट", "sb.hours": "{n} घंटे", -"sb.not_capture": "{f} कैप्चर फ़ाइल नहीं है। Wireshark या tcpdump से सहेजी .pcap या .pcapng इस्तेमाल करें।" +"sb.not_capture": "{f} कैप्चर फ़ाइल नहीं है। Wireshark या tcpdump से सहेजी .pcap या .pcapng इस्तेमाल करें।", +"sb.avg": "औसत दर", +"sb.limits_total": "अधिकतम {n} फ़ाइलें, कुल {gb} तक।" } diff --git a/internal/web/static/i18n/id.json b/internal/web/static/i18n/id.json index e9e15ed..d912b28 100644 --- a/internal/web/static/i18n/id.json +++ b/internal/web/static/i18n/id.json @@ -411,5 +411,7 @@ "sb.range": "seluruh tangkapan", "sb.min": "{n} mnt", "sb.hours": "{n} jam", -"sb.not_capture": "{f} bukan file tangkapan. Gunakan .pcap atau .pcapng yang disimpan Wireshark atau tcpdump." +"sb.not_capture": "{f} bukan file tangkapan. Gunakan .pcap atau .pcapng yang disimpan Wireshark atau tcpdump.", +"sb.avg": "Laju rata-rata", +"sb.limits_total": "Maksimal {n} file, total {gb}." } diff --git a/internal/web/static/i18n/ja.json b/internal/web/static/i18n/ja.json index ddf4e2a..2dbc289 100644 --- a/internal/web/static/i18n/ja.json +++ b/internal/web/static/i18n/ja.json @@ -411,5 +411,7 @@ "sb.range": "キャプチャ全体", "sb.min": "{n} 分", "sb.hours": "{n} 時間", -"sb.not_capture": "{f} はキャプチャファイルではありません。Wireshark や tcpdump で保存した .pcap か .pcapng を使ってください。" +"sb.not_capture": "{f} はキャプチャファイルではありません。Wireshark や tcpdump で保存した .pcap か .pcapng を使ってください。", +"sb.avg": "平均レート", +"sb.limits_total": "最大 {n} ファイル、合計 {gb} まで。" } diff --git a/internal/web/static/i18n/ko.json b/internal/web/static/i18n/ko.json index 96f9503..924b71d 100644 --- a/internal/web/static/i18n/ko.json +++ b/internal/web/static/i18n/ko.json @@ -411,5 +411,7 @@ "sb.range": "캡처 전체", "sb.min": "{n}분", "sb.hours": "{n}시간", -"sb.not_capture": "{f}은(는) 캡처 파일이 아닙니다. Wireshark나 tcpdump로 저장한 .pcap 또는 .pcapng를 쓰세요." +"sb.not_capture": "{f}은(는) 캡처 파일이 아닙니다. Wireshark나 tcpdump로 저장한 .pcap 또는 .pcapng를 쓰세요.", +"sb.avg": "평균 속도", +"sb.limits_total": "최대 {n}개 파일, 합계 {gb} 이하." } diff --git a/internal/web/static/i18n/pt.json b/internal/web/static/i18n/pt.json index 6754c62..de33a7e 100644 --- a/internal/web/static/i18n/pt.json +++ b/internal/web/static/i18n/pt.json @@ -411,5 +411,7 @@ "sb.range": "captura inteira", "sb.min": "{n} min", "sb.hours": "{n} h", -"sb.not_capture": "{f} não é um arquivo de captura. Use .pcap ou .pcapng salvo pelo Wireshark ou tcpdump." +"sb.not_capture": "{f} não é um arquivo de captura. Use .pcap ou .pcapng salvo pelo Wireshark ou tcpdump.", +"sb.avg": "Taxa média", +"sb.limits_total": "Até {n} arquivos, {gb} no total." } diff --git a/internal/web/static/i18n/ru.json b/internal/web/static/i18n/ru.json index b4a8f41..143a5ae 100644 --- a/internal/web/static/i18n/ru.json +++ b/internal/web/static/i18n/ru.json @@ -411,5 +411,7 @@ "sb.range": "весь захват", "sb.min": "{n} мин", "sb.hours": "{n} ч", -"sb.not_capture": "{f} — не файл захвата. Используйте .pcap или .pcapng, сохранённый Wireshark или tcpdump." +"sb.not_capture": "{f} — не файл захвата. Используйте .pcap или .pcapng, сохранённый Wireshark или tcpdump.", +"sb.avg": "Средняя скорость", +"sb.limits_total": "До {n} файлов, всего не больше {gb}." } diff --git a/internal/web/static/i18n/ur.json b/internal/web/static/i18n/ur.json index 7fb6a97..e1850f1 100644 --- a/internal/web/static/i18n/ur.json +++ b/internal/web/static/i18n/ur.json @@ -411,5 +411,7 @@ "sb.range": "پورا کیپچر", "sb.min": "{n} منٹ", "sb.hours": "{n} گھنٹے", -"sb.not_capture": "{f} کیپچر فائل نہیں ہے۔ Wireshark یا tcpdump سے محفوظ ‎.pcap یا ‎.pcapng استعمال کریں۔" +"sb.not_capture": "{f} کیپچر فائل نہیں ہے۔ Wireshark یا tcpdump سے محفوظ ‎.pcap یا ‎.pcapng استعمال کریں۔", +"sb.avg": "اوسط رفتار", +"sb.limits_total": "زیادہ سے زیادہ {n} فائلیں، کل {gb} تک۔" } diff --git a/internal/web/static/i18n/zh.json b/internal/web/static/i18n/zh.json index 4adec8f..403879a 100644 --- a/internal/web/static/i18n/zh.json +++ b/internal/web/static/i18n/zh.json @@ -411,5 +411,7 @@ "sb.range": "整个抓包", "sb.min": "{n} 分钟", "sb.hours": "{n} 小时", -"sb.not_capture": "{f} 不是抓包文件。请使用 Wireshark 或 tcpdump 保存的 .pcap 或 .pcapng。" +"sb.not_capture": "{f} 不是抓包文件。请使用 Wireshark 或 tcpdump 保存的 .pcap 或 .pcapng。", +"sb.avg": "平均带宽", +"sb.limits_total": "最多 {n} 个文件,总共不超过 {gb}。" }