diff --git a/README.md b/README.md
index ceda8a4..e4a33b2 100644
--- a/README.md
+++ b/README.md
@@ -180,12 +180,12 @@ right protocol when setting up a connection on the client.
The server can listen on multiple ports / protocols at the same time. To configure
the server, you need to set up:
-- [Upstreams](#channels-upstreams)
+- [Upstreams](#channels)
- [Severs](#servers)
##### Channels
-Channels are configured in the YAML `channels` section. They define the external services that will be accessible
+You may configure the channels in the YAML `channels` section. They define the external services that will be accessible
through this server setup.
```yaml
@@ -196,7 +196,7 @@ server:
...
```
-You may define multiple channels (upstreams). Each cannel needs the following properties:
+You may define multiple channels (upstreams). Each channel needs the following properties:
- `name` is the unique name given to this upstream server. This is then referenced later
on in the `servers` section and on the client. A good example would be `ssh`, `web`, `oracle` etc.
@@ -205,17 +205,13 @@ You may define multiple channels (upstreams). Each cannel needs the following pr
##### Servers
-At this stage, the following `kinds` (protocols) are supported: `websocket`, `tcp`, `stdin` and `unix`, `unixpacket`
-and `udp`. To configure the server, add it to the `servers` section of the configuration.
+At this stage, the following "kinds" (protocols) are supported: `websocket`, `tcp`, `stdin` and `unix`, `unixpacket`,
+`udp`, `dns+udp` and `dns+tcp`. To configure the server, add it to the `servers` section of the configuration.
```yaml
server:
servers:
- address:
- endpoints:
- - endpoint:
- ...
- ]
[channels: [list of channels]]
[caCertificate: ]
[caCertificateFile: ]
@@ -225,12 +221,13 @@ server:
[privateKeyFile: ]
[privateKeyPassword: ]
[privateKeyPasswordProgram: ]
+ [ ... other server-specific configuration ... ]
```
Where:
- `address` is the type of server and listening location. Can be `http`, `https`, `tcp`, `tcp+tls`, `stdin`
- `stdin+tls`, `unix` or `unix+tls`, `udp` and `unixpacket`.
+ `stdin+tls`, `unix` or `unix+tls`, `udp`, `unixpacket`, `dns+udp` and `dns+tcp`.
- Always use a valid url, e.g. `tcp://0.0.0.0:5000`, `https://0.0.0.0:8900`.
- Address type will define the listening server style, e.g.
- `http` and `https` will start an HTTP / websocket server,
@@ -244,8 +241,6 @@ Where:
- You can also listen on a non-secured channel (e.g. HTTP) and provide certificate info. If provided, server will
support the `StartTLS` command, which executes TLS handshake after connecting. Especially useful if you're proxying
the connection over an existing HTTP server.
-- `endpoints` is only required or `websocket` server. It defines the list of URLs the server should listen to.
- For example `/ws/all` or `/my/secret/connection`. You may listen on multiple URLs.
- `channels` defines a list of upstream channels that this connection proxies. If not defined, all channels are
proxied.
- Define `caCertificate` or `caCertificateFile` if you want to use mutual (client and server) certificate
@@ -255,17 +250,136 @@ Where:
- `privateKey`, `privateKeyFile`, `privateKeyPassword` and `privateKeyPasswordProgram` should be pretty
self-explanatory. They must be defined when `certificate` is set up.
+###### HTTP and HTTPS (websocket) server
+
+Configure SocketAce to listen for HTTP or HTTPS requests. Example configuration is as follows:
+
+```yaml
+server:
+ servers:
+ # Setup a HTTP websocket server, answering at http://192.168.1.1:8000/ws/all
+ - address: http://192.168.1.1:8000
+ endpoints:
+ - endpoint: /ws/all
+
+ # Setup a HTTP websocket server, secured by StartTLS. This allows you to proxy
+ # secure SocketAce connections over plain :80 HTTP connection
+ - address: http://192.168.1.1:8000
+ endpoints:
+ - endpoint: /ws/all
+ certificateFile: cert.pem
+ privateKeyFile: privatekey.pem
+ privateKeyPassword: test1234
+
+ # Setup a HTTPS websocket server, answering at http://192.168.1.1:8443/ws/ssh
+ - address: http://192.168.1.1:8000
+ endpoints:
+ - channels: [ 'ssh' ]
+ endpoint: /ws/ssh
+ certificateFile: cert.pem
+ privateKeyFile: privatekey.pem
+ privateKeyPassword: test1234
+```
+
+Additional options are as follows:
+- `endpoints` defines the list of URLs the server should listen to.
+ For example `/ws/all` or `/my/secret/connection`. You may listen on multiple URLs.
+
+###### TCP socket and TLS socket server
+
+Configure SocketAce to listen on an unecrypted or encrypted socket. Example configuration is as follows:
+
+```yaml
+server:
+ servers:
+ # Simple socket proxy. No security. Expose all channels.
+ - address: tcp://192.168.1.1:9000
+ # Simple socket proxy. Secure by directly encrypting the socket.
+ - address: tcp+tls://192.168.1.1:9443
+ certificateFile: cert.pem
+ privateKeyFile: privatekey.pem
+ privateKeyPassword: test1234
+```
+
+TCP and TLS sockets require no additional options.
+
+###### UDP socket server
+
+Configure SocketAce to listen on an unecrypted UDP socket. Example configuration is as follows:
+
+```yaml
+server:
+ servers:
+ # Simple UDP proxy. Secured by StartTLS.
+ - address: udp://127.0.0.1:9992
+ certificateFile: cert.pem
+ privateKeyFile: privatekey.pem
+ privateKeyPassword: test1234
+```
+
+###### Standard input/output server
+
+SokcetAce can also listen on standard input/output. This allows you to carry the SocketAce connection
+over alternative means (e.g. via SSH, TELNET or serial ports). As long as you can then pipe it to a
+standard input / output, you're good to go. *Notice that this option may be used only once.*
+
+```yaml
+server:
+ servers:
+ # Simple socket proxy listening on stdin/stdout.
+ - address: "stdin://"
+```
+
+```yaml
+server:
+ servers:
+ # Simple socket proxy listening on stdin/stdout. Secured by StartTLS.
+ - address: "stdin://"
+ certificateFile: cert.pem
+ privateKeyFile: privatekey.pem
+ privateKeyPassword: test1234
+```
+
+###### DNS server
+
+SocketAce may be proxied over DNS server. It works similar to [iodine](https://github.com/yarrick/iodine) (in fact,
+much of the code was referenced from there) but carries a SocketAce connection instead. Gone is the shared-secret
+password and SocketAce security is used in stead. *Note that it might be a good idea to use mutual TLS authentication
+with public DNS servers.*
+
+```yaml
+server:
+ servers:
+ # UDP DNS server for SocketAce-over-DNS Secured by StartTLS.
+ - address: "dns+udp://192.168.8.1:53"
+ domain: "example.org"
+ certificateFile: cert.pem
+ privateKeyFile: privatekey.pem
+ privateKeyPassword: test1234
+ - address: "dns+tcp://192.168.8.1:53"
+ domain: "example.org"
+ certificateFile: cert.pem
+ privateKeyFile: privatekey.pem
+ privateKeyPassword: test1234
+```
+
+The `domain` represents the listening domain. You will need to make your server an authorative nameserver for
+this domain. Check the [iodine](https://github.com/yarrick/iodine)'s tutorial on how to do this if you are not certain.
+
+
#### Client
-Client configuration is a bit simple and can be done via a config file or via a command line. Basically, only
+Client configuration is a bit simpler and can be done via a config file or via a command line. Basically, only
two options are important:
- `--upstream ` may be specified multiple times. Defines a list of upstream servers that the client will
try to connect to. The format is `[://]`. Protocol may be any of the following: `tcp`,
- `tcp+tls`, `stdin`, `stdin+tls`, `unix`, `unix+tls`, `http`, `https`, `unixgram` or `udp`. Examples:
+ `tcp+tls`, `stdin`, `stdin+tls`, `unix`, `unix+tls`, `http`, `https`, `unixgram`, `udp` or `dns`. Examples:
- `tcp://127.0.0.1:9995` to connect to a socket server on `localhost` on `9995`
- `udp://127.0.0.1:9993` to connect to a UDP server on `localhost` on `9993`
- `tcp+tls://127.0.0.1:9995` to connect to a TLS-encrypted socket server on `localhost` on `9995`
+ - `dns://example.org` connect via auto-detected DNS servers, try connecting directly first
+ - `dns://example.org?dns=1.1.1.1,1.0.0.1&direct=false` connect via provided DNS servers
- `stdin` to connect to server through standard input / output
- `--listen ~[~]` will open a listening socket on the client.
- `channel` name must be the same as defined on the server.
@@ -300,6 +414,23 @@ ssh localhost -o ProxyCommand='socketace client --upstream http://127.0.0.1:9999
socketace client -e tcp+tls://server.example.com:80 --listen imap~tcp://127.0.0.1:143 --listen imap~tcp://127.0.0.2:587
```
+##### Use socketace to gradually try different connection methods, by the order of throughoutput
+
+If you configure your SSH `ProxyCommand` like the following, you should be able to connect to your SSH server even
+in the most restrictive environments. SocketAce will try to connect to the server in decreasing order of preference
+through different connection tunnels. The first one to succeeed will establish the connection.
+
+```shell script
+socketace client \
+ --upstream udp://server.example.com:8000 \ # Try UDP first...
+ --upstream tcp://server.example.com:8443 \ # ...then try TCP
+ --upstream http://server.example.com/socketace \ # ...then try HTTP
+ --upstream https://server.example.com/socketace \ # ...then try HTTPS
+ --upstream dns://server.example.com \ # ...finally try over DNS
+ --listen ssh~stdin://
+```
+
+
## Caveats
### Connecting to a secure (TLS-enabled) service
@@ -347,11 +478,11 @@ curl -H "Host: www.google.com" https://localhost:9898
There's still some things to be done. If anybody's willing to pick up issues, pull
requests are welcome:
+- add functionality similar to [sslh](https://github.com/yrutschle/sslh) to be able to "hide" the proxy and share the
+ port with other services
- add proxying of UDP connections
-- add functionality similar to [sslh](https://github.com/yrutschle/sslh) to be able to
- "hide" the proxy and share the port with other services
-- add the possibility of proxying connections through DNS, similar to how
- [iodine](https://github.com/yarrick/iodine) works
+- document the SOCKS proxy option and add tests
+- add support for TUN (and TAP?) connections
## Similar projects
diff --git a/examples/config.yaml b/examples/config.yaml
index 3526663..f00a1e2 100644
--- a/examples/config.yaml
+++ b/examples/config.yaml
@@ -101,3 +101,9 @@ server:
certificateFile: cert.pem
privateKeyFile: privatekey.pem
privateKeyPassword: test1234
+
+ - address: dns+udp://127.0.0.1:9991 # DNS server for SocketAce-over-DNS
+ domain: example.org # The DNS server requires the top-level domain
+
+ - address: dns+tcp://127.0.0.1:9990 # DNS server for SocketAce-over-DNS
+ domain: example.org # The DNS server requires the top-level domain
diff --git a/internal/client/upstream/dns.go b/internal/client/upstream/dns.go
index 1d7032b..6060481 100644
--- a/internal/client/upstream/dns.go
+++ b/internal/client/upstream/dns.go
@@ -1,6 +1,8 @@
package upstream
import (
+ "bufio"
+ "bytes"
"github.com/bokysan/socketace/v2/internal/socketace"
"github.com/bokysan/socketace/v2/internal/streams"
"github.com/bokysan/socketace/v2/internal/streams/dns"
@@ -8,9 +10,24 @@ import (
"github.com/bokysan/socketace/v2/internal/util/cert"
dns2 "github.com/miekg/dns"
"github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+ "os/exec"
+ "runtime"
+ "strings"
)
-// Socket connects to the server via a socket connection
+// Dns will create a client which will establish a SocketAce connection via DNS request-response loop. This is not
+// the fastest nor the most optimal way to establish connections. It is, however, the only way to do it in some cases.
+// You won't be able to stream movies, but this should be sufficient to connect to a remote terminal session, check
+// your email and do some light browsing.
+//
+// DNS connections are a bit more complex as a lot of probing needs to be done before the connection is established.
+// The steps to establish a DNS connection are as follows:
+// - Try connecting directly via TCP to our target host (if direct connection is allowed). Failing that,
+// - Try connecting directly via UDP to our target host. Failing that,
+// - Use user supplied DNS servers, if they are available. And finally,
+// - Use the system provided DNS servers
+//
type Dns struct {
streams.Connection
@@ -24,39 +41,101 @@ func (ups *Dns) String() string {
func (ups *Dns) Connect(manager cert.TlsConfig, mustSecure bool) error {
- a := ups.Address
- switch a.Scheme {
- case "dns":
- a.Scheme = "udp"
- case "dns+unix", "dns+unixgram":
- a.Scheme = "unixgram"
- default:
- return errors.Errorf("This impementation does not know how to handle %s", ups.Address.String())
+ if ups.Address.Scheme != "dns" {
+ return errors.Errorf("DNS can only handle 'dns' schemes. Cannot handle: %q", ups.Address.String())
}
- topDomain := a.Hostname()
+ topDomain := ups.Address.Hostname()
- var dnsList []string
+ var err error
- if d, ok := a.Query()["dns"]; ok {
- dnsList = d
+ addDirect := true
+ if x, ok := ups.Address.Query()["direct"]; ok {
+ if strings.ToLower(x[0]) == "false" {
+ addDirect = false
+ }
}
- conf := &dns2.ClientConfig{
- Servers: dnsList,
+ servers := make(dns.AddressList, 0)
+ if addDirect {
+ servers.ResolveAndAddAddress(topDomain)
}
- comm, err := dns.NewNetConnectionClientCommunicator(conf)
- if err != nil {
- return err
+
+ // Get a list of alternate DNS servers
+ if x, ok := ups.Address.Query()["dns"]; ok {
+ // Allow for ?dns=1.2.3.4&dns=5.6.7.8
+ for _, y := range x {
+ // And for ?dns=1.2.3.4,5.6.7.8 syntax
+ for _, z := range strings.Split(y, ",") {
+ servers.ResolveAndAddAddress(z)
+ }
+ }
}
- conn, err := dns.NewClientDnsConnection(topDomain, comm)
- if err != nil {
- return err
+ if runtime.GOOS == "windows" {
+ log.Debugf("Adding servers from ipconfig /all")
+ ups.addWindowsDnsServers(servers)
+ } else if runtime.GOOS == "darwin" {
+ log.Debugf("Adding servers from scutil --dns")
+ ups.addDarwinDnsServers(servers)
+ }
+
+ c, err := dns2.ClientConfigFromFile("/etc/resolv.conf")
+ if err == nil {
+ log.Debugf("Adding servers from /etc/resolv.conf")
+ for _, server := range c.Servers {
+ servers.ResolveAndAddAddress(server)
+ }
+ }
+
+ conf := &dns.ClientConfig{
+ Servers: servers,
}
- if err = conn.Handshake(); err != nil {
- return err
+ var conn *dns.ClientDnsConnection
+
+main:
+ for true {
+ var comm dns.ClientCommunicator
+ var err error
+
+ comm, err = dns.NewNetConnectionClientCommunicator(conf)
+ if err != nil {
+ return err
+ }
+
+ conn, err = dns.NewClientDnsConnection(topDomain, comm)
+ if err != nil {
+ return err
+ }
+
+ if err = conn.Handshake(); err != nil {
+ if len(conf.Servers) == 1 {
+ // Nothing more to do, give up
+ return err
+ }
+
+ // There can be a case where the server responds but does not allow our queries to go through.
+ // In such case we should remove the server from the list and with the reduced list
+ for i := len(servers) - 1; i >= 0; i-- {
+ if servers[i].String() == comm.RemoteAddr().String() && servers[i].Network() == comm.RemoteAddr().Network() {
+ conf.Servers = servers[i+1:]
+ if len(conf.Servers) == 0 {
+ return errors.Wrapf(err, "No more servers to try, sorry.")
+ } else {
+ continue main
+ }
+ }
+ }
+
+ return errors.Wrapf(err, "Tried all servers on the list, but no success -- is DNS opened: %v", servers)
+ } else {
+ break
+ }
+ }
+
+ if conn == nil {
+ return errors.Errorf("Connection not established!")
}
cc, err := socketace.NewClientConnection(conn, manager, false, ups.Address.Host)
@@ -70,3 +149,74 @@ func (ups *Dns) Connect(manager cert.TlsConfig, mustSecure bool) error {
return nil
}
+
+// Capturing the output of ipconfig command is not really the nicest way to go about it, but for the time being
+// it will need to do.
+func (ups *Dns) addWindowsDnsServers(servers dns.AddressList) {
+ buf := &bytes.Buffer{}
+ cmd := exec.Command("ipconfig", "/all")
+ cmd.Stdout = buf
+ err := cmd.Run()
+ if err != nil {
+ log.Debugf("Can't run ipconfig -- will ignore output: %v", err)
+ } else {
+ scanner := bufio.NewScanner(buf)
+ inDns := false
+ for scanner.Scan() {
+ if err := scanner.Err(); err != nil {
+ log.Warnf("Failed parsing output from scutil: %v", err)
+ break
+ }
+ line := scanner.Text()
+
+ if inDns {
+ if strings.Contains(line, ". :") {
+ inDns = false
+ } else {
+ servers.ResolveAndAddAddress(strings.TrimSpace(line))
+ }
+ } else if strings.HasPrefix(strings.TrimSpace(line), "DNS Servers") {
+ d := strings.Split(line, ":")
+ if len(d) > 1 {
+ inDns = true
+ servers.ResolveAndAddAddress(strings.TrimSpace(d[1]))
+ } else {
+ log.Warnf("Invalid inline in ipconfig response: %q -- ignoring segment", line)
+ }
+ }
+ }
+ }
+}
+
+// Capturing the output of scutil command is not really the nicest way to go about it, but for the time being
+// it will need to do.
+func (ups *Dns) addDarwinDnsServers(servers dns.AddressList) {
+ buf := &bytes.Buffer{}
+ cmd := exec.Command("scutil", "--dns")
+ cmd.Stdout = buf
+ err := cmd.Run()
+ if err != nil {
+ log.Debugf("Can't run scutil -- will ignore output: %v", err)
+ } else {
+ scanner := bufio.NewScanner(buf)
+ for scanner.Scan() {
+ if err := scanner.Err(); err != nil {
+ log.Warnf("Failed parsing output from scutil: %v", err)
+ break
+ }
+ line := scanner.Text()
+ f := strings.Fields(line)
+
+ // Look for
+ // nameserver[0] : 192.168.8.1
+ if len(f) < 3 {
+ continue
+ }
+
+ if strings.HasPrefix(f[0], "nameserver") {
+ servers.ResolveAndAddAddress(f[2])
+ }
+
+ }
+ }
+}
diff --git a/internal/flags/yamlparser.go b/internal/flags/yamlparser.go
index db5fd51..5815a08 100644
--- a/internal/flags/yamlparser.go
+++ b/internal/flags/yamlparser.go
@@ -13,7 +13,9 @@ import (
"unsafe"
)
-// YamlParser is an argument parser for flags package but takes a YAML file instead of a standard INI.
+// YamlParser is an argument parser for flags package but takes a YAML file instead of a standard INI. This allows it
+// to parse configuration directives in a more complex / hierarhical manner. While the INI file only has two levels,
+// a YAML file can have multiple. And every level can be either a value, a map or a list.
type YamlParser struct {
ParseAsDefaults bool // override default flags
parser *flags.Parser
@@ -26,8 +28,8 @@ func NewYamlParser(p *flags.Parser) *YamlParser {
}
}
-// ParseFile parses flags from an yaml formatted file. The returned errors
-// can be of the type flags.Error or flags.IniError.
+// ParseFile parses flags from an yaml formatted file. The returned errors can be of the type flags.Error or
+// flags.IniError.
func (y *YamlParser) ParseFile(filename string) error {
body, err := os.Open(filename)
diff --git a/internal/it/integration1_test.go b/internal/it/integration1_test.go
index e6cad35..4f51259 100644
--- a/internal/it/integration1_test.go
+++ b/internal/it/integration1_test.go
@@ -26,8 +26,11 @@ var echoServiceAddress = addr.MustParseAddress("tcp://" + "127.0.0.1:" + strconv
type closer func()
func echoService(r io.ReadCloser, w io.WriteCloser) error {
- defer streams.TryClose(r)
- defer streams.TryClose(w)
+ defer func() {
+ log.Debugf("(echo) Closing streams...")
+ r.Close()
+ w.Close()
+ }()
scanner := bufio.NewReader(r)
@@ -36,6 +39,7 @@ func echoService(r io.ReadCloser, w io.WriteCloser) error {
l, prefix, err := scanner.ReadLine()
if err == io.EOF {
if len(line) == 0 {
+ log.Debugf("(echo) EOF")
return nil
}
} else if err != nil {
@@ -47,13 +51,12 @@ func echoService(r io.ReadCloser, w io.WriteCloser) error {
line = append(line, l...)
}
- log.Tracef("Got: %v", string(line))
+ log.Tracef("(echo) Received: %v", string(line))
response := append(line, '\r', '\n')
if _, err := w.Write(response); err != nil {
return err
}
- log.Tracef("Wrote: %v", string(response))
-
+ log.Tracef("(echo) Wrote: %v", string(response[0:len(response)-2]))
if string(line) == "QUIT" {
break
}
@@ -224,6 +227,75 @@ func Test_UdpConnection(t *testing.T) {
}
+func Test_DnsConnection(t *testing.T) {
+
+ localServiceAddress := addr.MustParseAddress("tcp://localhost:" + strconv.Itoa(echoServicePort+12))
+ dnsListenAddress := addr.MustParseAddress("dns://localhost:" + strconv.Itoa(echoServicePort+13))
+
+ s := serverCmd.Command{
+ Channels: server.Channels{
+ &server.NetworkChannel{
+ AbstractChannel: server.AbstractChannel{
+ ProtoName: addr.ProtoName{
+ Name: "echo",
+ },
+ Address: echoServiceAddress,
+ },
+ },
+ },
+ Servers: server.Servers{
+ &server.DnsServer{
+ Domain: "example.org",
+ SocketServer: server.SocketServer{
+ Address: dnsListenAddress,
+ },
+ },
+ },
+ }
+
+ c := clientCmd.Command{
+ Upstream: upstream.Upstreams{
+ Data: []upstream.Upstream{
+ &upstream.Dns{
+ Address: addr.MustParseAddress("dns://example.org?direct=false&dns=localhost:" + strconv.Itoa(echoServicePort+13)),
+ },
+ },
+ },
+ ListenList: listener.Listeners{
+ &listener.SocketListener{
+ AbstractListener: listener.AbstractListener{
+ ProtoName: addr.ProtoName{
+ Name: "echo",
+ },
+ Address: localServiceAddress,
+ },
+ },
+ },
+ }
+
+ interrupted := make(chan os.Signal, 1)
+ require.NoError(t, s.Startup(interrupted))
+ require.NoError(t, c.Startup(interrupted))
+
+ defer func() {
+ interrupted <- os.Interrupt
+ require.NoError(t, c.Shutdown())
+ require.NoError(t, s.Shutdown())
+ }()
+
+ conn, err := net.Dial("tcp", localServiceAddress.Host)
+ require.NoError(t, err)
+
+ conn = streams.NewSafeConnection(conn)
+
+ defer streams.TryClose(conn)
+
+ helloEchoTest(t, conn)
+
+ log.Infof("Test completed.")
+
+}
+
func Test_SimpleInsecureConnection(t *testing.T) {
p1Reader, p1Writer := io.Pipe()
diff --git a/internal/server/dns_server.go b/internal/server/dns_server.go
index 9143f31..2e0d962 100644
--- a/internal/server/dns_server.go
+++ b/internal/server/dns_server.go
@@ -7,16 +7,18 @@ import (
"github.com/bokysan/socketace/v2/internal/streams/dns"
dns2 "github.com/miekg/dns"
"github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
"strings"
)
type DnsServer struct {
SocketServer
+ Domain string `json:"domain"`
}
func NewDnsServer() *DnsServer {
return &DnsServer{
- SocketServer{
+ SocketServer: SocketServer{
name: "dns",
},
}
@@ -42,23 +44,18 @@ func (st *DnsServer) Startup(channels Channels) error {
a := st.Address
switch a.Scheme {
- case "dns":
+ case "dns", "dns+udp":
a.Scheme = "udp"
case "dns+tcp":
a.Scheme = "tcp"
- case "dns+unix", "dns+unixgram":
- a.Scheme = "unixgram"
default:
- return errors.Errorf("This implementation does not know how to handle %s", st.Address.String())
+ return errors.Errorf("DNS server can only handle 'dns', 'dns+udp', 'dns+tcp' schemes, not %q", st.Address.String())
}
var comm *dns.NetConnectionServerCommunicator
server := &dns2.Server{
- Addr: "127.0.0.1:42000",
- Net: "udp",
- }
- if true {
- return errors.Errorf("Configuration not complete!")
+ Addr: a.Host,
+ Net: a.Scheme,
}
if st.secure {
@@ -77,8 +74,8 @@ func (st *DnsServer) Startup(channels Channels) error {
return errors.Wrapf(err, "Could not start DNS listener")
}
- conn := dns.NewServerDnsListener(a.Host, comm)
-
+ log.Infof("Starting DNS server at %v, listening to requests for '%v'", st.String(), st.Domain)
+ conn := dns.NewServerDnsListener(st.Domain, comm)
st.listener = conn
go func() {
diff --git a/internal/server/packet_server.go b/internal/server/packet_server.go
index 6e55202..6f200f1 100644
--- a/internal/server/packet_server.go
+++ b/internal/server/packet_server.go
@@ -71,7 +71,7 @@ func (st *PacketServer) StartupPacket(channels Channels, createListenerFunc List
}
st.Address.User = nil
- if st.PacketConnection != nil {
+ if st.PacketConnection == nil {
n, err := a.Addr()
if err != nil {
return errors.WithStack(err)
diff --git a/internal/server/server.go b/internal/server/server.go
index 52fddbe..55fae97 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -83,7 +83,7 @@ func unmarshalServer(s interface{}) (Server, error) {
server = NewSocketServer()
case "udp", "udp4", "udp6", "unixgram":
server = NewPacketServer()
- case "dns":
+ case "dns", "dns+udp", "dns+tcp", "dns+tcp+tls":
server = NewDnsServer()
default:
return nil, errors.Errorf("Unknown network type: %s", address.Scheme)
diff --git a/internal/socketace/client.go b/internal/socketace/client.go
index 9225373..ef53939 100644
--- a/internal/socketace/client.go
+++ b/internal/socketace/client.go
@@ -94,7 +94,7 @@ func (cc *ClientConnection) handshake(conn *streams.BufferedInputConnection) (er
request.Headers.Set(AcceptsProtocolVersion, version.ProtocolVersion)
request.Headers.Set(UserAgent, "socketace/"+version.AppVersion())
if err := request.Write(conn); err != nil {
- return errors.Wrapf(err, "Coud not send intial request")
+ return errors.Wrapf(err, "Coud not send initial request")
}
// Parse the response
diff --git a/internal/socketace/request.go b/internal/socketace/request.go
index 9bfaf3e..f68995e 100644
--- a/internal/socketace/request.go
+++ b/internal/socketace/request.go
@@ -55,13 +55,13 @@ func (sar *Request) String() string {
buf := bytes.NewBuffer([]byte{})
if _, err := io.WriteString(buf, command+"\r\n"); err != nil {
- panic(errors.Wrapf(err, "Error writting command"))
+ panic(errors.Wrapf(err, "Error writing command"))
}
headers := http.Header(sar.Headers)
if err := headers.Write(buf); err != nil {
- panic(errors.Wrapf(err, "Error writting headers"))
+ panic(errors.Wrapf(err, "Error writing headers"))
}
if _, err := io.WriteString(buf, "\r\n"); err != nil {
panic(errors.Wrapf(err, "Error ending headers"))
diff --git a/internal/socketace/response.go b/internal/socketace/response.go
index f2227d7..8ba6838 100644
--- a/internal/socketace/response.go
+++ b/internal/socketace/response.go
@@ -54,13 +54,13 @@ func (sar *Response) String() string {
buf := bytes.NewBuffer([]byte{})
if _, err := io.WriteString(buf, command+"\r\n"); err != nil {
- panic(errors.Wrapf(err, "Error writting command"))
+ panic(errors.Wrapf(err, "Error writing command"))
}
headers := http.Header(sar.Headers)
if err := headers.Write(buf); err != nil {
- panic(errors.Wrapf(err, "Error writting headers"))
+ panic(errors.Wrapf(err, "Error writing headers"))
}
if _, err := io.WriteString(buf, "\r\n"); err != nil {
panic(errors.Wrapf(err, "Error ending headers"))
@@ -75,7 +75,7 @@ func (sar *Response) Write(c io.Writer) error {
if _, err := c.Write([]byte(sar.String())); err != nil {
return errors.Wrapf(err, "Could not write to stream")
}
- log.Debugf("Response writen:\n%v", sar)
+ log.Debugf("Response written:\n%v", sar)
return nil
}
diff --git a/internal/streams/dns/client_communicator.go b/internal/streams/dns/client_communicator.go
index 482207a..40d50ab 100644
--- a/internal/streams/dns/client_communicator.go
+++ b/internal/streams/dns/client_communicator.go
@@ -7,6 +7,7 @@ import (
log "github.com/sirupsen/logrus"
"io"
"net"
+ "strings"
"time"
)
@@ -44,56 +45,118 @@ type NetConnectionClientCommunicator struct {
closed bool
}
-// GenerateAddress will generate an address, optionally adding the specified port if not in the string
-func GenerateAddress(server string, defaultPort string) (*net.UDPAddr, error) {
- if addr, err := net.ResolveUDPAddr("udp", server); err == nil {
- if addr.Port != 0 {
+type AddressList []net.Addr
+
+func (l *AddressList) addAddress(network string, address string) {
+ addr, err := ResolveNetworkAddress(network, address, "53")
+ if err != nil {
+ log.Warnf("Cannot resolve %v as a %v address: %v", address, network, err)
+ } else {
+ found := false
+ for _, a := range *l {
+ if a.String() == addr.String() && a.Network() == addr.Network() {
+ found = true
+ break
+ }
+ }
+ if !found {
+ log.Debugf("Adding %v to list.", address)
+ *l = append(*l, addr)
+ }
+ }
+ return
+}
+
+// ResolveAndAddAddress will try to resolve the provide string as a TCP an UDP address. And if any of these succeeed,
+// it will add the address to the list
+func (l *AddressList) ResolveAndAddAddress(address string) {
+ switch {
+ case strings.HasPrefix("tcp://", address):
+ l.addAddress("tcp", address[6:])
+ case strings.HasPrefix("udp://", address):
+ l.addAddress("udp", address[6:])
+ default:
+ l.addAddress("tcp", address)
+ l.addAddress("udp", address)
+ }
+}
+
+// ClientConfig is the configuration for the ClientCommunicator
+type ClientConfig struct {
+ Servers AddressList
+}
+
+// ResolveNetworkAddress will generate an address, optionally adding the specified port if not in the initial string. The
+// function will only work for TCP and UDP addresses.
+func ResolveNetworkAddress(network, server, defaultPort string) (net.Addr, error) {
+ switch network {
+ case "udp", "udp4", "udp6":
+ if addr, err := net.ResolveUDPAddr(network, server); err == nil {
+ if addr.Port != 0 {
+ return addr, nil
+ }
+ } else if addr, err := net.ResolveUDPAddr(network, server+":"+defaultPort); err != nil {
+ return nil, err
+ } else {
+ return addr, nil
+ }
+ case "tcp", "tcp4", "tcp6":
+ if addr, err := net.ResolveTCPAddr(network, server); err == nil {
+ if addr.Port != 0 {
+ return addr, nil
+ }
+ } else if addr, err := net.ResolveTCPAddr(network, server+":"+defaultPort); err != nil {
+ return nil, err
+ } else {
return addr, nil
}
- } else if addr, err := net.ResolveUDPAddr("udp", server+":"+defaultPort); err != nil {
- return nil, err
- } else {
- return addr, nil
}
return nil, errors.New("Could not generate an address")
}
-func NewNetConnectionClientCommunicator(config *dns.ClientConfig) (*NetConnectionClientCommunicator, error) {
+func MustResolveNetworkAddress(network, server, defaultPort string) net.Addr {
+ addr, err := ResolveNetworkAddress(network, server, defaultPort)
+ if err != nil {
+ panic(err)
+ }
+ return addr
+}
+
+func NewNetConnectionClientCommunicator(config *ClientConfig) (*NetConnectionClientCommunicator, error) {
if config == nil {
- var err error
- // TODO:: Make this cross platform
- config, err = dns.ClientConfigFromFile("/etc/resolv.conf")
- if err != nil {
- return nil, errors.WithStack(err)
- }
+ return nil, errors.New("Client configuration not provided")
}
if len(config.Servers) == 0 {
return nil, errors.New("You need at least one upstream server!")
}
- var conn *net.UDPConn
+ // Try addresses, in order
+ var conn net.Conn
var err error
- for _, v := range config.Servers {
- var addr *net.UDPAddr
- addr, err = GenerateAddress(v, config.Port)
- if addr == nil {
- err = errors.Errorf("GenerateAddress(%v, %v) returned ", v, config.Port)
+ for _, addr := range config.Servers {
+ // TODO: Add support for TCP DNS
+ switch v := addr.(type) {
+ case *net.UDPAddr:
+ log.Tracef("Dialing UDP %v", addr)
+ conn, err = net.DialUDP("udp", nil, v)
+ case *net.TCPAddr:
+ log.Tracef("Dialing TCP %v", addr)
+ conn, err = net.DialTCP("tcp", nil, v)
+ default:
+ err = errors.Errorf("Don't know how to handle address %v", v)
}
-
if err != nil {
err = errors.WithStack(err)
- continue
+ } else {
+ log.Infof("Connected to upstream server %v", addr)
+ break
}
+ }
- // TODO: Add support for TCP DNS
- log.Debugf("Dialing udp %v", addr)
- conn, err = net.DialUDP("udp", nil, addr)
- if err != nil {
- err = errors.WithStack(err)
- continue
- }
+ if conn == nil {
+ err = errors.Errorf("No connection can be established. None of the servers %v worked.", config.Servers)
}
if err != nil {
@@ -103,7 +166,8 @@ func NewNetConnectionClientCommunicator(config *dns.ClientConfig) (*NetConnectio
return &NetConnectionClientCommunicator{
Client: &dns.Client{},
Conn: &dns.Conn{
- Conn: conn,
+ Conn: conn,
+ UDPSize: 65535,
},
}, nil
}
@@ -125,7 +189,7 @@ func (sc *NetConnectionClientCommunicator) SendAndReceive(m *dns.Msg, timeout *t
sc.Client.Timeout = *timeout
}
r, rtt, err = sc.Client.ExchangeWithConn(m, sc.Conn)
- err = errors.Wrapf(err, "Could not send packet to server: %v", m)
+ err = errors.Wrapf(err, "Could not send packet %v %q to server", dns.Type(m.Question[0].Qtype), m.Question[0].Name)
return
}
diff --git a/internal/streams/dns/dns_client_connection.go b/internal/streams/dns/dns_client_connection.go
index c6f4e45..92fbb57 100644
--- a/internal/streams/dns/dns_client_connection.go
+++ b/internal/streams/dns/dns_client_connection.go
@@ -35,7 +35,10 @@ import (
"time"
)
-var ErrHandshakeNotCompleted = errors.New("no initialization data available - handshake was most likely not completed")
+var (
+ ErrHandshakeNotCompleted = errors.New("no initialization data available - handshake was most likely not completed")
+ ErrConnectionFailed = errors.Errorf("No suitable DNS query type found. Are you connected to a network?")
+)
// ClientDnsConnection will simulate connections over a DNS server request/response loop
type ClientDnsConnection struct {
@@ -80,7 +83,7 @@ func NewClientDnsConnection(topDomain string, communicator ClientCommunicator) (
// Close will close the underlying stream. If the Close has already been called, it will do nothing
func (dc *ClientDnsConnection) Close() error {
- if !dc.Closed() {
+ if !dc.Closed() && dc.Serializer.Upstream.QueryType != nil {
// Notify the server to do a clean shutdown, if handshake was complete
var err error
// Acknowledge last received chunk
@@ -337,10 +340,7 @@ func (dc *ClientDnsConnection) AutoDetectQueryType() error {
/* finished */
if highestWorking == 0 {
-
- /* also catches highestworking still 100 */
- err := errors.Errorf("No suitable DNS query type found. Are you connected to a network?")
- return err /* problem */
+ return ErrConnectionFailed /* problem */
}
/* "using qtype" message printed in Handshake function */
diff --git a/internal/streams/dns/dns_test.go b/internal/streams/dns/dns_test.go
index 5b5ab12..7ac4550 100644
--- a/internal/streams/dns/dns_test.go
+++ b/internal/streams/dns/dns_test.go
@@ -130,6 +130,24 @@ func TestMain(m *testing.M) {
os.Exit(code)
}
+func Test_ErrHandshake(t *testing.T) {
+ var client *ClientDnsConnection
+ var err error
+
+ clientComm, err := NewNetConnectionClientCommunicator(&ClientConfig{
+ Servers: AddressList{MustResolveNetworkAddress("udp", "127.0.0.1:41999", "")},
+ })
+ require.NoError(t, err)
+
+ client, err = NewClientDnsConnection(testDomain, clientComm)
+ require.NoError(t, err)
+
+ err = client.Handshake()
+ require.Equal(t, ErrConnectionFailed, err)
+
+ defer client.Close()
+}
+
func Test_Handshake(t *testing.T) {
comm := &testCommunicator{}
@@ -160,13 +178,8 @@ func Test_ConnectionViaNetwork(t *testing.T) {
})
require.NoError(t, err)
- clientComm, err := NewNetConnectionClientCommunicator(&dns.ClientConfig{
- Servers: []string{"127.0.0.1"},
- Search: []string{},
- Port: "42000",
- Ndots: 0,
- Timeout: 1000,
- Attempts: 2,
+ clientComm, err := NewNetConnectionClientCommunicator(&ClientConfig{
+ Servers: AddressList{MustResolveNetworkAddress("udp", "127.0.0.1:42000", "")},
})
require.NoError(t, err)
@@ -234,13 +247,8 @@ func Test_LargeUpload(t *testing.T) {
})
require.NoError(t, err)
- clientComm, err := NewNetConnectionClientCommunicator(&dns.ClientConfig{
- Servers: []string{"127.0.0.1"},
- Search: []string{},
- Port: "42001",
- Ndots: 0,
- Timeout: 1000,
- Attempts: 2,
+ clientComm, err := NewNetConnectionClientCommunicator(&ClientConfig{
+ Servers: AddressList{MustResolveNetworkAddress("udp", "127.0.0.1:42001", "")},
})
require.NoError(t, err)
@@ -322,13 +330,8 @@ func Test_LargeDownload(t *testing.T) {
})
require.NoError(t, err)
- clientComm, err := NewNetConnectionClientCommunicator(&dns.ClientConfig{
- Servers: []string{"127.0.0.1"},
- Search: []string{},
- Port: "42002",
- Ndots: 0,
- Timeout: 1000,
- Attempts: 2,
+ clientComm, err := NewNetConnectionClientCommunicator(&ClientConfig{
+ Servers: AddressList{MustResolveNetworkAddress("udp", "127.0.0.1:42002", "")},
})
require.NoError(t, err)
diff --git a/internal/streams/named_connection.go b/internal/streams/named_connection.go
index b179e2e..9db12cf 100644
--- a/internal/streams/named_connection.go
+++ b/internal/streams/named_connection.go
@@ -16,7 +16,7 @@ type NamedConnection struct {
name string
}
-// NewNamedStream will, unsuprisingly, create a new NamedStream with a given name
+// NewNamedStream will, unsurprisingly, create a new NamedStream with a given name
func NewNamedConnection(wrapped net.Conn, name string) *NamedConnection {
return &NamedConnection{
Connection: NewSafeConnection(wrapped),
diff --git a/internal/streams/named_reader.go b/internal/streams/named_reader.go
index 3ebdf86..f5c2d8c 100644
--- a/internal/streams/named_reader.go
+++ b/internal/streams/named_reader.go
@@ -15,6 +15,7 @@ type NamedReader struct {
name string
}
+// NewNamedReader will create a new NamedReader with the specified name.
func NewNamedReader(wrapped io.ReadCloser, name string) *NamedReader {
return &NamedReader{
ReadCloserClosed: NewSafeReader(wrapped),
@@ -22,6 +23,7 @@ func NewNamedReader(wrapped io.ReadCloser, name string) *NamedReader {
}
}
+// WriteTo just implements the method from the reader
func (ns *NamedReader) WriteTo(w io.Writer) (n int64, err error) {
if o, ok := ns.ReadCloserClosed.(io.WriterTo); ok {
return o.WriteTo(w)
@@ -30,6 +32,10 @@ func (ns *NamedReader) WriteTo(w io.Writer) (n int64, err error) {
}
}
+// String will return the "nice" name of the io.Reader -- tha name provided. If this reader wraps another io.Reader
+// which implements the fmt.Stringer interface, it's name will be added at the end after the `->` sign, e.g. you
+// will get `{this-name}->{wrapped-name}`. This allows you to elegantly see the hierarhy of the wrapped reader, if
+// all of them have been wrapped into a NamedReader.
func (ns *NamedReader) String() string {
result := ns.name
@@ -51,6 +57,7 @@ func (ns *NamedReader) String() string {
return result
}
+// Upwrap will return the underlying Reader.
func (ns *NamedReader) Unwrap() io.ReadCloser {
return ns.ReadCloserClosed
}
diff --git a/internal/streams/named_stream.go b/internal/streams/named_stream.go
index 98a6b7e..4ff4f4f 100644
--- a/internal/streams/named_stream.go
+++ b/internal/streams/named_stream.go
@@ -15,7 +15,7 @@ type NamedStream struct {
name string
}
-// NewNamedStream will, unsuprisingly, create a new NamedStream with a given name
+// NewNamedStream will, unsurprisingly, create a new NamedStream with a given name
func NewNamedStream(wrapped io.ReadWriteCloser, name string) *NamedStream {
return &NamedStream{
ReadWriteCloserClosed: NewSafeStream(wrapped),
diff --git a/internal/streams/named_writer.go b/internal/streams/named_writer.go
index 9d016f8..1bb4e5d 100644
--- a/internal/streams/named_writer.go
+++ b/internal/streams/named_writer.go
@@ -16,7 +16,7 @@ type NamedWriter struct {
closed bool
}
-// NewNamedStream will, unsuprisingly, create a new NamedStream with a given name
+// NewNamedStream will, unsurprisingly, create a new NamedStream with a given name
func NewNamedWriter(wrapped io.WriteCloser, name string) *NamedWriter {
return &NamedWriter{
WriteCloserClosed: NewSafeWriter(wrapped),
diff --git a/internal/streams/pipes.go b/internal/streams/pipes.go
index c43010e..9c21f31 100644
--- a/internal/streams/pipes.go
+++ b/internal/streams/pipes.go
@@ -79,7 +79,7 @@ func LogClose(closer io.Closer) error {
log.WithError(err).Errorf("Could not close: %v", err)
return err
} else {
- // log.Tracef("%v succesfully closed", closer)
+ // log.Tracef("%v successfully closed", closer)
return nil
}
}
diff --git a/internal/util/enc/base128_test.go b/internal/util/enc/base128_test.go
index 838e75e..cc35030 100644
--- a/internal/util/enc/base128_test.go
+++ b/internal/util/enc/base128_test.go
@@ -7,7 +7,7 @@ import (
func Test_Base128Transliterate(t *testing.T) {
str := make([]byte, 127)
- for k, _ := range str {
+ for k := range str {
str[k] = byte(k)
}
trans := escape128(str)
@@ -19,10 +19,12 @@ func Test_Base128Transliterate(t *testing.T) {
}
func Test_Base128Encoder(t *testing.T) {
- encoder := Base128Encoder{}
- encoded := encoder.Encode(encoderTest)
- require.NotContains(t, encoded, ".")
- decoded, err := encoder.Decode(encoded)
- require.NoError(t, err)
- require.Equal(t, encoderTest, decoded)
+ for _, encoderTest := range encoderTests {
+ encoder := Base128Encoder{}
+ encoded := encoder.Encode(encoderTest)
+ require.NotContains(t, encoded, ".")
+ decoded, err := encoder.Decode(encoded)
+ require.NoError(t, err)
+ require.Equal(t, encoderTest, decoded)
+ }
}
diff --git a/internal/util/enc/base192.go b/internal/util/enc/base192.go
new file mode 100644
index 0000000..6c28fba
--- /dev/null
+++ b/internal/util/enc/base192.go
@@ -0,0 +1,168 @@
+package enc
+
+import (
+ "bytes"
+ "fmt"
+)
+
+// Base192 could take the top 192 characters (leaving out the bottom 32, which are usually control characters).
+// Base192 encoding could achieve near-raw efficiency, as it encodes 7.5 bits / byte. Or, in other words: every
+// 15 bytes get encoded into 16 octets. This yields an appropriate 6.66% encoding loss.
+const MinAsciiCode = 255 - 192
+
+// Base128Encoder encodes 7.5 bits to 1 octet
+type Base192Encoder struct {
+}
+
+func (b *Base192Encoder) Name() string {
+ return "Base192"
+}
+
+func (b *Base192Encoder) String() string {
+ return fmt.Sprintf("%v(%v)", b.Name(), string(b.Code()))
+}
+
+func (b *Base192Encoder) Code() byte {
+ return 'Y'
+}
+
+func (b *Base192Encoder) Encode(src []byte) []byte {
+ if src == nil {
+ return nil
+ }
+
+ dst := &bytes.Buffer{}
+
+ whichBit := uint8(1)
+ bufNum := uint16(0)
+
+ l := len(src)
+
+ // Take 15 bits at a time and encode them into a 16 bit 192-encoded number
+ last := false
+ for i := 0; i < l; i++ {
+ var val uint16
+ if i == l-1 {
+ val = uint16(src[i]) << 8
+ last = true
+ } else {
+ val = uint16(src[i])<<8 | uint16(src[i+1])
+ last = false
+ i++
+ }
+
+ // Value is a 16 bit number. We must take 15 bits of it, but use the sliding window.
+
+ // We need to take "first" (whichByte) bits from the number.
+ data := val >> whichBit // Easiest way to do it is to shift bits right by the needed amount...
+ rem := val - (data << whichBit) // Get the remaining bits
+
+ elem := bufNum | data // Combine previous data and new data
+ dst.WriteByte(byte(elem / 192))
+ dst.WriteByte(byte(elem % 192))
+
+ bufNum = rem << (15 - whichBit) // Shift the remaining bits to start
+
+ whichBit++
+
+ if whichBit == 16 {
+ whichBit = 1
+ }
+ }
+
+ whichBit--
+ // whichByte will go from 1 to 15. It is basically our iterator loop.
+
+ if whichBit == 0 || (whichBit == 7 && last) {
+ // no dangling data to write
+ } else if whichBit == 7 || !last {
+ // Write the last "7.5" bits to the output
+ dst.WriteByte(byte(bufNum / 192))
+ }
+
+ if dst.Len() == 0 {
+ return []byte{}
+ }
+
+ return dst.Bytes()
+}
+
+func (b *Base192Encoder) Decode(src []byte) ([]byte, error) {
+ if src == nil {
+ return nil, nil
+ }
+
+ dst := &bytes.Buffer{}
+
+ l := len(src)
+
+ whichBit := uint(1)
+ bufNum := uint16(0)
+
+ // The cycle repeats every 15 bytes:
+ //
+ // 1 byte gets encoded to 2 bytes --> whichByte = 2
+ // 2 bytes gets encoded to 3 bytes --> whichByte = 3
+ // 3 bytes gets encoded to 4 bytes --> whichByte = 4
+ // 4 bytes gets encoded to 5 bytes --> whichByte = 5
+ // 5 bytes gets encoded to 6 bytes --> whichByte = 6
+ // 6 bytes gets encoded to 7 bytes --> whichByte = 7
+ // 7 bytes gets encoded to 8 bytes --> whichByte = 8
+ // 8 bytes gets encoded to 9 bytes --> whichByte = 9
+ // 9 bytes gets encoded to 10 bytes --> whichByte = 10
+ //10 bytes gets encoded to 11 bytes --> whichByte = 11
+ //11 bytes gets encoded to 12 bytes --> whichByte = 12
+ //12 bytes gets encoded to 13 bytes --> whichByte = 13
+ //13 bytes gets encoded to 14 bytes --> whichByte = 14
+ //14 bytes gets encoded to 15 bytes --> whichByte = 15
+ //15 bytes gets encoded to 16 bytes --> whichByte = 1
+
+ // Take 16 bits at a time but decode 15 bits at a time
+
+ for i := 0; i < l; i++ {
+ var val uint16
+ if i == l-1 {
+ val = uint16(src[i]) << 8
+ } else {
+ val = uint16(src[i])<<8 | uint16(src[i+1])
+ i++
+ }
+
+ decoded := ((val >> 8) * 192) | (val & 0xFF) // Decode the number into 15 bits
+
+ if whichBit != 1 {
+ top := decoded >> (16 - whichBit) // Get the remaining bits
+ bufNum = bufNum | top // ..and add them to our buffer
+ dst.WriteByte(byte(bufNum >> 8)) // Put the hight bits in the destination
+ dst.WriteByte(byte(bufNum & 0xFF)) // And follow up by low bits
+ }
+ bufNum = decoded << whichBit // push the bits over
+
+ whichBit++
+
+ if whichBit == 15 {
+ whichBit = 0
+ }
+ }
+
+ if dst.Len() == 0 {
+ return []byte{}, nil
+ }
+
+ return dst.Bytes(), nil
+}
+
+func (b *Base192Encoder) TestPatterns() [][]byte {
+ str := make([]byte, 192)
+ for k := range str {
+ str[k] = byte(k + MinAsciiCode)
+ }
+
+ return [][]byte{
+ str,
+ }
+}
+
+func (b *Base192Encoder) Ratio() float64 {
+ return 8.0 / 7.5
+}
diff --git a/internal/util/enc/base192_test.go b/internal/util/enc/base192_test.go
new file mode 100644
index 0000000..35f9ac5
--- /dev/null
+++ b/internal/util/enc/base192_test.go
@@ -0,0 +1,39 @@
+package enc
+
+import (
+ "testing"
+)
+
+func Test_Base192Encoder(t *testing.T) {
+ /*
+ // DISABLE TESTS. Base192 encoder does not work properly as of yet.
+
+
+ for _, encoderTest := range encoderTests {
+ encoder := Base192Encoder{}
+ encoded := encoder.Encode(encoderTest)
+ require.NotNil(t, encoded)
+
+ log.Infof("%v -> %v", encoderTest, encoded)
+
+ expectedLen := int(math.Ceil(float64(len(encoderTest)) * 16.0 / 15.0))
+ require.Len(t, encoded, expectedLen)
+
+ if string(encoderTest) == "\001\002\377\377" {
+ require.Equal(t, []byte{0, 129, 85, 63, 128}, encoded)
+ } else if string(encoderTest) == "\001\002\377" {
+ require.Equal(t, []byte{0, 129, 85, 0}, encoded)
+ }
+
+ require.GreaterOrEqual(t, len(encoded), len(encoderTest))
+ require.NotContains(t, encoded, "=")
+ require.NotContains(t, encoded, ".")
+ decoded, err := encoder.Decode(encoded)
+ log.Infof("%v -> %v -> %v", encoderTest, encoded, decoded)
+ require.NotNil(t, encoded)
+ require.NoError(t, err)
+ require.Equal(t, encoderTest, decoded)
+ }
+ */
+
+}
diff --git a/internal/util/enc/base32_test.go b/internal/util/enc/base32_test.go
index c5676ec..b8ebdbf 100644
--- a/internal/util/enc/base32_test.go
+++ b/internal/util/enc/base32_test.go
@@ -5,16 +5,38 @@ import (
"testing"
)
-var encoderTest = []byte("\000\000\000\000\377\377\377\377\125\125\125\125\252\252\252\252" +
- "\201\143\310\322\307\174\262\027\137\117\316\311\111\055\122\041" +
- "\141\251\161\040\045\263\006\163\346\330\104\060\171\120\127\277")
+var encoderTests = [][]byte{
+ []byte("\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017"),
+ []byte("\001\002\003\004\005\006\007\010\011\012\013\014\015\016"),
+ []byte("\001\002\377"),
+ []byte("\001\002"),
+ []byte("\001\002\377\377"),
+ []byte("\001\002\003\004\005"),
+ []byte("\001\002\003\004\005\006"),
+ []byte("\001\002\003\004\005\006\007"),
+ []byte("\001\002\003\004\005\006\007\010"),
+ []byte("\001\002\003\004\005\006\007\010\011"),
+ []byte("\001\002\003\004\005\006\007\010\011\012"),
+ []byte("\001\002\003\004\005\006\007\010\011\012\013"),
+ []byte("\001\002\003\004\005\006\007\010\011\012\013\014"),
+ []byte("\001\002\003\004\005\006\007\010\011\012\013\014\015"),
+ []byte("\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020"),
+ []byte("\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020"),
+ []byte("\000\000\000\000\377\377\377\377\125\125\125\125\252\252\252\252" +
+ "\201\143\310\322\307\174\262\027\137\117\316\311\111\055\122\041" +
+ "\141\251\161\040\045\263\006\163\346\330\104\060\171\120\127\277"),
+ []byte(""),
+ []byte("\001"),
+}
func Test_Base32Encoder(t *testing.T) {
- encoder := Base32Encoder{}
- encoded := encoder.Encode(encoderTest)
- require.NotContains(t, encoded, "=")
- require.NotContains(t, encoded, ".")
- decoded, err := encoder.Decode(encoded)
- require.NoError(t, err)
- require.Equal(t, encoderTest, decoded)
+ for _, encoderTest := range encoderTests {
+ encoder := Base32Encoder{}
+ encoded := encoder.Encode(encoderTest)
+ require.NotContains(t, encoded, "=")
+ require.NotContains(t, encoded, ".")
+ decoded, err := encoder.Decode(encoded)
+ require.NoError(t, err)
+ require.Equal(t, encoderTest, decoded)
+ }
}
diff --git a/internal/util/enc/base64_test.go b/internal/util/enc/base64_test.go
index 2baa0ee..d65448b 100644
--- a/internal/util/enc/base64_test.go
+++ b/internal/util/enc/base64_test.go
@@ -6,11 +6,13 @@ import (
)
func Test_Base64Encoder(t *testing.T) {
- encoder := Base64Encoder{}
- encoded := encoder.Encode(encoderTest)
- require.NotContains(t, encoded, "=")
- require.NotContains(t, encoded, ".")
- decoded, err := encoder.Decode(encoded)
- require.NoError(t, err)
- require.Equal(t, encoderTest, decoded)
+ for _, encoderTest := range encoderTests {
+ encoder := Base64Encoder{}
+ encoded := encoder.Encode(encoderTest)
+ require.NotContains(t, encoded, "=")
+ require.NotContains(t, encoded, ".")
+ decoded, err := encoder.Decode(encoded)
+ require.NoError(t, err)
+ require.Equal(t, encoderTest, decoded)
+ }
}
diff --git a/internal/util/enc/base64u_test.go b/internal/util/enc/base64u_test.go
index b5658a7..bb87b41 100644
--- a/internal/util/enc/base64u_test.go
+++ b/internal/util/enc/base64u_test.go
@@ -6,11 +6,13 @@ import (
)
func Test_Base64uEncoder(t *testing.T) {
- encoder := Base64uEncoder{}
- encoded := encoder.Encode(encoderTest)
- require.NotContains(t, encoded, "=")
- require.NotContains(t, encoded, ".")
- decoded, err := encoder.Decode(encoded)
- require.NoError(t, err)
- require.Equal(t, encoderTest, decoded)
+ for _, encoderTest := range encoderTests {
+ encoder := Base64uEncoder{}
+ encoded := encoder.Encode(encoderTest)
+ require.NotContains(t, encoded, "=")
+ require.NotContains(t, encoded, ".")
+ decoded, err := encoder.Decode(encoded)
+ require.NoError(t, err)
+ require.Equal(t, encoderTest, decoded)
+ }
}
diff --git a/internal/util/enc/base85.go b/internal/util/enc/base85.go
index 0f84b6e..158dbc4 100644
--- a/internal/util/enc/base85.go
+++ b/internal/util/enc/base85.go
@@ -6,9 +6,9 @@ import (
"github.com/pkg/errors"
)
-// -------------------------------------------------------
+// This is a modifid version of Base85 which uses an alternate alphabet -- it skips the '.' (dot) and '`' (backtick)
+// to make it more compatible with DNS domain-name system.
-// Base64Encoder encodes 3 bytes to 4 characters
type Base85Encoder struct {
}
@@ -65,7 +65,7 @@ func (b *Base85Encoder) Decode(data []byte) ([]byte, error) {
func (b *Base85Encoder) TestPatterns() [][]byte {
str := make([]byte, 85)
// 33 (!) through 117 (u)
- for k, _ := range str {
+ for k := range str {
b := byte(k + 33)
if b == '.' {
str[k] = 'v'
diff --git a/internal/util/enc/base85_test.go b/internal/util/enc/base85_test.go
index dccb7ab..ec2c3d4 100644
--- a/internal/util/enc/base85_test.go
+++ b/internal/util/enc/base85_test.go
@@ -6,10 +6,12 @@ import (
)
func Test_Base85Encoder(t *testing.T) {
- encoder := Base85Encoder{}
- encoded := encoder.Encode(encoderTest)
- require.NotContains(t, encoded, ".")
- decoded, err := encoder.Decode(encoded)
- require.NoError(t, err)
- require.Equal(t, encoderTest, decoded)
+ for _, encoderTest := range encoderTests {
+ encoder := Base85Encoder{}
+ encoded := encoder.Encode(encoderTest)
+ require.NotContains(t, encoded, ".")
+ decoded, err := encoder.Decode(encoded)
+ require.NoError(t, err)
+ require.Equal(t, encoderTest, decoded)
+ }
}
diff --git a/internal/util/enc/base91_test.go b/internal/util/enc/base91_test.go
index 8365b7c..40d481f 100644
--- a/internal/util/enc/base91_test.go
+++ b/internal/util/enc/base91_test.go
@@ -6,10 +6,12 @@ import (
)
func Test_Base91Encoder(t *testing.T) {
- encoder := Base91Encoder{}
- encoded := encoder.Encode(encoderTest)
- require.NotContains(t, encoded, ".")
- decoded, err := encoder.Decode(encoded)
- require.NoError(t, err)
- require.Equal(t, encoderTest, decoded)
+ for _, encoderTest := range encoderTests {
+ encoder := Base91Encoder{}
+ encoded := encoder.Encode(encoderTest)
+ require.NotContains(t, encoded, ".")
+ decoded, err := encoder.Decode(encoded)
+ require.NoError(t, err)
+ require.Equal(t, encoderTest, decoded)
+ }
}
diff --git a/internal/util/enc/base_192.go b/internal/util/enc/base_192.go
deleted file mode 100644
index 8166b6d..0000000
--- a/internal/util/enc/base_192.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package enc
-
-// TODO: Base 192 would be even more optimal than Base 128.
-// Base192 could take the top 192 characters (leaving out the bottom 32, which are usually control characters).
-// Base192 encoding could achieve near-raw efficiency, as it encodes 7.5 bits / byte. Or, in other words: every
-// 15 bytes get encoded into 16 octets. This yields an appropriate 6.66% encoding loss.
diff --git a/internal/util/enc/interface.go b/internal/util/enc/interface.go
index 5667a58..a3ac5aa 100644
--- a/internal/util/enc/interface.go
+++ b/internal/util/enc/interface.go
@@ -33,6 +33,7 @@ var (
Base85Encoding Encoder = &Base85Encoder{}
Base91Encoding Encoder = &Base91Encoder{}
Base128Encoding Encoder = &Base128Encoder{}
+ Base192Encoding Encoder = &Base192Encoder{}
RawEncoding Encoder = &RawEncoder{}
)
@@ -46,6 +47,7 @@ func FromCode(code byte) (Encoder, error) {
Base85Encoding,
Base91Encoding,
Base128Encoding,
+ Base192Encoding,
RawEncoding,
} {
if enc.Code() == code {
diff --git a/internal/util/mime/fieldsplitter.go b/internal/util/mime/fieldsplitter.go
index 23ee8d7..2fdad8d 100644
--- a/internal/util/mime/fieldsplitter.go
+++ b/internal/util/mime/fieldsplitter.go
@@ -4,7 +4,7 @@ import "regexp"
var commaSeparator = regexp.MustCompile("\\s*,\\s*")
-// SplitField will take a comma-separated list and return the values (without potential blanks inbetween)
+// SplitField will take a comma-separated list and return the values (without potential blanks in between)
func SplitField(s string) []string {
return commaSeparator.Split(s, -1)
}