2024-07-27 13:01:27 +00:00
|
|
|
package internet
|
|
|
|
|
|
|
|
import (
|
2024-08-15 13:47:20 +00:00
|
|
|
"context"
|
2024-07-27 13:01:27 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"os/exec"
|
2024-08-15 13:47:20 +00:00
|
|
|
"strconv"
|
2024-07-27 13:01:27 +00:00
|
|
|
"strings"
|
2024-08-15 13:47:20 +00:00
|
|
|
"time"
|
2024-07-27 13:01:27 +00:00
|
|
|
|
2024-08-08 10:32:55 +00:00
|
|
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
2024-07-27 13:01:27 +00:00
|
|
|
)
|
|
|
|
|
2024-08-15 13:47:20 +00:00
|
|
|
const (
|
|
|
|
pingPacketsCount = 3
|
|
|
|
pingTimeout = 5
|
2024-08-19 14:07:46 +00:00
|
|
|
inetConnectedTimeout = 4 * time.Second
|
2024-08-15 13:47:20 +00:00
|
|
|
pingAddr = "8.8.8.8"
|
|
|
|
ifName = "ppp0" // Interface name
|
|
|
|
inetMetric = 2000
|
|
|
|
)
|
|
|
|
|
2024-07-27 13:01:27 +00:00
|
|
|
type conn struct {
|
|
|
|
logger *log.Logger
|
|
|
|
port at.Port
|
2024-08-12 16:24:31 +00:00
|
|
|
|
|
|
|
pppPort string
|
|
|
|
|
|
|
|
isConnectExecuted bool
|
|
|
|
isInited bool
|
2024-08-15 13:47:20 +00:00
|
|
|
isRouteSet bool
|
|
|
|
|
|
|
|
connectTime time.Time
|
|
|
|
gw string // Gateway
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Conn interface {
|
2024-08-12 16:24:31 +00:00
|
|
|
Init(pppPort string) error
|
|
|
|
|
2024-08-12 10:40:26 +00:00
|
|
|
Connect() error
|
|
|
|
Disconnect() error
|
2024-08-12 16:24:31 +00:00
|
|
|
|
2024-08-15 13:47:20 +00:00
|
|
|
SetDefaultRouteTable() error
|
|
|
|
UnsetDefaultRouteTable() error
|
|
|
|
|
2024-08-12 16:24:31 +00:00
|
|
|
IsConnected() bool // Check interface existance
|
|
|
|
Ping() error
|
|
|
|
|
2024-07-27 13:01:27 +00:00
|
|
|
io.Closer
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(logger *log.Logger, port at.Port) Conn {
|
|
|
|
return &conn{
|
2024-08-15 13:47:20 +00:00
|
|
|
logger: logger,
|
|
|
|
port: port,
|
|
|
|
|
2024-08-12 16:24:31 +00:00
|
|
|
isConnectExecuted: false,
|
|
|
|
isInited: false,
|
2024-08-15 13:47:20 +00:00
|
|
|
isRouteSet: false,
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-12 16:24:31 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-08-12 10:40:26 +00:00
|
|
|
func (c *conn) Connect() error {
|
2024-08-12 16:24:31 +00:00
|
|
|
if !c.isInited {
|
|
|
|
return fmt.Errorf("internet submodule is not inited")
|
|
|
|
}
|
|
|
|
// Check is already connected
|
|
|
|
if c.isConnectExecuted {
|
|
|
|
return fmt.Errorf("already connected")
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
2024-08-12 16:24:31 +00:00
|
|
|
// Check signal
|
2024-07-27 13:01:27 +00:00
|
|
|
|
2024-07-29 11:02:06 +00:00
|
|
|
resp, err := exec.Command("pon", pppConfigName).Output()
|
2024-07-27 13:01:27 +00:00
|
|
|
if err != nil {
|
2024-08-12 16:24:31 +00:00
|
|
|
return fmt.Errorf("execute pon cmd: %w", err)
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
2024-08-12 16:24:31 +00:00
|
|
|
if len(resp) > 0 {
|
|
|
|
c.logger.Println("pon response:", string(resp))
|
|
|
|
}
|
|
|
|
|
2024-08-15 13:47:20 +00:00
|
|
|
c.isConnectExecuted = true
|
|
|
|
c.connectTime = time.Now()
|
|
|
|
c.gw, err = c.GetHostIp()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("get host ip: %w", err)
|
|
|
|
}
|
2024-08-12 16:24:31 +00:00
|
|
|
c.logger.Println("\x1b[38;2;0;255;0mInternet connected.\x1b[38;2;255;255;255m")
|
2024-08-15 13:47:20 +00:00
|
|
|
|
2024-07-27 13:01:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-12 10:40:26 +00:00
|
|
|
func (c *conn) Disconnect() error {
|
2024-08-12 16:24:31 +00:00
|
|
|
// return nil // Temporary do not turn off inet
|
|
|
|
if !c.isConnectExecuted {
|
|
|
|
return nil
|
|
|
|
// return fmt.Errorf("internet is not connected")
|
|
|
|
}
|
|
|
|
c.isConnectExecuted = false
|
2024-07-29 11:02:06 +00:00
|
|
|
resp, err := exec.Command("poff", pppConfigName).Output()
|
2024-07-27 13:01:27 +00:00
|
|
|
if err != nil {
|
2024-08-12 16:24:31 +00:00
|
|
|
return fmt.Errorf("execute poff cmd: %w", err)
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
2024-08-12 16:24:31 +00:00
|
|
|
if len(resp) > 0 {
|
|
|
|
c.logger.Println("poff response:", string(resp))
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
2024-08-12 16:24:31 +00:00
|
|
|
c.isConnectExecuted = false
|
2024-08-15 13:47:20 +00:00
|
|
|
c.logger.Println("\x1b[38;2;0;255;0mInternet disconnected.\x1b[38;2;255;255;255m")
|
2024-07-27 13:01:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-15 13:47:20 +00:00
|
|
|
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()
|
2024-07-27 13:01:27 +00:00
|
|
|
if err != nil {
|
2024-08-15 13:47:20 +00:00
|
|
|
return fmt.Errorf("execute add cmd: %w", err)
|
|
|
|
}
|
|
|
|
// Check response
|
|
|
|
if len(resp) != 0 {
|
|
|
|
c.logger.Println("Not nil response:", string(resp))
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
2024-08-15 13:47:20 +00:00
|
|
|
c.isRouteSet = true
|
|
|
|
c.logger.Println("\x1b[38;2;0;255;0mInternet route table set.\x1b[38;2;255;255;255m")
|
|
|
|
return nil
|
|
|
|
}
|
2024-07-29 12:17:00 +00:00
|
|
|
|
2024-08-15 13:47:20 +00:00
|
|
|
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()
|
2024-07-29 12:17:00 +00:00
|
|
|
if err != nil {
|
2024-08-15 13:47:20 +00:00
|
|
|
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)
|
2024-07-29 12:17:00 +00:00
|
|
|
}
|
|
|
|
|
2024-08-15 13:47:20 +00:00
|
|
|
// 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)
|
2024-08-12 16:24:31 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-15 13:47:20 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2024-08-12 16:24:31 +00:00
|
|
|
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]
|
2024-08-15 13:47:20 +00:00
|
|
|
if interfaceName == ifName {
|
2024-08-12 16:24:31 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false // Did not found
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *conn) Close() error {
|
2024-08-12 16:24:31 +00:00
|
|
|
c.isInited = false
|
2024-08-15 13:47:20 +00:00
|
|
|
// 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())
|
|
|
|
}
|
2024-07-27 13:01:27 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|