initial release
This commit is contained in:
parent
7f4c08c89b
commit
4f8ef2757d
1
.gitignore
vendored
1
.gitignore
vendored
@ -72,3 +72,4 @@ flycheck_*.el
|
|||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
modemulator
|
||||||
|
7
go.mod
Normal file
7
go.mod
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module modemulator
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require github.com/sirupsen/logrus v1.9.0
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
15
go.sum
Normal file
15
go.sum
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
818
modemulator.go
Normal file
818
modemulator.go
Normal file
@ -0,0 +1,818 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// some config options
|
||||||
|
|
||||||
|
const PPS_TARGET = 10
|
||||||
|
|
||||||
|
const SERVER_BIND = ""
|
||||||
|
const SERVER_PORT_INDEX = 10000
|
||||||
|
|
||||||
|
const TELNET_SERVER = "localhost"
|
||||||
|
|
||||||
|
var BAUDS []uint = []uint{
|
||||||
|
300,
|
||||||
|
1200,
|
||||||
|
2400,
|
||||||
|
4800,
|
||||||
|
9600,
|
||||||
|
14400,
|
||||||
|
19200,
|
||||||
|
28800,
|
||||||
|
33600,
|
||||||
|
56000,
|
||||||
|
115000,
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddressBook map[string]string = map[string]string{
|
||||||
|
"54311": "shell.fr-par1.burble.dn42:23",
|
||||||
|
"42": "localhost:23",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OnlyNumbers *regexp.Regexp = regexp.MustCompile("([^0-9]+)")
|
||||||
|
|
||||||
|
// global data and structures
|
||||||
|
|
||||||
|
var Listeners []net.Listener
|
||||||
|
|
||||||
|
type TCode uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
TN_NORM TCode = 65535
|
||||||
|
TN_IAC TCode = 255
|
||||||
|
TN_DONT TCode = 254
|
||||||
|
TN_DO TCode = 253
|
||||||
|
TN_WONT TCode = 252
|
||||||
|
TN_WILL TCode = 251
|
||||||
|
TN_SB TCode = 250
|
||||||
|
TN_GA TCode = 249
|
||||||
|
TN_EL TCode = 248
|
||||||
|
TN_EC TCode = 247
|
||||||
|
TN_AYT TCode = 246
|
||||||
|
TN_AO TCode = 245
|
||||||
|
TN_IP TCode = 244
|
||||||
|
TN_BRK TCode = 243
|
||||||
|
TN_DM TCode = 242
|
||||||
|
TN_NOP TCode = 241
|
||||||
|
TN_SE TCode = 240
|
||||||
|
)
|
||||||
|
|
||||||
|
type DTEMode uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
DTE_COMMAND DTEMode = iota
|
||||||
|
DTE_COMMAND_A
|
||||||
|
DTE_COMMAND_AT
|
||||||
|
DTE_ATTN_1
|
||||||
|
DTE_ATTN_2
|
||||||
|
DTE_ATTN_3
|
||||||
|
DTE_CONNECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
type Modem struct {
|
||||||
|
baud uint
|
||||||
|
dce net.Conn
|
||||||
|
mode DTEMode
|
||||||
|
echo bool
|
||||||
|
cmdBuff *bytes.Buffer
|
||||||
|
dteBuff []byte
|
||||||
|
connected bool
|
||||||
|
dte net.Conn
|
||||||
|
state TCode
|
||||||
|
readSlot time.Time
|
||||||
|
writeSlot time.Time
|
||||||
|
dceBuff []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var Modems map[string]*Modem
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// utility function to set the log level
|
||||||
|
|
||||||
|
func setLogLevel(levelStr string) {
|
||||||
|
|
||||||
|
if level, err := log.ParseLevel(levelStr); err != nil {
|
||||||
|
// failed to set the level
|
||||||
|
|
||||||
|
// set a sensible default and, of course, log the error
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"loglevel": levelStr,
|
||||||
|
"error": err,
|
||||||
|
}).Error("Failed to set requested log level")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// set the requested level
|
||||||
|
log.SetLevel(level)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// listen on required ports
|
||||||
|
|
||||||
|
func Listen() {
|
||||||
|
|
||||||
|
for _, baud := range BAUDS {
|
||||||
|
port := SERVER_PORT_INDEX + (baud / 100)
|
||||||
|
bind := SERVER_BIND + ":" + strconv.Itoa(int(port))
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"bind": bind,
|
||||||
|
"baud": baud,
|
||||||
|
}).Debug("Listening on socket")
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", bind)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"bind": bind,
|
||||||
|
"baud": baud,
|
||||||
|
"error": err,
|
||||||
|
}).Fatal("Failed to bind listening port")
|
||||||
|
}
|
||||||
|
|
||||||
|
Listeners = append(Listeners, listener)
|
||||||
|
|
||||||
|
// spin off a thread waiting for new connections
|
||||||
|
go func(p uint, b uint) {
|
||||||
|
for {
|
||||||
|
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, net.ErrClosed) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"port": p,
|
||||||
|
}).Debug("Listener closed")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"port": p,
|
||||||
|
"error": err,
|
||||||
|
}).Error("Listen Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialise modem and start listening for commands
|
||||||
|
modem := &Modem{}
|
||||||
|
go modem.DTEReceive(b, conn)
|
||||||
|
}
|
||||||
|
}(port, baud)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// modem utility functions
|
||||||
|
|
||||||
|
func (m *Modem) disconnect() {
|
||||||
|
if m.connected {
|
||||||
|
m.dce.Close()
|
||||||
|
}
|
||||||
|
m.connected = false
|
||||||
|
m.mode = DTE_COMMAND
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) Shutdown() {
|
||||||
|
m.disconnect()
|
||||||
|
m.dte.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) bytesPerTimeslot() uint {
|
||||||
|
return (m.baud / (PPS_TARGET * 9)) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) createBuffers() {
|
||||||
|
m.dteBuff = make([]byte, m.baud)
|
||||||
|
m.dceBuff = make([]byte, m.bytesPerTimeslot())
|
||||||
|
if m.cmdBuff == nil {
|
||||||
|
m.cmdBuff = new(bytes.Buffer)
|
||||||
|
m.cmdBuff.Grow(80)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) dteWrite(data []byte) {
|
||||||
|
if _, err := m.dte.Write(data); err != nil {
|
||||||
|
m.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) dceWrite(data []byte) {
|
||||||
|
|
||||||
|
// step through the data in chunks
|
||||||
|
csize := int(m.bytesPerTimeslot())
|
||||||
|
for scan := 0; scan < len(data); {
|
||||||
|
|
||||||
|
// wait if necessary until we can send
|
||||||
|
now := time.Now()
|
||||||
|
if m.writeSlot.After(now) {
|
||||||
|
wtime := m.writeSlot.Sub(now)
|
||||||
|
fmt.Printf("waiting %d for write slot\n", wtime)
|
||||||
|
time.Sleep(wtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the data
|
||||||
|
end := scan + csize
|
||||||
|
if end > len(data) {
|
||||||
|
end = len(data)
|
||||||
|
}
|
||||||
|
chunk := data[scan:end]
|
||||||
|
if _, err := m.dce.Write(chunk); err != nil {
|
||||||
|
m.disconnect()
|
||||||
|
m.noCarrier()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scan += len(chunk)
|
||||||
|
|
||||||
|
// set the next available timeslot
|
||||||
|
tlen := time.Duration((9 * 1000 * 1000 * 1000 * len(chunk)) / int(m.baud))
|
||||||
|
fmt.Printf("%d chars, next write slot in %d\n", len(chunk), tlen)
|
||||||
|
m.writeSlot = now.Add(tlen)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) tnWrite(codes []TCode) {
|
||||||
|
b := make([]byte, len(codes))
|
||||||
|
for i, c := range codes {
|
||||||
|
b[i] = byte(c)
|
||||||
|
}
|
||||||
|
m.dceWrite(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) cmdOK() {
|
||||||
|
m.dteWrite([]byte("OK\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) cmdError() {
|
||||||
|
m.dteWrite([]byte("ERROR\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) noCarrier() {
|
||||||
|
m.dteWrite([]byte("NO CARRIER\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Modem) connect(dial string) error {
|
||||||
|
|
||||||
|
endpoint, found := AddressBook[dial]
|
||||||
|
if !found {
|
||||||
|
return errors.New("Address not found:" + dial)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial("tcp", endpoint)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"error": err,
|
||||||
|
}).Error("Telnet connection failed")
|
||||||
|
m.connected = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.dce = conn
|
||||||
|
m.connected = true
|
||||||
|
m.mode = DTE_CONNECTED
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Modem receive thread
|
||||||
|
|
||||||
|
func (m *Modem) DTEReceive(baud uint, dte net.Conn) {
|
||||||
|
|
||||||
|
remote := dte.RemoteAddr().String()
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": remote,
|
||||||
|
}).Info("Accepting new modem")
|
||||||
|
|
||||||
|
// initialise state
|
||||||
|
m.baud = baud
|
||||||
|
m.dte = dte
|
||||||
|
m.createBuffers()
|
||||||
|
m.atCmd("z")
|
||||||
|
|
||||||
|
// register
|
||||||
|
Modems[remote] = m
|
||||||
|
defer func() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": remote,
|
||||||
|
}).Debug("Shutting down modem")
|
||||||
|
m.Shutdown()
|
||||||
|
delete(Modems, remote)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
// read some data
|
||||||
|
|
||||||
|
if m.mode == DTE_ATTN_3 {
|
||||||
|
// potential hangup, set a read deadline
|
||||||
|
m.dte.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the read
|
||||||
|
available, err := m.dte.Read(m.dteBuff)
|
||||||
|
|
||||||
|
if m.mode == DTE_ATTN_3 {
|
||||||
|
// clear the deadline and return to connected state
|
||||||
|
m.dte.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if (err != nil) && errors.Is(err, os.ErrDeadlineExceeded) {
|
||||||
|
// back to command mode
|
||||||
|
m.mode = DTE_COMMAND
|
||||||
|
m.cmdOK()
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no attention
|
||||||
|
m.mode = DTE_CONNECTED
|
||||||
|
m.dce.Write([]byte("+++"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": remote,
|
||||||
|
}).Info("Modem DTE closed")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": remote,
|
||||||
|
"error": err,
|
||||||
|
}).Error("DTE Read error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for scan := 0; scan < available; {
|
||||||
|
switch m.mode {
|
||||||
|
|
||||||
|
case DTE_COMMAND:
|
||||||
|
// waiting for AT
|
||||||
|
|
||||||
|
start := scan
|
||||||
|
for ; scan < available; scan++ {
|
||||||
|
if m.dteBuff[scan] == 'a' || m.dteBuff[scan] == 'A' {
|
||||||
|
m.mode = DTE_COMMAND_A
|
||||||
|
scan++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// echo characters if required
|
||||||
|
if m.echo {
|
||||||
|
m.dteWrite(m.dteBuff[start:scan])
|
||||||
|
}
|
||||||
|
|
||||||
|
case DTE_COMMAND_A:
|
||||||
|
// have A, is next byte a T ?
|
||||||
|
if m.dteBuff[scan] == 't' || m.dteBuff[scan] == 'T' {
|
||||||
|
// yes, start AT command mode
|
||||||
|
m.mode = DTE_COMMAND_AT
|
||||||
|
scan++
|
||||||
|
|
||||||
|
// echo char if needed
|
||||||
|
if m.echo {
|
||||||
|
m.dteWrite(m.dteBuff[scan-1 : scan])
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// not a T, reset the mode
|
||||||
|
m.mode = DTE_COMMAND
|
||||||
|
}
|
||||||
|
|
||||||
|
case DTE_COMMAND_AT:
|
||||||
|
// have AT, waiting for \r
|
||||||
|
|
||||||
|
eol := false
|
||||||
|
start := scan
|
||||||
|
for ; scan < available; scan++ {
|
||||||
|
if m.dteBuff[scan] == '\r' {
|
||||||
|
// signal EOL
|
||||||
|
eol = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := m.dteBuff[start:scan]
|
||||||
|
|
||||||
|
// echo characters if required
|
||||||
|
if m.echo {
|
||||||
|
m.dteWrite(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to command buffer
|
||||||
|
if (m.cmdBuff.Len() + len(data)) > 80 {
|
||||||
|
// nah too long
|
||||||
|
m.cmdBuff.Reset()
|
||||||
|
m.cmdError()
|
||||||
|
} else {
|
||||||
|
m.cmdBuff.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse AT command if EOL
|
||||||
|
if eol {
|
||||||
|
scan++
|
||||||
|
m.dteWrite([]byte("\r\n"))
|
||||||
|
|
||||||
|
cmd := m.cmdBuff.String()
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": remote,
|
||||||
|
"cmd": cmd,
|
||||||
|
}).Debug("ATCMD")
|
||||||
|
|
||||||
|
m.atCmd(cmd)
|
||||||
|
m.cmdBuff.Reset()
|
||||||
|
if m.mode != DTE_CONNECTED {
|
||||||
|
m.mode = DTE_COMMAND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case DTE_CONNECTED:
|
||||||
|
// stream to DCE
|
||||||
|
|
||||||
|
start := scan
|
||||||
|
for ; scan < available; scan++ {
|
||||||
|
if m.dteBuff[scan] == '+' {
|
||||||
|
m.mode = DTE_ATTN_1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data so far
|
||||||
|
data := m.dteBuff[start:scan]
|
||||||
|
m.dceWrite(data)
|
||||||
|
|
||||||
|
if scan < available {
|
||||||
|
// + was received, skip over it
|
||||||
|
scan++
|
||||||
|
}
|
||||||
|
|
||||||
|
case DTE_ATTN_1:
|
||||||
|
if m.dteBuff[scan] == '+' {
|
||||||
|
m.mode = DTE_ATTN_2
|
||||||
|
scan++
|
||||||
|
} else {
|
||||||
|
// no attention, send suitable number of +
|
||||||
|
m.mode = DTE_CONNECTED
|
||||||
|
m.dceWrite([]byte("+"))
|
||||||
|
}
|
||||||
|
|
||||||
|
case DTE_ATTN_2:
|
||||||
|
if m.dteBuff[scan] == '+' {
|
||||||
|
|
||||||
|
scan++
|
||||||
|
if scan < available {
|
||||||
|
// more bytes were available, no hangup
|
||||||
|
m.mode = DTE_CONNECTED
|
||||||
|
m.dceWrite([]byte("+++"))
|
||||||
|
} else {
|
||||||
|
// wait for pause
|
||||||
|
m.mode = DTE_ATTN_3
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no attention, send suitable number of +
|
||||||
|
m.mode = DTE_CONNECTED
|
||||||
|
m.dceWrite([]byte("++"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// action AT command
|
||||||
|
|
||||||
|
func (m *Modem) atCmd(cmd string) {
|
||||||
|
cmd = strings.ToLower(strings.TrimSpace(cmd))
|
||||||
|
l := len(cmd)
|
||||||
|
|
||||||
|
// cope with zero length commands
|
||||||
|
if l == 0 {
|
||||||
|
m.cmdError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cmd[0] {
|
||||||
|
case 'd':
|
||||||
|
// dial
|
||||||
|
|
||||||
|
if m.connected || l < 4 {
|
||||||
|
m.cmdError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any non-digit characters
|
||||||
|
dial := OnlyNumbers.ReplaceAllString(cmd, "")
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"dial": dial,
|
||||||
|
}).Debug("Dial number")
|
||||||
|
|
||||||
|
if err := m.connect(dial); err != nil {
|
||||||
|
m.noCarrier()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// show connect string
|
||||||
|
cstr := "CONNECT " + strconv.Itoa(int(m.baud)) + "\r\n"
|
||||||
|
m.dte.Write([]byte(cstr))
|
||||||
|
|
||||||
|
// start dce read thread
|
||||||
|
go m.dceTelnet()
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
// hangup
|
||||||
|
switch {
|
||||||
|
case l == 1 || cmd[1] == '0':
|
||||||
|
m.disconnect()
|
||||||
|
m.cmdOK()
|
||||||
|
default:
|
||||||
|
m.cmdError()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
// enable/disable echo
|
||||||
|
switch {
|
||||||
|
case l == 1 || cmd[1] == '0':
|
||||||
|
m.echo = false
|
||||||
|
m.cmdOK()
|
||||||
|
case cmd[1] == '1':
|
||||||
|
m.echo = true
|
||||||
|
m.cmdOK()
|
||||||
|
default:
|
||||||
|
m.cmdError()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
if l == 1 {
|
||||||
|
if m.connected {
|
||||||
|
m.mode = DTE_CONNECTED
|
||||||
|
m.cmdOK()
|
||||||
|
} else {
|
||||||
|
m.noCarrier()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.cmdError()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'z':
|
||||||
|
if l == 1 || cmd[1] == '0' {
|
||||||
|
m.disconnect()
|
||||||
|
m.echo = true
|
||||||
|
m.mode = DTE_COMMAND
|
||||||
|
m.cmdBuff.Reset()
|
||||||
|
m.cmdOK()
|
||||||
|
} else {
|
||||||
|
m.cmdError()
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
m.cmdOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// DCE read thread
|
||||||
|
|
||||||
|
func (m *Modem) dceTelnet() {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": m.dte.RemoteAddr().String(),
|
||||||
|
}).Debug("Shutting down DCE")
|
||||||
|
m.disconnect()
|
||||||
|
m.noCarrier()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// suppress GA (3)
|
||||||
|
// binary transmission (0)
|
||||||
|
// echo (1)
|
||||||
|
m.tnWrite([]TCode{
|
||||||
|
TN_IAC, TN_WILL, TCode(3),
|
||||||
|
TN_IAC, TN_DO, TCode(3),
|
||||||
|
TN_IAC, TN_WILL, TCode(0),
|
||||||
|
TN_IAC, TN_DO, TCode(0),
|
||||||
|
TN_IAC, TN_WONT, TCode(1),
|
||||||
|
TN_IAC, TN_DO, TCode(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
m.state = TN_NORM
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
// wait if necessary until we can read
|
||||||
|
now := time.Now()
|
||||||
|
if m.readSlot.After(now) {
|
||||||
|
rtime := m.readSlot.Sub(now)
|
||||||
|
fmt.Printf("waiting %d for read slot\n", rtime)
|
||||||
|
time.Sleep(rtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read some data
|
||||||
|
available, err := m.dce.Read(m.dceBuff)
|
||||||
|
|
||||||
|
// set the next available timeslot
|
||||||
|
tlen := time.Duration((9 * 1000 * 1000 * 1000 * len(m.dceBuff)) / int(m.baud))
|
||||||
|
fmt.Printf("%d chars, next read slot in %d\n", len(m.dceBuff), tlen)
|
||||||
|
m.readSlot = now.Add(tlen)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": m.dte.RemoteAddr().String(),
|
||||||
|
}).Info("Telnet DCE closed")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"remote": m.dte.RemoteAddr().String(),
|
||||||
|
"error": err,
|
||||||
|
}).Error("DCE Read error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the read data for control chars
|
||||||
|
for scan := 0; scan < available; {
|
||||||
|
switch m.state {
|
||||||
|
case TN_NORM:
|
||||||
|
// normal data processing, scan for IAC code
|
||||||
|
|
||||||
|
start := scan
|
||||||
|
for ; scan < available; scan++ {
|
||||||
|
if TCode(m.dceBuff[scan]) == TN_IAC {
|
||||||
|
m.state = TN_IAC
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data so far
|
||||||
|
data := m.dceBuff[start:scan]
|
||||||
|
m.dte.Write(data)
|
||||||
|
|
||||||
|
if scan < available {
|
||||||
|
// TN_IAC was received, skip over it
|
||||||
|
scan++
|
||||||
|
}
|
||||||
|
|
||||||
|
case TN_IAC:
|
||||||
|
// have received IAC look for the next code
|
||||||
|
code := TCode(m.dceBuff[scan])
|
||||||
|
scan++
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
case TN_IAC:
|
||||||
|
// actually send 255
|
||||||
|
m.dte.Write([]byte{byte(TN_IAC)})
|
||||||
|
case TN_WILL:
|
||||||
|
m.state = TN_WILL
|
||||||
|
case TN_WONT:
|
||||||
|
m.state = TN_WONT
|
||||||
|
case TN_DO:
|
||||||
|
m.state = TN_DO
|
||||||
|
case TN_DONT:
|
||||||
|
m.state = TN_DO
|
||||||
|
default:
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"code": code,
|
||||||
|
}).Debug("Un-implemented TN code")
|
||||||
|
m.state = TN_NORM
|
||||||
|
}
|
||||||
|
|
||||||
|
case TN_WILL:
|
||||||
|
|
||||||
|
code := TCode(m.dceBuff[scan])
|
||||||
|
m.state = TN_NORM
|
||||||
|
scan++
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
case 0:
|
||||||
|
// ignore binary confirmation
|
||||||
|
case 1:
|
||||||
|
// ignore echo
|
||||||
|
case 3:
|
||||||
|
// ignore GA confirmation
|
||||||
|
default:
|
||||||
|
// refuse any other WILL codes
|
||||||
|
m.tnWrite([]TCode{TN_IAC, TN_DONT, code})
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"code": code,
|
||||||
|
}).Debug("Un-implemented TN_WILL")
|
||||||
|
}
|
||||||
|
|
||||||
|
case TN_WONT:
|
||||||
|
|
||||||
|
code := TCode(m.dceBuff[scan])
|
||||||
|
m.state = TN_NORM
|
||||||
|
scan++
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
default:
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"code": code,
|
||||||
|
}).Debug("Un-implemented TN_WONT")
|
||||||
|
}
|
||||||
|
|
||||||
|
case TN_DO:
|
||||||
|
|
||||||
|
code := TCode(m.dceBuff[scan])
|
||||||
|
m.state = TN_NORM
|
||||||
|
scan++
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
case 0:
|
||||||
|
// ignore binary confirmation
|
||||||
|
case 3:
|
||||||
|
// ignore GA confirmation
|
||||||
|
default:
|
||||||
|
// refuse any DO codes
|
||||||
|
m.tnWrite([]TCode{TN_IAC, TN_WONT, code})
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"code": code,
|
||||||
|
}).Debug("Un-implemented TN_DO")
|
||||||
|
}
|
||||||
|
|
||||||
|
case TN_DONT:
|
||||||
|
|
||||||
|
code := TCode(m.dceBuff[scan])
|
||||||
|
m.state = TN_NORM
|
||||||
|
scan++
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
default:
|
||||||
|
// confirm won't do any codes
|
||||||
|
m.tnWrite([]TCode{TN_IAC, TN_WONT, code})
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"code": code,
|
||||||
|
}).Debug("Un-implemented TN_DONT")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"state": m.state,
|
||||||
|
}).Debug("Unknown TN state")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// everything starts here
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
log.Info("modemulator starting")
|
||||||
|
|
||||||
|
// startup
|
||||||
|
Modems = make(map[string]*Modem)
|
||||||
|
Listen()
|
||||||
|
|
||||||
|
// graceful shutdown via SIGINT (^C)
|
||||||
|
csig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(csig, os.Interrupt)
|
||||||
|
|
||||||
|
// and block
|
||||||
|
<-csig
|
||||||
|
|
||||||
|
log.Info("Server shutting down")
|
||||||
|
|
||||||
|
// shutdown listeners here
|
||||||
|
for _, listener := range Listeners {
|
||||||
|
listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown active modems
|
||||||
|
for _, modem := range Modems {
|
||||||
|
modem.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing left to do
|
||||||
|
log.Info("Shutdown complete, all done")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// end of code
|
Loading…
x
Reference in New Issue
Block a user