Skip to content
This repository was archived by the owner on Jun 5, 2019. It is now read-only.

Commit 071f4f6

Browse files
xtdathrasher-
authored andcommitted
New NTP Client (thrasher-corp#277)
* WIP * Added check for time out of sync * merged upstream/master * added tests * Increased configuration options for NTPclient and test coverage * removed unneeded config save at end of ntp update * Added test for empty response to confirm it will loop * formatting correction * converted to pointer to allow for default allowance settings to be checked * added readme for NTP server * corrected some formatting * updated configtest negativedifference value * gofmt config_test.go for correct import order * corrected typo value in test * bugfix for windows newline and changes based on PR feedback * added minus sign to output * fixed negative number input * Fixed spelling mistakes and removed redundant test * reverted back to a positive number in the config instead of negative for allowednegativedifference * restructured code for cleaner output
1 parent 400c1cc commit 071f4f6

8 files changed

+273
-0
lines changed

config/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,23 @@ comm method and add in your contact list if available.
211211
},
212212
```
213213
214+
215+
## Configure Network Time Server
216+
217+
+ To configure and enable a NTP server you need to set the "enabled" field to one of 3 values -1 is disabled 0 is enabled and alert at start up 1 is enabled and warn at start up
218+
servers are configured by the pool array and attempted first to last allowedDifference and allowedNegativeDifference are how far ahead and behind is acceptable for the time to be out in nanoseconds
219+
220+
```js
221+
"ntpclient": {
222+
"enabled": 0,
223+
"pool": [
224+
"pool.ntp.org:123"
225+
],
226+
"allowedDifference": 0,
227+
"allowedNegativeDifference": 0
228+
},
229+
```
230+
214231
### Please click GoDocs chevron above to view current GoDoc information for this package
215232
216233
## Contribution

config/config.go

+69
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package config
22

33
import (
4+
"bufio"
45
"encoding/json"
56
"errors"
67
"flag"
78
"fmt"
9+
"io"
810
"os"
911
"path"
1012
"runtime"
1113
"strconv"
14+
"strings"
1215
"sync"
1316
"time"
1417

@@ -32,6 +35,8 @@ const (
3235
configPairsLastUpdatedWarningThreshold = 30 // 30 days
3336
configDefaultHTTPTimeout = time.Second * 15
3437
configMaxAuthFailres = 3
38+
defaultNTPAllowedDifference = 50000000
39+
defaultNTPAllowedNegativeDifference = 50000000
3540
)
3641

3742
// Constants here hold some messages
@@ -104,6 +109,7 @@ type Config struct {
104109
GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"`
105110
Logging log.Logging `json:"logging"`
106111
Profiler ProfilerConfig `json:"profiler"`
112+
NTPClient NTPClientConfig `json:"ntpclient"`
107113
Currency CurrencyConfig `json:"currencyConfig"`
108114
Communications CommunicationsConfig `json:"communications"`
109115
Portfolio portfolio.Base `json:"portfolioAddresses"`
@@ -122,6 +128,13 @@ type ProfilerConfig struct {
122128
Enabled bool `json:"enabled"`
123129
}
124130

131+
type NTPClientConfig struct {
132+
Level int `json:"enabled"`
133+
Pool []string `json:"pool"`
134+
AllowedDifference *time.Duration `json:"allowedDifference"`
135+
AllowedNegativeDifference *time.Duration `json:"allowedNegativeDifference"`
136+
}
137+
125138
// ExchangeConfig holds all the information needed for each enabled Exchange.
126139
type ExchangeConfig struct {
127140
Name string `json:"name"`
@@ -1086,6 +1099,62 @@ func (c *Config) CheckLoggerConfig() error {
10861099
return nil
10871100
}
10881101

1102+
// CheckNTPConfig() checks for missing or incorrectly configured NTPClient and recreates with known safe defaults
1103+
func (c *Config) CheckNTPConfig() {
1104+
m.Lock()
1105+
defer m.Unlock()
1106+
1107+
if c.NTPClient.AllowedDifference == nil || *c.NTPClient.AllowedDifference == 0 {
1108+
c.NTPClient.AllowedDifference = new(time.Duration)
1109+
*c.NTPClient.AllowedDifference = defaultNTPAllowedDifference
1110+
}
1111+
1112+
if c.NTPClient.AllowedNegativeDifference == nil || *c.NTPClient.AllowedNegativeDifference <= 0 {
1113+
c.NTPClient.AllowedNegativeDifference = new(time.Duration)
1114+
*c.NTPClient.AllowedNegativeDifference = defaultNTPAllowedNegativeDifference
1115+
}
1116+
1117+
if len(c.NTPClient.Pool) < 1 {
1118+
log.Warn("NTPClient enabled with no servers configured enabling default pool")
1119+
c.NTPClient.Pool = []string{"pool.ntp.org:123"}
1120+
}
1121+
}
1122+
1123+
// DisableNTPCheck() allows the user to change how they are prompted for timesync alerts
1124+
func (c *Config) DisableNTPCheck(input io.Reader) (string, error) {
1125+
m.Lock()
1126+
defer m.Unlock()
1127+
1128+
reader := bufio.NewReader(input)
1129+
log.Warn("Your system time is out of sync this may cause issues with trading")
1130+
log.Warn("How would you like to show future notifications? (a)lert / (w)arn / (d)isable \n")
1131+
1132+
var answered = false
1133+
for ok := true; ok; ok = (!answered) {
1134+
answer, err := reader.ReadString('\n')
1135+
if err != nil {
1136+
return "", err
1137+
}
1138+
1139+
answer = strings.TrimRight(answer, "\r\n")
1140+
switch answer {
1141+
case "a":
1142+
c.NTPClient.Level = 0
1143+
answered = true
1144+
return "Time sync has been set to alert", nil
1145+
case "w":
1146+
c.NTPClient.Level = 1
1147+
answered = true
1148+
return "Time sync has been set to warn only", nil
1149+
case "d":
1150+
c.NTPClient.Level = -1
1151+
answered = true
1152+
return "Future notications for out time sync have been disabled", nil
1153+
}
1154+
}
1155+
return "", errors.New("something went wrong NTPCheck should never make it this far")
1156+
}
1157+
10891158
// GetFilePath returns the desired config file or the default config file name
10901159
// based on if the application is being run under test or normal mode.
10911160
func GetFilePath(file string) (string, error) {

config/config_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package config
22

33
import (
4+
"strings"
45
"testing"
56

67
"github.com/thrasher-/gocryptotrader/common"
78
"github.com/thrasher-/gocryptotrader/currency"
89
log "github.com/thrasher-/gocryptotrader/logger"
10+
"github.com/thrasher-/gocryptotrader/ntpclient"
911
)
1012

1113
const (
@@ -967,3 +969,61 @@ func TestCheckLoggerConfig(t *testing.T) {
967969
t.Errorf("Failed to create logger with user settings: reason: %v", err)
968970
}
969971
}
972+
973+
func TestDisableNTPCheck(t *testing.T) {
974+
c := GetConfig()
975+
err := c.LoadConfig(ConfigTestFile)
976+
if err != nil {
977+
t.Fatal(err)
978+
}
979+
980+
warn, err := c.DisableNTPCheck(strings.NewReader("w\n"))
981+
if err != nil {
982+
t.Fatalf("test failed to create ntpclient failed reason: %v", err)
983+
}
984+
985+
if warn != "Time sync has been set to warn only" {
986+
t.Errorf("failed expected %v got %v", "Time sync has been set to warn only", warn)
987+
}
988+
alert, _ := c.DisableNTPCheck(strings.NewReader("a\n"))
989+
if alert != "Time sync has been set to alert" {
990+
t.Errorf("failed expected %v got %v", "Time sync has been set to alert", alert)
991+
}
992+
993+
disable, _ := c.DisableNTPCheck(strings.NewReader("d\n"))
994+
if disable != "Future notications for out time sync have been disabled" {
995+
t.Errorf("failed expected %v got %v", "Future notications for out time sync have been disabled", disable)
996+
}
997+
998+
_, err = c.DisableNTPCheck(strings.NewReader(" "))
999+
if err.Error() != "EOF" {
1000+
t.Errorf("failed expected EOF got: %v", err)
1001+
}
1002+
}
1003+
1004+
func TestCheckNTPConfig(t *testing.T) {
1005+
c := GetConfig()
1006+
1007+
c.NTPClient.Level = 0
1008+
c.NTPClient.Pool = nil
1009+
c.NTPClient.AllowedNegativeDifference = nil
1010+
c.NTPClient.AllowedDifference = nil
1011+
1012+
c.CheckNTPConfig()
1013+
_, err := ntpclient.NTPClient(c.NTPClient.Pool)
1014+
if err != nil {
1015+
t.Fatalf("test failed to create ntpclient failed reason: %v", err)
1016+
}
1017+
1018+
if c.NTPClient.Pool[0] != "pool.ntp.org:123" {
1019+
t.Error("ntpclient with no valid pool should default to pool.ntp.org ")
1020+
}
1021+
1022+
if c.NTPClient.AllowedDifference == nil {
1023+
t.Error("ntpclient with nil alloweddifference should default to sane value")
1024+
}
1025+
1026+
if c.NTPClient.AllowedNegativeDifference == nil {
1027+
t.Error("ntpclient with nil allowednegativedifference should default to sane value")
1028+
}
1029+
}

config_example.json

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
"profiler": {
1313
"enabled": false
1414
},
15+
"ntpclient": {
16+
"enabled": 0,
17+
"pool": [
18+
"pool.ntp.org:123"
19+
],
20+
"allowedDifference": 50000000,
21+
"allowedNegativeDifference": 50000000
22+
},
1523
"currencyConfig": {
1624
"forexProviders": [
1725
{

main.go

+26
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"runtime"
1010
"strconv"
1111
"syscall"
12+
"time"
1213

1314
"github.com/thrasher-/gocryptotrader/common"
1415
"github.com/thrasher-/gocryptotrader/communications"
@@ -17,6 +18,7 @@ import (
1718
"github.com/thrasher-/gocryptotrader/currency/coinmarketcap"
1819
exchange "github.com/thrasher-/gocryptotrader/exchanges"
1920
log "github.com/thrasher-/gocryptotrader/logger"
21+
"github.com/thrasher-/gocryptotrader/ntpclient"
2022
"github.com/thrasher-/gocryptotrader/portfolio"
2123
)
2224

@@ -103,6 +105,30 @@ func main() {
103105
log.Errorf("Failed to setup logger reason: %s", err)
104106
}
105107

108+
if bot.config.NTPClient.Level != -1 {
109+
bot.config.CheckNTPConfig()
110+
NTPTime, errNTP := ntpclient.NTPClient(bot.config.NTPClient.Pool)
111+
currentTime := time.Now()
112+
if errNTP != nil {
113+
log.Warnf("NTPClient failed to create: %v", errNTP)
114+
} else {
115+
NTPcurrentTimeDifference := NTPTime.Sub(currentTime)
116+
configNTPTime := *bot.config.NTPClient.AllowedDifference
117+
configNTPNegativeTime := (*bot.config.NTPClient.AllowedNegativeDifference - (*bot.config.NTPClient.AllowedNegativeDifference * 2))
118+
if NTPcurrentTimeDifference > configNTPTime || NTPcurrentTimeDifference < configNTPNegativeTime {
119+
log.Warnf("Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime)
120+
if bot.config.NTPClient.Level == 0 {
121+
disable, errNTP := bot.config.DisableNTPCheck(os.Stdin)
122+
if errNTP != nil {
123+
log.Errorf("failed to disable ntp time check reason: %v", err)
124+
} else {
125+
log.Info(disable)
126+
}
127+
}
128+
}
129+
}
130+
}
131+
106132
AdjustGoMaxProcs()
107133
log.Debugf("Bot '%s' started.\n", bot.config.Name)
108134
log.Debugf("Bot dry run mode: %v.\n", common.IsEnabled(bot.dryRun))

ntpclient/ntpclient.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package ntpclient
2+
3+
import (
4+
"encoding/binary"
5+
"errors"
6+
"net"
7+
"time"
8+
9+
log "github.com/thrasher-/gocryptotrader/logger"
10+
)
11+
12+
type ntppacket struct {
13+
Settings uint8 // leap yr indicator, ver number, and mode
14+
Stratum uint8 // stratum of local clock
15+
Poll int8 // poll exponent
16+
Precision int8 // precision exponent
17+
RootDelay uint32 // root delay
18+
RootDispersion uint32 // root dispersion
19+
ReferenceID uint32 // reference id
20+
RefTimeSec uint32 // reference timestamp sec
21+
RefTimeFrac uint32 // reference timestamp fractional
22+
OrigTimeSec uint32 // origin time secs
23+
OrigTimeFrac uint32 // origin time fractional
24+
RxTimeSec uint32 // receive time secs
25+
RxTimeFrac uint32 // receive time frac
26+
TxTimeSec uint32 // transmit time secs
27+
TxTimeFrac uint32 // transmit time frac
28+
}
29+
30+
// NTPClient create's a new NTPClient and returns local based on ntp servers provided timestamp
31+
func NTPClient(pool []string) (time.Time, error) {
32+
for i := range pool {
33+
con, err := net.Dial("udp", pool[i])
34+
if err != nil {
35+
log.Warnf("Unable to connect to hosts %v attempting next", pool[i])
36+
continue
37+
}
38+
39+
defer con.Close()
40+
41+
con.SetDeadline(time.Now().Add(5 * time.Second))
42+
43+
req := &ntppacket{Settings: 0x1B}
44+
if err := binary.Write(con, binary.BigEndian, req); err != nil {
45+
continue
46+
}
47+
48+
rsp := &ntppacket{}
49+
if err := binary.Read(con, binary.BigEndian, rsp); err != nil {
50+
continue
51+
}
52+
53+
secs := float64(rsp.TxTimeSec) - 2208988800
54+
nanos := (int64(rsp.TxTimeFrac) * 1e9) >> 32
55+
56+
return time.Unix(int64(secs), nanos), nil
57+
}
58+
return time.Unix(0, 0), errors.New("no valid time servers")
59+
}

ntpclient/ntpclient_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ntpclient
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestNTPClient(t *testing.T) {
8+
pool := []string{"pool.ntp.org:123", "0.pool.ntp.org:123"}
9+
_, err := NTPClient(pool)
10+
if err != nil {
11+
t.Fatalf("failed to get time %v", err)
12+
}
13+
14+
invalidpool := []string{"pool.thisisinvalid.org"}
15+
_, err = NTPClient(invalidpool)
16+
if err == nil {
17+
t.Errorf("failed to get time %v", err)
18+
}
19+
20+
firstInvalid := []string{"pool.thisisinvalid.org", "pool.ntp.org:123", "0.pool.ntp.org:123"}
21+
_, err = NTPClient(firstInvalid)
22+
if err != nil {
23+
t.Errorf("failed to get time %v", err)
24+
}
25+
}

testdata/configtest.json

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
"profiler": {
1313
"enabled": false
1414
},
15+
"ntpclient": {
16+
"enabled": 0,
17+
"pool": [
18+
"0.pool.ntp.org:123",
19+
"pool.ntp.org:123"
20+
],
21+
"allowedDifference": 50000000,
22+
"allowedNegativeDifference": 50000000
23+
},
1524
"currencyConfig": {
1625
"forexProviders": [
1726
{

0 commit comments

Comments
 (0)