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