Skip to content

Commit

Permalink
Implement fallback to softmodem for failed T.38 transmissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Markus Lindenberg committed May 28, 2014
1 parent 4b28668 commit 00d9045
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 9 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ curl http://files.freeswitch.org/repo/deb/debian/freeswitch_archive_g0.pub | apt

```
apt-get update
apt-get install hylafax-server freeswitch freeswitch-mod-commands freeswitch-mod-dptools freeswitch-mod-event-socket freeswitch-mod-logfile freeswitch-mod-sofia freeswitch-mod-spandsp freeswitch-mod-timerfd freeswitch-mod-tone-stream freeswitch-sysvinit
apt-get install hylafax-server freeswitch freeswitch-mod-commands freeswitch-mod-dptools freeswitch-mod-event-socket freeswitch-mod-logfile freeswitch-mod-sofia freeswitch-mod-spandsp freeswitch-mod-timerfd freeswitch-mod-tone-stream freeswitch-mod-db freeswitch-sysvinit
```

It is recommended to run gofaxd using a process management system as it doesn't daemonize by itself. A configuration file for the supervisor process control system is provided in the GOfax.IP repository. To use it, the supervisor packae has to be installed:
Expand Down Expand Up @@ -178,3 +178,12 @@ The following arguments are provided to the `DynamicConfig` script for outgoing
* `LocalIdentifier: +1 234 567` will assign a TSI (Transmitting Station Identifier) for this call. The Default TSI can be set in `gofax.conf` in the `ident` parameter.
* `TagLine: ACME` will assign a header string to be shown on the top of each page. This does not support format strings as used by HylaFAX; if defined a header string is always shown with the current timestamp and page number as set by SpanDSP.
* `FAXNumber: 1337` will set the outgoing caller id number as used by FreeSWITCH when originating the call.

### Fallback from T.38 to SpanDSP softmodem

In rare cases we noticed problems with certain remote stations that could not successfully negotiate with some T.38 Gateways we tested. In the case we observed, the remote tried to use T.4 1-D compression with ECM enabled. After disabling T.38 the fax was successfully received.

To work around this rare sort of problem and improve compatiblity, GOfax.IP can identify failed transmissions and dynamically disable T.38 for the affected remote station and have FreeSWITCH use SpanDSP's pure software fax implementation. The station is identified by caller id and saved in FreeSWITCH's `mod_db`.
To enable this feature, set `softmodemfallback = false` in `gofax.conf`.

Note that this will only affect all subsequent calls from/to the remote station, assuming that the remote station will retry a failed fax. Entries in the fallback list are persistant and will not be expired by GOfax.IP. It is possible however to manually expire entries. The used `<realm>/<key>/<value>` is `fallback/<callerid>/<unix_timestamp>`, with unix_timestamp being the time when the entry was added. See https://wiki.freeswitch.org/wiki/Mod_db for details.
5 changes: 5 additions & 0 deletions config/freeswitch/autoload_configs/db.conf.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<configuration name="db.conf" description="LIMIT DB Configuration">
<settings>
<!--<param name="odbc-dsn" value="dsn:user:pass"/>-->
</settings>
</configuration>
8 changes: 8 additions & 0 deletions config/gofax.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ header = "GONICUS LABS"
; Enable to get excessive SpanDSP debug messages in FreeSWITCH
verbose = false

; Disable T.38
disablet38 = false

; Enable fallback to softmodem for the next call of
; a caller (identified by caller id) if negotiation using T.38 fails
; Persistent fallback data is saved in FreeSWITCH's mod_db
softmodemfallback = false

[hylafax]
spooldir = /var/spool/hylafax

Expand Down
54 changes: 52 additions & 2 deletions src/gofaxd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ func (e *EventSocketServer) handler(c *eventsocket.Connection) {

logger.Logger.Println(channel_uuid, "Logging events for commid", sessionlog.CommId(), "to", sessionlog.Logfile())
sessionlog.Log("Inbound channel UUID: ", channel_uuid)

// Check if T.38 should be disabled
disable_t38 := gofaxlib.Config.Freeswitch.DisableT38
if disable_t38 {
sessionlog.Log("T.38 disabled by configuration")
} else {
disable_t38, err = gofaxlib.GetSoftmodemFallback(nil, cidnum)
if err != nil {
sessionlog.Log(err)
disable_t38 = false
}
if disable_t38 {
sessionlog.Log(fmt.Sprintf("Softmodem fallback active for caller %s, disabling T.38", cidnum))
}
}

sessionlog.Log(fmt.Sprintf("Accepting call to %v from %v <%v> with commid %v", recipient, cidname, cidnum, sessionlog.CommId()))

if device != nil {
Expand Down Expand Up @@ -191,8 +207,12 @@ func (e *EventSocketServer) handler(c *eventsocket.Connection) {

sessionlog.Log("Rxfax to", filename_abs)

c.Execute("set", "fax_enable_t38_request=true", true)
c.Execute("set", "fax_enable_t38=true", true)
if disable_t38 {
c.Execute("set", "fax_enable_t38=false", true)
} else {
c.Execute("set", "fax_enable_t38_request=true", true)
c.Execute("set", "fax_enable_t38=true", true)
}
c.Execute("set", fmt.Sprintf("fax_ident=%s", csi), true)
c.Execute("rxfax", filename_abs, true)

Expand Down Expand Up @@ -253,6 +273,36 @@ EventLoop:
sessionlog.Log(err)
}

// If reception failed:
// Check if softmodem fallback should be enabled on the next call
if gofaxlib.Config.Freeswitch.SoftmodemFallback && !result.Success {
var activate_fallback bool

if result.NegotiateCount > 1 {
// Activate fallback if negotiation was repeated
sessionlog.Log(fmt.Sprintf("Fax failed with %d negotiations, enabling softmodem fallback for calls from/to %s.", result.NegotiateCount, cidnum))
activate_fallback = true
} else {
var badrows uint
for _, p := range result.PageResults {
badrows += p.BadRows
}
if badrows > 0 {
// Activate fallback if any bad rows were present
sessionlog.Log(fmt.Sprintf("Fax failed with %d bad rows in %d pages, enabling softmodem fallback for calls from/to %s.", badrows, result.TransferredPages, cidnum))
activate_fallback = true
}
}

if activate_fallback {
err = gofaxlib.SetSoftmodemFallback(nil, cidnum, true)
if err != nil {
sessionlog.Log(err)
}
}

}

// Process received file
rcvdcmd := gofaxlib.Config.Gofaxd.FaxRcvdCmd
if rcvdcmd == "" {
Expand Down
14 changes: 8 additions & 6 deletions src/gofaxlib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ var (

type config struct {
Freeswitch struct {
Socket string
Password string
Gateway []string
Ident string
Header string
Verbose bool
Socket string
Password string
Gateway []string
Ident string
Header string
Verbose bool
DisableT38 bool
SoftmodemFallback bool
}
Hylafax struct {
Spooldir string
Expand Down
2 changes: 2 additions & 0 deletions src/gofaxlib/faxresult.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type FaxResult struct {
ResultText string
Success bool
TransferRate uint
NegotiateCount uint

PageResults []PageResult
}
Expand Down Expand Up @@ -123,6 +124,7 @@ func (f *FaxResult) AddEvent(ev *eventsocket.Event) {
case "spandsp::rxfaxnegociateresult":
fallthrough
case "spandsp::txfaxnegociateresult":
f.NegotiateCount++
if ecm := ev.Get("Fax-Ecm-Used"); ecm == "on" {
f.Ecm = true
}
Expand Down
39 changes: 39 additions & 0 deletions src/gofaxlib/fsdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package gofaxlib

import (
"fmt"
"github.com/fiorix/go-eventsocket/eventsocket"
)

func FreeSwitchDBInsert(c *eventsocket.Connection, realm string, key string, value string) error {
_, err := c.Send(fmt.Sprintf("api db insert/%s/%s/%s", realm, key, value))
if err != nil {
return err
}
return nil
}

func FreeSwitchDBDelete(c *eventsocket.Connection, realm string, key string) error {
_, err := c.Send(fmt.Sprintf("api db delete/%s/%s", realm, key))
if err != nil {
return err
}
return nil
}

func FreeSwitchDBSelect(c *eventsocket.Connection, realm string, key string) (string, error) {
result, err := c.Send(fmt.Sprintf("api db select/%s/%s", realm, key))
if err != nil {
return "", err
}
return result.Body, nil
}

func FreeSwitchDBExists(c *eventsocket.Connection, realm string, key string) (bool, error) {
result, err := c.Send(fmt.Sprintf("api db exists/%s/%s", realm, key))
if err != nil {
return false, err
}

return result.Body == "true", nil
}
50 changes: 50 additions & 0 deletions src/gofaxlib/softmodemfallback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gofaxlib

import (
"fmt"
"github.com/fiorix/go-eventsocket/eventsocket"
"time"
)

const (
MOD_DB_FALLBACK_REALM = "fallback"
)

func GetSoftmodemFallback(c *eventsocket.Connection, cidnum string) (bool, error) {
if !Config.Freeswitch.SoftmodemFallback || cidnum == "" {
return false, nil
}

var err error
if c == nil {
c, err = eventsocket.Dial(Config.Freeswitch.Socket, Config.Freeswitch.Password)
if err != nil {
return false, err
}
defer c.Close()
}

exists, err := FreeSwitchDBExists(c, MOD_DB_FALLBACK_REALM, cidnum)
if err != nil {
return false, err
}

return exists, nil
}

func SetSoftmodemFallback(c *eventsocket.Connection, cidnum string, enabled bool) error {
if !Config.Freeswitch.SoftmodemFallback || cidnum == "" {
return nil
}

var err error
if c == nil {
c, err = eventsocket.Dial(Config.Freeswitch.Socket, Config.Freeswitch.Password)
if err != nil {
return err
}
defer c.Close()
}

return FreeSwitchDBInsert(c, MOD_DB_FALLBACK_REALM, cidnum, fmt.Sprintf("%d", time.Now().Unix()))
}
52 changes: 52 additions & 0 deletions src/gofaxsend/transmission.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ func (t *transmission) start() {
return
}

// Check if T.38 should be disabled
disable_t38 := gofaxlib.Config.Freeswitch.DisableT38
if disable_t38 {
t.sessionlog.Log("T.38 disabled by configuration")
} else {
disable_t38, err = gofaxlib.GetSoftmodemFallback(t.conn, t.faxjob.Number)
if err != nil {
t.sessionlog.Log(err)
disable_t38 = false
}
if disable_t38 {
t.sessionlog.Log(fmt.Sprintf("Softmodem fallback active for destination %s, disabling T.38", t.faxjob.Number))
}
}

// Assemble dialstring
ds_variables_map := map[string]string{
"ignore_early_media": "true",
Expand All @@ -108,6 +123,12 @@ func (t *transmission) start() {
"fax_verbose": strconv.FormatBool(gofaxlib.Config.Freeswitch.Verbose),
}

if disable_t38 {
ds_variables_map["fax_enable_t38"] = "false"
} else {
ds_variables_map["fax_enable_t38"] = "true"
}

ds_variables_pairs := make([]string, len(ds_variables_map))
i := 0
for k, v := range ds_variables_map {
Expand Down Expand Up @@ -152,6 +173,37 @@ func (t *transmission) start() {
case ev := <-es.Events():
result.AddEvent(ev)
if result.Hangupcause != "" {

// If transmission failed:
// Check if softmodem fallback should be enabled on the next call
if gofaxlib.Config.Freeswitch.SoftmodemFallback && !result.Success {
var activate_fallback bool

if result.NegotiateCount > 1 {
// Activate fallback if negotiation was repeated
t.sessionlog.Log(fmt.Sprintf("Fax failed with %d negotiations, enabling softmodem fallback for calls from/to %s.", result.NegotiateCount, t.faxjob.Number))
activate_fallback = true
} else {
var badrows uint
for _, p := range result.PageResults {
badrows += p.BadRows
}
if badrows > 0 {
// Activate fallback if any bad rows were present
t.sessionlog.Log(fmt.Sprintf("Fax failed with %d bad rows in %d pages, enabling softmodem fallback for calls from/to %s.", badrows, result.TransferredPages, t.faxjob.Number))
activate_fallback = true
}
}

if activate_fallback {
err = gofaxlib.SetSoftmodemFallback(t.conn, t.faxjob.Number, true)
if err != nil {
t.sessionlog.Log(err)
}
}

}

t.resultChan <- result
return
}
Expand Down

0 comments on commit 00d9045

Please sign in to comment.