Commit 13d6cf85 authored by Sophie Brun's avatar Sophie Brun

New upstream version 2.22

parent f4269504
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:a2c142e6c2aa1c71796c748bbe42d224e23d6638fd5b3ae153e70a4b08a8da4e" digest = "1:881bb9d751b9408f038b83e9331ce3c57603710f3546f16e7d43b5c24e974f6d"
name = "github.com/bettercap/gatt" name = "github.com/bettercap/gatt"
packages = [ packages = [
".", ".",
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
"xpc", "xpc",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "277ee0d0ef94d26e3190252c59fa34dde0df4f26" revision = "d1a17475747afe7c0d78813596d4e95801a5d592"
[[projects]] [[projects]]
branch = "master" branch = "master"
...@@ -184,6 +184,14 @@ ...@@ -184,6 +184,14 @@
pruneopts = "UT" pruneopts = "UT"
revision = "f16ca3b7b383d3f0373109cac19147de3e8ae2d1" revision = "f16ca3b7b383d3f0373109cac19147de3e8ae2d1"
[[projects]]
digest = "1:7ad278b575635babef38e4ad4219500c299a58ea14b30eb21383d0efca00b369"
name = "github.com/kr/binarydist"
packages = ["."]
pruneopts = "UT"
revision = "88f551ae580780cc79d12ab4c218ba1ca346b83a"
version = "v0.1.0"
[[projects]] [[projects]]
digest = "1:4701b2acabe16722ecb1e387d39741a29269386bfc4ba6283ecda362d289eff1" digest = "1:4701b2acabe16722ecb1e387d39741a29269386bfc4ba6283ecda362d289eff1"
name = "github.com/malfunkt/iprange" name = "github.com/malfunkt/iprange"
...@@ -333,6 +341,7 @@ ...@@ -333,6 +341,7 @@
"github.com/gorilla/websocket", "github.com/gorilla/websocket",
"github.com/inconshreveable/go-vhost", "github.com/inconshreveable/go-vhost",
"github.com/jpillora/go-tld", "github.com/jpillora/go-tld",
"github.com/kr/binarydist",
"github.com/malfunkt/iprange", "github.com/malfunkt/iprange",
"github.com/mdlayher/dhcp6", "github.com/mdlayher/dhcp6",
"github.com/mdlayher/dhcp6/dhcp6opts", "github.com/mdlayher/dhcp6/dhcp6opts",
......
...@@ -73,3 +73,7 @@ ...@@ -73,3 +73,7 @@
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true
[[constraint]]
name = "github.com/kr/binarydist"
version = "0.1.0"
...@@ -2,7 +2,7 @@ package core ...@@ -2,7 +2,7 @@ package core
const ( const (
Name = "bettercap" Name = "bettercap"
Version = "2.21.1" Version = "2.22"
Author = "Simone 'evilsocket' Margaritelli" Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/" Website = "https://bettercap.org/"
) )
package core package core
import ( import (
"fmt"
"os/exec" "os/exec"
"sort" "sort"
...@@ -34,7 +33,7 @@ func HasBinary(executable string) bool { ...@@ -34,7 +33,7 @@ func HasBinary(executable string) bool {
return true return true
} }
func ExecSilent(executable string, args []string) (string, error) { func Exec(executable string, args []string) (string, error) {
path, err := exec.LookPath(executable) path, err := exec.LookPath(executable)
if err != nil { if err != nil {
return "", err return "", err
...@@ -47,11 +46,3 @@ func ExecSilent(executable string, args []string) (string, error) { ...@@ -47,11 +46,3 @@ func ExecSilent(executable string, args []string) (string, error) {
return str.Trim(string(raw)), nil return str.Trim(string(raw)), nil
} }
} }
func Exec(executable string, args []string) (string, error) {
out, err := ExecSilent(executable, args)
if err != nil {
fmt.Printf("ERROR for '%s %s': %s\n", executable, args, err)
}
return out, err
}
...@@ -41,7 +41,7 @@ func Make(iface *network.Endpoint) FirewallManager { ...@@ -41,7 +41,7 @@ func Make(iface *network.Endpoint) FirewallManager {
} }
func (f PfFirewall) sysCtlRead(param string) (string, error) { func (f PfFirewall) sysCtlRead(param string) (string, error) {
if out, err := core.ExecSilent("sysctl", []string{param}); err != nil { if out, err := core.Exec("sysctl", []string{param}); err != nil {
return "", err return "", err
} else if m := sysCtlParser.FindStringSubmatch(out); len(m) == 3 && m[1] == param { } else if m := sysCtlParser.FindStringSubmatch(out); len(m) == 3 && m[1] == param {
return m[2], nil return m[2], nil
...@@ -52,7 +52,7 @@ func (f PfFirewall) sysCtlRead(param string) (string, error) { ...@@ -52,7 +52,7 @@ func (f PfFirewall) sysCtlRead(param string) (string, error) {
func (f PfFirewall) sysCtlWrite(param string, value string) (string, error) { func (f PfFirewall) sysCtlWrite(param string, value string) (string, error) {
args := []string{"-w", fmt.Sprintf("%s=%s", param, value)} args := []string{"-w", fmt.Sprintf("%s=%s", param, value)}
_, err := core.ExecSilent("sysctl", args) _, err := core.Exec("sysctl", args)
if err != nil { if err != nil {
return "", err return "", err
} }
...@@ -115,9 +115,9 @@ func (f PfFirewall) generateRule(r *Redirection) string { ...@@ -115,9 +115,9 @@ func (f PfFirewall) generateRule(r *Redirection) string {
func (f *PfFirewall) enable(enabled bool) { func (f *PfFirewall) enable(enabled bool) {
f.enabled = enabled f.enabled = enabled
if enabled { if enabled {
core.ExecSilent("pfctl", []string{"-e"}) core.Exec("pfctl", []string{"-e"})
} else { } else {
core.ExecSilent("pfctl", []string{"-d"}) core.Exec("pfctl", []string{"-d"})
} }
} }
...@@ -139,7 +139,7 @@ func (f PfFirewall) EnableRedirection(r *Redirection, enabled bool) error { ...@@ -139,7 +139,7 @@ func (f PfFirewall) EnableRedirection(r *Redirection, enabled bool) error {
f.enable(true) f.enable(true)
// load the rule // load the rule
if _, err := core.ExecSilent("pfctl", []string{"-f", f.filename}); err != nil { if _, err := core.Exec("pfctl", []string{"-f", f.filename}); err != nil {
return err return err
} }
} else { } else {
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"time" "time"
"github.com/bettercap/bettercap/session" "github.com/bettercap/bettercap/session"
...@@ -26,6 +27,17 @@ type RestAPI struct { ...@@ -26,6 +27,17 @@ type RestAPI struct {
useWebsocket bool useWebsocket bool
upgrader websocket.Upgrader upgrader websocket.Upgrader
quit chan bool quit chan bool
recClock int
recording bool
recTime int
loading bool
replaying bool
recordFileName string
recordWait *sync.WaitGroup
record *Record
recStarted time.Time
recStopped time.Time
} }
func NewRestAPI(s *session.Session) *RestAPI { func NewRestAPI(s *session.Session) *RestAPI {
...@@ -39,8 +51,28 @@ func NewRestAPI(s *session.Session) *RestAPI { ...@@ -39,8 +51,28 @@ func NewRestAPI(s *session.Session) *RestAPI {
ReadBufferSize: 1024, ReadBufferSize: 1024,
WriteBufferSize: 1024, WriteBufferSize: 1024,
}, },
recClock: 1,
recording: false,
recTime: 0,
loading: false,
replaying: false,
recordFileName: "",
recordWait: &sync.WaitGroup{},
record: nil,
} }
mod.State.Store("recording", &mod.recording)
mod.State.Store("rec_clock", &mod.recClock)
mod.State.Store("replaying", &mod.replaying)
mod.State.Store("loading", &mod.loading)
mod.State.Store("load_progress", 0)
mod.State.Store("rec_time", &mod.recTime)
mod.State.Store("rec_filename", &mod.recordFileName)
mod.State.Store("rec_frames", 0)
mod.State.Store("rec_cur_frame", 0)
mod.State.Store("rec_started", &mod.recStarted)
mod.State.Store("rec_stopped", &mod.recStopped)
mod.AddParam(session.NewStringParameter("api.rest.address", mod.AddParam(session.NewStringParameter("api.rest.address",
"127.0.0.1", "127.0.0.1",
session.IPv4Validator, session.IPv4Validator,
...@@ -93,6 +125,34 @@ func NewRestAPI(s *session.Session) *RestAPI { ...@@ -93,6 +125,34 @@ func NewRestAPI(s *session.Session) *RestAPI {
return mod.Stop() return mod.Stop()
})) }))
mod.AddParam(session.NewIntParameter("api.rest.record.clock",
"1",
"Number of seconds to wait while recording with api.rest.record between one sample and the next one."))
mod.AddHandler(session.NewModuleHandler("api.rest.record off", "",
"Stop recording the session.",
func(args []string) error {
return mod.stopRecording()
}))
mod.AddHandler(session.NewModuleHandler("api.rest.record FILENAME", `api\.rest\.record (.+)`,
"Start polling the rest API periodically recording each sample in a compressed file that can be later replayed.",
func(args []string) error {
return mod.startRecording(args[0])
}))
mod.AddHandler(session.NewModuleHandler("api.rest.replay off", "",
"Stop replaying the recorded session.",
func(args []string) error {
return mod.stopReplay()
}))
mod.AddHandler(session.NewModuleHandler("api.rest.replay FILENAME", `api\.rest\.replay (.+)`,
"Start the rest API module in replay mode using FILENAME as the recorded session file, will revert to normal mode once the replay is over.",
func(args []string) error {
return mod.startReplay(args[0])
}))
return mod return mod
} }
...@@ -205,7 +265,9 @@ func (mod *RestAPI) Configure() error { ...@@ -205,7 +265,9 @@ func (mod *RestAPI) Configure() error {
} }
func (mod *RestAPI) Start() error { func (mod *RestAPI) Start() error {
if err := mod.Configure(); err != nil { if mod.replaying {
return fmt.Errorf("the api is currently in replay mode, run api.rest.replay off before starting it")
} else if err := mod.Configure(); err != nil {
return err return err
} }
...@@ -229,6 +291,12 @@ func (mod *RestAPI) Start() error { ...@@ -229,6 +291,12 @@ func (mod *RestAPI) Start() error {
} }
func (mod *RestAPI) Stop() error { func (mod *RestAPI) Stop() error {
if mod.recording {
mod.stopRecording()
} else if mod.replaying {
mod.stopReplay()
}
return mod.SetRunning(false, func() { return mod.SetRunning(false, func() {
go func() { go func() {
mod.quit <- true mod.quit <- true
......
...@@ -36,7 +36,7 @@ func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) { ...@@ -36,7 +36,7 @@ func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) {
func (mod *RestAPI) toJSON(w http.ResponseWriter, o interface{}) { func (mod *RestAPI) toJSON(w http.ResponseWriter, o interface{}) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(o); err != nil { if err := json.NewEncoder(w).Encode(o); err != nil {
mod.Error("error while encoding object to JSON: %v", err) mod.Warning("error while encoding object to JSON: %v", err)
} }
} }
...@@ -64,8 +64,68 @@ func (mod *RestAPI) checkAuth(r *http.Request) bool { ...@@ -64,8 +64,68 @@ func (mod *RestAPI) checkAuth(r *http.Request) bool {
return true return true
} }
func (mod *RestAPI) patchFrame(buf []byte) (frame map[string]interface{}, err error) {
// this is ugly but necessary: since we're replaying, the
// api.rest state object is filled with *old* values (the
// recorded ones), but the UI needs updated values at least
// of that in order to understand that a replay is going on
// and where we are at it. So we need to parse the record
// back into a session object and update only the api.rest.state
frame = make(map[string]interface{})
if err = json.Unmarshal(buf, &frame); err != nil {
return
}
for _, i := range frame["modules"].([]interface{}) {
m := i.(map[string]interface{})
if m["name"] == "api.rest" {
state := m["state"].(map[string]interface{})
mod.State.Range(func(key interface{}, value interface{}) bool {
state[key.(string)] = value
return true
})
break
}
}
return
}
func (mod *RestAPI) showSession(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I) if mod.replaying {
if !mod.record.Session.Over() {
from := mod.record.Session.CurFrame() - 1
q := r.URL.Query()
vals := q["from"]
if len(vals) > 0 {
if n, err := strconv.Atoi(vals[0]); err == nil {
from = n
}
}
mod.record.Session.SetFrom(from)
mod.Debug("replaying session %d of %d from %s",
mod.record.Session.CurFrame(),
mod.record.Session.Frames(),
mod.recordFileName)
mod.State.Store("rec_frames", mod.record.Session.Frames())
mod.State.Store("rec_cur_frame", mod.record.Session.CurFrame())
buf := mod.record.Session.Next()
if frame, err := mod.patchFrame(buf); err != nil {
mod.Error("%v", err)
} else {
mod.toJSON(w, frame)
return
}
} else {
mod.stopReplay()
}
}
mod.toJSON(w, mod.Session)
} }
func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
...@@ -73,8 +133,8 @@ func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) { ...@@ -73,8 +133,8 @@ func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.BLE) mod.toJSON(w, mod.Session.BLE)
} else if dev, found := session.I.BLE.Get(mac); found { } else if dev, found := mod.Session.BLE.Get(mac); found {
mod.toJSON(w, dev) mod.toJSON(w, dev)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
...@@ -86,8 +146,8 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) { ...@@ -86,8 +146,8 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.HID) mod.toJSON(w, mod.Session.HID)
} else if dev, found := session.I.HID.Get(mac); found { } else if dev, found := mod.Session.HID.Get(mac); found {
mod.toJSON(w, dev) mod.toJSON(w, dev)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
...@@ -95,19 +155,19 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) { ...@@ -95,19 +155,19 @@ func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
} }
func (mod *RestAPI) showEnv(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showEnv(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Env) mod.toJSON(w, mod.Session.Env)
} }
func (mod *RestAPI) showGateway(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showGateway(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Gateway) mod.toJSON(w, mod.Session.Gateway)
} }
func (mod *RestAPI) showInterface(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showInterface(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Interface) mod.toJSON(w, mod.Session.Interface)
} }
func (mod *RestAPI) showModules(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showModules(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Modules) mod.toJSON(w, mod.Session.Modules)
} }
func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
...@@ -115,8 +175,8 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) { ...@@ -115,8 +175,8 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.Lan) mod.toJSON(w, mod.Session.Lan)
} else if host, found := session.I.Lan.Get(mac); found { } else if host, found := mod.Session.Lan.Get(mac); found {
mod.toJSON(w, host) mod.toJSON(w, host)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
...@@ -124,15 +184,15 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) { ...@@ -124,15 +184,15 @@ func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
} }
func (mod *RestAPI) showOptions(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showOptions(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Options) mod.toJSON(w, mod.Session.Options)
} }
func (mod *RestAPI) showPackets(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showPackets(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Queue) mod.toJSON(w, mod.Session.Queue)
} }
func (mod *RestAPI) showStartedAt(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showStartedAt(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.StartedAt) mod.toJSON(w, mod.Session.StartedAt)
} }
func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
...@@ -140,10 +200,10 @@ func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) { ...@@ -140,10 +200,10 @@ func (mod *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
mac := strings.ToLower(params["mac"]) mac := strings.ToLower(params["mac"])
if mac == "" { if mac == "" {
mod.toJSON(w, session.I.WiFi) mod.toJSON(w, mod.Session.WiFi)
} else if station, found := session.I.WiFi.Get(mac); found { } else if station, found := mod.Session.WiFi.Get(mac); found {
mod.toJSON(w, station) mod.toJSON(w, station)
} else if client, found := session.I.WiFi.GetClient(mac); found { } else if client, found := mod.Session.WiFi.GetClient(mac); found {
mod.toJSON(w, client) mod.toJSON(w, client)
} else { } else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
...@@ -170,14 +230,9 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) { ...@@ -170,14 +230,9 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, APIResponse{Success: true}) mod.toJSON(w, APIResponse{Success: true})
} }
func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) getEvents(limit int) []session.Event {
var err error
if mod.useWebsocket {
mod.startStreamingEvents(w, r)
} else {
events := make([]session.Event, 0) events := make([]session.Event, 0)
for _, e := range session.I.Events.Sorted() { for _, e := range mod.Session.Events.Sorted() {
if mod.Session.EventsIgnoreList.Ignored(e) == false { if mod.Session.EventsIgnoreList.Ignored(e) == false {
events = append(events, e) events = append(events, e)
} }
...@@ -187,25 +242,60 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) { ...@@ -187,25 +242,60 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
nmax := nevents nmax := nevents
n := nmax n := nmax
if limit > 0 && limit < nmax {
n = limit
}
return events[nevents-n:]
}
func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query() q := r.URL.Query()
vals := q["n"]
if mod.replaying {
if !mod.record.Events.Over() {
from := mod.record.Events.CurFrame() - 1
vals := q["from"]
if len(vals) > 0 { if len(vals) > 0 {
n, err = strconv.Atoi(q["n"][0]) if n, err := strconv.Atoi(vals[0]); err == nil {
if err == nil { from = n
if n > nmax { }
n = nmax
} }
mod.record.Events.SetFrom(from)
mod.Debug("replaying events %d of %d from %s",
mod.record.Events.CurFrame(),
mod.record.Events.Frames(),
mod.recordFileName)
buf := mod.record.Events.Next()
if _, err := w.Write(buf); err != nil {
mod.Error("%v", err)
} else { } else {
n = nmax return
}
} else {
mod.stopReplay()
}
}
if mod.useWebsocket {
mod.startStreamingEvents(w, r)
} else {
vals := q["n"]
limit := 0
if len(vals) > 0 {
if n, err := strconv.Atoi(q["n"][0]); err == nil {
limit = n
} }
} }
mod.toJSON(w, events[nevents-n:]) mod.toJSON(w, mod.getEvents(limit))
} }
} }
func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) {
session.I.Events.Clear() mod.Session.Events.Clear()
} }
func (mod *RestAPI) corsRoute(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) corsRoute(w http.ResponseWriter, r *http.Request) {
...@@ -227,10 +317,10 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) { ...@@ -227,10 +317,10 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
return return
} }
session.I.Lock() mod.Session.Lock()
defer session.I.Unlock() defer mod.Session.Unlock()
path := r.URL.String() path := r.URL.Path
switch { switch {
case path == "/api/session": case path == "/api/session":
mod.showSession(w, r) mod.showSession(w, r)
......
package api_rest
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/evilsocket/islazy/fs"
)
var (
errNotRecording = errors.New("not recording")
)
func (mod *RestAPI) errAlreadyRecording() error {
return fmt.Errorf("the module is already recording to %s", mod.recordFileName)
}
func (mod *RestAPI) recordState() error {
mod.Session.Lock()
defer mod.Session.Unlock()
session := new(bytes.Buffer)
encoder := json.NewEncoder(session)
if err := encoder.Encode(mod.Session); err != nil {
return err
}
events := new(bytes.Buffer)
encoder = json.NewEncoder(events)
if err := encoder.Encode(mod.getEvents(0)); err != nil {
return err
}
return mod.record.NewState(session.Bytes(), events.Bytes())
}
func (mod *RestAPI) recorder() {
clock := time.Duration(mod.recClock) * time.Second
mod.recTime = 0
mod.recording = true
mod.replaying = false
mod.record = NewRecord(mod.recordFileName, &mod.SessionModule)