sim-modem/api/modem/internet/ic.go

311 lines
7.2 KiB
Go
Raw Normal View History

package internet
import (
2024-08-15 13:47:20 +00:00
"context"
"fmt"
"io"
"log"
"os/exec"
2024-08-15 13:47:20 +00:00
"strconv"
"strings"
2024-08-15 13:47:20 +00:00
"time"
2024-08-08 10:32:55 +00:00
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
)
2024-08-15 13:47:20 +00:00
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
2024-08-15 13:47:20 +00:00
isRouteSet bool
connectTime time.Time
gw string // Gateway
}
type Conn interface {
Init(pppPort string) error
Connect() error
Disconnect() error
2024-08-15 13:47:20 +00:00
SetDefaultRouteTable() error
UnsetDefaultRouteTable() error
IsConnected() bool // Check interface existance
Ping() error
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,
isConnectExecuted: false,
isInited: false,
2024-08-15 13:47:20 +00:00
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
2024-07-29 11:02:06 +00:00
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))
}
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)
}
c.logger.Println("\x1b[38;2;0;255;0mInternet connected.\x1b[38;2;255;255;255m")
2024-08-15 13:47:20 +00:00
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
2024-07-29 11:02:06 +00:00
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
2024-08-15 13:47:20 +00:00
c.logger.Println("\x1b[38;2;0;255;0mInternet disconnected.\x1b[38;2;255;255;255m")
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()
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-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-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()
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-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)
}
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()
}
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 {
return true
}
}
return false // Did not found
}
func (c *conn) Close() error {
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())
}
}
return nil
}