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.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