package internet import ( "context" "fmt" "io" "log" "os/exec" "strconv" "strings" "time" "gitea.unprism.ru/KRBL/sim-modem/api/modem/at" ) const ( pingPacketsCount = 3 pingTimeout = 5 inetConnectedTimeout = 2 * time.Second pingAddr = "8.8.8.8" ifName = "ppp0" // Interface name inetMetric = 2000 ) type conn struct { logger *log.Logger port at.Port pppPort string isConnectExecuted bool isInited bool isRouteSet bool connectTime time.Time gw string // Gateway } type Conn interface { Init(pppPort string) error Connect() error Disconnect() error SetDefaultRouteTable() error UnsetDefaultRouteTable() error IsConnected() bool // Check interface existance Ping() error io.Closer } func New(logger *log.Logger, port at.Port) Conn { return &conn{ logger: logger, port: port, isConnectExecuted: false, isInited: false, isRouteSet: false, } } func (c *conn) Init(pppPort string) error { c.pppPort = pppPort // Setup only setup if err := c.setup(); err != nil { return fmt.Errorf("setup: %w", err) } c.isInited = true return nil } func (c *conn) Connect() error { if !c.isInited { return fmt.Errorf("internet submodule is not inited") } // Check is already connected if c.isConnectExecuted { return fmt.Errorf("already connected") } // Check signal resp, err := exec.Command("pon", pppConfigName).Output() if err != nil { return fmt.Errorf("execute pon cmd: %w", err) } if len(resp) > 0 { c.logger.Println("pon response:", string(resp)) } c.isConnectExecuted = true c.connectTime = time.Now() c.gw, err = c.GetHostIp() if err != nil { return fmt.Errorf("get host ip: %w", err) } c.logger.Println("\x1b[38;2;0;255;0mInternet connected.\x1b[38;2;255;255;255m") return nil } func (c *conn) Disconnect() error { // return nil // Temporary do not turn off inet if !c.isConnectExecuted { return nil // return fmt.Errorf("internet is not connected") } c.isConnectExecuted = false resp, err := exec.Command("poff", pppConfigName).Output() if err != nil { return fmt.Errorf("execute poff cmd: %w", err) } if len(resp) > 0 { c.logger.Println("poff response:", string(resp)) } c.isConnectExecuted = false c.logger.Println("\x1b[38;2;0;255;0mInternet disconnected.\x1b[38;2;255;255;255m") return nil } func (c *conn) SetDefaultRouteTable() error { // route add -net default gw 10.64.64.64 metric 2000 dev ppp0 resp, err := exec.Command("route", "add", "-net", "default", "gw", c.gw, "metric", strconv.Itoa(inetMetric), "dev", ifName).Output() if err != nil { return fmt.Errorf("execute add cmd: %w", err) } // Check response if len(resp) != 0 { c.logger.Println("Not nil response:", string(resp)) } c.isRouteSet = true c.logger.Println("\x1b[38;2;0;255;0mInternet route table set.\x1b[38;2;255;255;255m") return nil } func (c *conn) UnsetDefaultRouteTable() error { if !c.isRouteSet { return fmt.Errorf("route table is not set") } resp, err := exec.Command("route", "del", "-net", "default", "gw", c.gw, "metric", strconv.Itoa(inetMetric), "dev", ifName).Output() if err != nil { return fmt.Errorf("execute del cmd: %w", err) } // Check response if len(resp) != 0 { c.logger.Println("Not nil response:", string(resp)) } c.logger.Println("\x1b[38;2;0;255;0mInternet route table unset.\x1b[38;2;255;255;255m") return nil } func (c *conn) ping(flags []string, timeout int) error { c.logger.Println("Ping", flags[len(flags)-1]) // Just counter ctx, cancel := context.WithCancel(context.Background()) go func(c *conn, ctx context.Context) { for i := 0; i < timeout; i++ { c.logger.Printf("Ping %d/%d", i, timeout) select { case <-ctx.Done(): return case <-time.After(time.Second): } } }(c, ctx) // Executing cmd cmd := exec.Command("ping", flags...) resp, err := cmd.Output() cancel() if err != nil { c.logger.Println("Ping default interface cmd error:", err) } // Parse lines := strings.Split(string(resp), "\n") // Look for string "--- *.*.*.* ping statistics ---" by first simbol '-' stLineI := 0 searchStLineLoop: for i, l := range lines { if len(l) > 0 && l[0] == '-' { stLineI = i + 1 break searchStLineLoop } } if stLineI == 0 || stLineI >= len(lines) { return fmt.Errorf("failed to find statistics line: %d", stLineI) } stStr := lines[stLineI] // Get third value "packet lost" values := strings.Split(stStr, ",") if len(values) < 3 { return fmt.Errorf("invalid statistic values(len): [%s]", values) } // Get number words := strings.Split(values[2], " ") if len(words) < 2 { return fmt.Errorf("invalid \"packets lost\" value(words count): [%s]", words) } // First is '' // Second is '...%' packetsLost, err := strconv.Atoi(words[1][:len(words[1])-1]) // Without '%' char if err != nil { return fmt.Errorf("parse \"packets lost\" value: %w", err) } if packetsLost == 100 { return fmt.Errorf("lost all packages") } if packetsLost > 0 { c.logger.Printf("lost some packets: %d%%\n", packetsLost) } return nil } func (c *conn) GetHostIp() (string, error) { if !c.isConnectExecuted { return "", fmt.Errorf("internet not connected") } // Wait some time for system to setup route table time.Sleep(time.Until(c.connectTime.Add(inetConnectedTimeout))) // Execute cmd resp, err := exec.Command("route").Output() if err != nil { return "", fmt.Errorf("exec route cmd: %w", err) } // Check and split to lines lines := strings.Split(string(resp), "\n") if len(lines) <= 3 || lines[0] != "Kernel IP routing table" { return "", fmt.Errorf("invalid route response: [% s]", lines) } // Search line about ppp interface searchLoop: for _, l := range lines[1:] { words := strings.Fields(l) if len(words) != 8 { /// c.logger.Printf("invalid route line(words number): [%s]\n", words) continue searchLoop } if words[7] == ifName { if words[3] != "UH" { // c.logger.Println("invalid flags") continue searchLoop } return words[0], nil } } return "", fmt.Errorf("found no suitable ppp interface") } func (c *conn) PingDefault() error { return c.ping([]string{"-c", strconv.Itoa(pingPacketsCount), "-w", strconv.Itoa(pingTimeout), pingAddr}, pingTimeout) } func (c *conn) PingPPP() error { return c.ping([]string{"-I", ifName, "-c", string(pingPacketsCount), "-w", string(pingTimeout), pingAddr}, pingTimeout) } func (c *conn) Ping() error { return c.PingDefault() } func (c *conn) IsConnected() bool { if !c.isConnectExecuted { return false } // Make "ifconfig" request resp, err := exec.Command("ifconfig").Output() if err != nil { c.logger.Println("ifconfig cmd error:", err.Error()) return false } lines := strings.Split(string(resp), "\n") for _, l := range lines { if len(l) == 0 { continue } if l[0] == ' ' { continue } interfaceName := strings.Split(l, ":")[0] if interfaceName == ifName { return true } } return false // Did not found } func (c *conn) Close() error { c.isInited = false // Unset route table if c.isRouteSet { if err := c.UnsetDefaultRouteTable(); err != nil { c.logger.Println("unset route table error:", err.Error()) } } // Disconnect if c.isConnectExecuted { if err := c.Disconnect(); err != nil { c.logger.Println("diconnect error:", err.Error()) } } return nil }