diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Kevin Quick (c) 2016, 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/readknownhosts.hs b/readknownhosts.hs
new file mode 100644
--- /dev/null
+++ b/readknownhosts.hs
@@ -0,0 +1,5 @@
+module Main where
+
+import Network.SSH.KnownHosts
+
+main = mapM_ (putStrLn . show) =<< readKnownHosts
diff --git a/src/Network/SSH/KnownHosts.hs b/src/Network/SSH/KnownHosts.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/SSH/KnownHosts.hs
@@ -0,0 +1,180 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Module      : KnownHosts
+-- Description : Parses SSH known_hosts file into local data objects
+-- Copyright   : (c) Kevin Quick, 2016
+-- License     : BSD-3
+-- Maintainer  : quick@sparq.org
+-- Stability   : stable
+-- Portability : POSIX
+--
+-- Remote hosts specified in the .ssh/known_hosts file can take one of
+-- the following forms:
+--
+--   * 192.168.3.1
+--   * ::1
+--   * shortname
+--   * sys.domain.name
+--
+-- Any of the above can be enclosed in square brackets and followed by
+-- :PORTNUM
+--
+-- Multiples of any of the above (with or without port specifications)
+-- can occur with comma separators, representing equivalent addresses
+-- for the line.
+--
+-- The known_hosts key file can be hashed (ssh-keygen -H), in which
+-- case all of the above (including port specifications) are replaced
+-- by hash values of the form "|1|partone|part2=".  Note that comma
+-- separators are not used for multiple hash values: multiple lines
+-- are used instead.
+--
+-- This library parses all of the above forms, returning an array of
+-- 'SSHRemote' specifications.  In addition, this library provides a
+-- 'targetAddr' function to get the actual target address from an
+-- entry.
+
+
+module Network.SSH.KnownHosts
+    ( readKnownHosts
+    , readKnownHostsFile
+    , parseRemotes
+    , SSHRemoteAddr(..), SSHRemoteAddrs(..)
+    , KeyAlgorithm, SSHKey, DNSName, SSHRemote(..)
+    , IPv4, IPv6
+    , targetAddr
+    ) where
+
+import           Data.IP.Internal
+import           Data.List (intercalate)
+import           Data.Maybe
+import           Data.Monoid ((<>))
+import qualified Data.Text as T
+import qualified Data.Text.IO as TIO
+import           System.Environment
+import           Text.Read
+
+
+type DNSName = [T.Text]
+
+data SSHRemoteAddr = RemoteV4 IPv4
+                   | RemoteV4Port IPv4 Int
+                   | RemoteV6 IPv6
+                   | RemoteV6Port IPv6 Int
+                   | RemoteDNS [T.Text]
+                   | RemoteDNSPort [T.Text] Int
+                   | RemoteHashed String
+                 deriving (Show, Eq)
+
+data SSHRemoteAddrs = SSH1Addr SSHRemoteAddr
+                    | SSHAddrs [SSHRemoteAddr]
+                    deriving (Show, Eq)
+
+type KeyAlgorithm = String
+type SSHKey = String
+
+data SSHRemote = SSHRemote SSHRemoteAddrs KeyAlgorithm SSHKey
+               deriving (Show, Eq)
+
+-- | targetAddr returns the target address and port, using the best
+-- (most explicit) form of the address in the case where there are
+-- multiple addresses for the same remote system.
+targetAddr :: SSHRemote -> (T.Text, Int)
+targetAddr (SSHRemote (SSH1Addr a) _ _) =
+  let addr (RemoteV4 a)        = (T.pack $ show a, 22)
+      addr (RemoteV4Port a p)  = (T.pack $ show a, p)
+      addr (RemoteV6 a)        = (T.pack $ show a, 22)
+      addr (RemoteV6Port a p)  = (T.pack $ show a, p)
+      addr (RemoteDNS d)       = (dotPack $ reverse d, 22)
+      addr (RemoteDNSPort d p) = (dotPack $ reverse d, p)
+      addr (RemoteHashed h)    = ("[{hashed}]",22)  -- unuseable
+  in addr a
+targetAddr (SSHRemote (SSHAddrs m) a b) =
+  targetAddr $ SSHRemote (SSH1Addr $ foldr1 findBest m) a b
+  where findBest   best@(RemoteV4Port _ _)  _ = best
+        findBest _ best@(RemoteV4Port _ _)    = best
+        findBest   best@(RemoteV4 _)        _ = best
+        findBest _ best@(RemoteV4 _)          = best
+        findBest   best@(RemoteV6Port _ _)  _ = best
+        findBest _ best@(RemoteV6Port _ _)    = best
+        findBest   best@(RemoteV6 _)        _ = best
+        findBest _ best@(RemoteV6 _)          = best
+        findBest   best@(RemoteDNSPort _ _) _ = best
+        findBest _ best@(RemoteDNSPort _ _)   = best
+        findBest   best@(RemoteDNS _)       _ = best
+        findBest _ best@(RemoteDNS _)         = best
+        findBest best _                       = best
+
+dotPack = T.intercalate (T.pack ".")
+
+splitDNS :: T.Text -> [T.Text]
+splitDNS n = let split = T.splitOn "." n
+                 dottedQuad = and [ length split == 4
+                                  , T.null $ snd $ T.span isNumOrDot n]
+                 isNumOrDot = flip elem ("0123456789." :: String)
+             in if dottedQuad then [] else reverse split
+
+parseAddr :: T.Text -> SSHRemoteAddrs
+parseAddr addrSpec =
+  let addrs = T.splitOn "," addrSpec
+  in if null (tail addrs) then SSH1Addr (parse1Addr addrSpec)
+     else SSHAddrs (map parse1Addr addrs)
+
+parse1Addr :: T.Text -> SSHRemoteAddr
+parse1Addr addrSpec =
+  let hasPort = T.head addrSpec == '[' && T.head (snd parts) == ']'
+      hashedAddr = T.head addrSpec == '|'
+      (a',p) = if hasPort
+               then (T.tail $ fst parts,
+                     read (T.unpack $ T.tail $ T.tail $ snd parts))
+               else (addrSpec, 22)
+      parts = T.break (== ']') addrSpec
+      dns = splitDNS a'
+      addr = case readMaybe (T.unpack a') of
+               Just a -> if hasPort
+                         then RemoteV4Port a p
+                         else RemoteV4 a
+               Nothing -> case readMaybe (T.unpack a') of
+                           Just a -> if hasPort
+                                     then RemoteV6Port a p
+                                     else RemoteV6 a
+                           Nothing -> if hashedAddr
+                                      then RemoteHashed $ T.unpack addrSpec
+                                      else if hasPort
+                                           then RemoteDNSPort dns p
+                                           else RemoteDNS dns
+  in addr
+
+
+parseRemote :: T.Text -> SSHRemote
+parseRemote line =
+  let w = T.words line
+      expfmt = "address algorithm key"
+      addr = parseAddr $ head w
+      alg = T.unpack $ head $ tail w
+      hash = T.unpack $ head $ tail $ tail w
+  in if length w == 3
+     then SSHRemote addr alg hash
+     else error $ "Expected \"" <> expfmt <> "\" but got: " <> show line
+
+-- | Passed the specification of remote ssh known hosts and returns
+-- the parsed SSHRemote structures representing those hosts.  The
+-- input text block is expected to be the raw contents of a
+-- known_hosts file (e.g. comparable to a @Data.Text.IO.readFile
+-- "$HOME/.ssh/known_hosts@) and the output is the parsed data
+-- structures.
+parseRemotes :: T.Text -> [SSHRemote]
+parseRemotes = map parseRemote . T.lines
+
+-- | Reads the current user's known_hosts file and returns the
+-- SSHRemote structures representing the contents of that file (by
+-- calling 'parseRemotes').
+readKnownHosts :: IO [SSHRemote]
+readKnownHosts =
+  do home <- getEnv "HOME"
+     readKnownHostsFile $ home <> "/.ssh/known_hosts"
+
+-- | Reads the specified file and returns the SSHRemote structures
+-- representing the contents of that file (by calling 'parseRemotes').
+readKnownHostsFile :: String -> IO [SSHRemote]
+readKnownHostsFile f = parseRemotes <$> TIO.readFile f
diff --git a/ssh-known-hosts.cabal b/ssh-known-hosts.cabal
new file mode 100644
--- /dev/null
+++ b/ssh-known-hosts.cabal
@@ -0,0 +1,52 @@
+name:                ssh-known-hosts
+version:             0.1.0.0
+synopsis:            Read and interpret the SSH known-hosts file
+
+description:
+
+   Utility to read the contents of the SSH known_hosts file and create
+   local in-memory data structures for each of the remote entities
+   listed.
+
+homepage:            http://hub.darcs.net/kquick/ssh-known-hosts
+license:             BSD3
+license-file:        LICENSE
+author:              Kevin Quick
+maintainer:          quick@sparq.org
+copyright:           2016 Kevin Quick
+category:            Network
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Network.SSH.KnownHosts
+  build-depends:       base >= 4.7 && < 5
+                     , iproute >= 1.5
+                     , text
+  default-language:    Haskell2010
+
+Flag sample_progs
+     Description: Build sample programs
+     Default:     False
+
+executable readknownhosts
+  main-is: readknownhosts.hs
+  build-depends:   base, ssh-known-hosts
+  if !flag(sample_progs)
+    buildable: False
+  default-language:    Haskell2010
+
+test-suite test_parsing
+  type: exitcode-stdio-1.0
+  main-is: test_ssh_known_hosts.hs
+  hs-source-dirs: test
+  default-language:    Haskell2010
+  build-depends: base, HUnit, QuickCheck, text, iproute
+               , test-framework, test-framework-hunit
+               , test-framework-quickcheck2
+               , ssh-known-hosts
+
+source-repository head
+  type:     darcs
+  location: http://hub.darcs.net/kquick/ssh-known-hosts
diff --git a/test/test_ssh_known_hosts.hs b/test/test_ssh_known_hosts.hs
new file mode 100644
--- /dev/null
+++ b/test/test_ssh_known_hosts.hs
@@ -0,0 +1,558 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import           Data.IP.Internal
+import           Data.Monoid ((<>))
+import qualified Data.Text as T
+import           Network.SSH.KnownHosts
+import           System.Environment
+import           Test.Framework
+import           Test.Framework.Providers.HUnit
+import           Test.Framework.Providers.QuickCheck2
+import           Test.HUnit
+import           Test.QuickCheck
+
+main = defaultMain
+       [ testGroup "Parsing entries"
+         [ testParse1
+         , testParse2
+         , testParse3
+         , testParse4
+         , testParse5
+         , testParse6
+         , testParse7
+         , testParse8
+         , testParse9
+         , testParse12
+         , testParse13
+         , testParse14
+         , testParse15
+         , testParse16
+         , testParse17
+         , testParse18
+         , testParse19
+         , testParse20
+         , testParse21
+         , testParse22
+         ]
+       , testGroup "Read and parse existing $HOME/.ssh/known_hosts"
+         [ testReadFile
+         ]
+       , testGroup "Target address recovery"
+         [ testTgtAddr1
+         , testTgtAddr2
+         , testTgtAddr3
+         , testTgtAddr4
+         , testTgtAddr5
+         , testTgtAddr6
+         , testTgtAddr7
+         , testTgtAddr8
+         , testTgtAddr9
+         , testTgtAddr12
+         , testTgtAddr13
+         , testTgtAddr14
+         , testTgtAddr15
+         , testTgtAddr16
+         , testTgtAddr17
+         , testTgtAddr18
+         , testTgtAddr19
+         , testTgtAddr20
+         , testTgtAddr22
+         ]
+       , testGroup "Best address from group"
+         [ testBestTgtAddr1
+         , testBestTgtAddr2
+         , testBestTgtAddr3
+         , testBestTgtAddr4
+         , testBestTgtAddr5
+         , testBestTgtAddr6
+         , testBestTgtAddr7
+         , testBestTgtAddr8
+         , testBestTgtAddr9
+         , testBestTgtAddr10
+         , testBestTgtAddr11
+         , testBestTgtAddr12
+         , testBestTgtAddr13
+         , testBestTgtAddr14
+         , testBestTgtAddr15
+         , testBestTgtAddr16
+         , testBestTgtAddr17
+         , testBestTgtAddr18
+         , testBestTgtAddr19
+         ]
+       ]
+
+testReadFile = testCase "read file" $
+               do h <- readKnownHosts
+                  home <- getEnv "HOME"
+                  l <- lines <$> readFile (home <> "/.ssh/known_hosts")
+                  length h @?= length l
+
+testParse1 = testCase "empty parse"
+             $ parseRemotes (T.pack "") @?= []
+
+testTgtAddr1 = testCase "target IPv4 address"
+               $ (targetAddr $ SSHRemote (SSH1Addr $ RemoteV4 "1.2.3.4") "alg1" "key1")
+               @?= ("1.2.3.4", 22)
+
+addr2 = T.pack "1.2.3.4 alg hash"
+testParse2 = testCase "single IPv4 parse"
+             $ parseRemotes addr2
+             @?= [SSHRemote (SSH1Addr $ RemoteV4 "1.2.3.4") "alg" "hash"]
+testTgtAddr2 = testCase "target IPv4 address"
+               $ (targetAddr $ head $ parseRemotes addr2)
+               @?= ("1.2.3.4", 22)
+
+addr3 = T.pack "1.2.3.4 alg hash\n11.22.33.44 alg2 hash2\n"
+testParse3 = testCase "multiple IPv4 parse"
+             $ parseRemotes addr3
+             @?= [ SSHRemote (SSH1Addr $ RemoteV4 "1.2.3.4") "alg" "hash"
+                 , SSHRemote (SSH1Addr $ RemoteV4 "11.22.33.44") "alg2" "hash2"
+                 ]
+testTgtAddr3 = testCase "target IPv4 address"
+               $ (targetAddr $ head $ parseRemotes addr3)
+               @?= ("1.2.3.4", 22)
+
+addr4 = T.pack "::1 alg hash"
+testParse4 = testCase "single IPv6 address"
+             $ parseRemotes addr4
+             @?= [SSHRemote (SSH1Addr $ RemoteV6 "::1") "alg" "hash"]
+testTgtAddr4 = testCase "target IPv6 address"
+               $ (targetAddr $ head $ parseRemotes addr4)
+               @?= ("::1", 22)
+
+addr5 = T.pack "::1 alg hash\n11::2bed alg2 hash2\n"
+testParse5 = testCase "multiple IPv6 parse"
+             $ parseRemotes addr5
+             @?= [ SSHRemote (SSH1Addr $ RemoteV6 "::1") "alg" "hash"
+                 , SSHRemote (SSH1Addr $ RemoteV6 "11::2bed") "alg2" "hash2"
+                 ]
+testTgtAddr5 = testCase "multiple IPv6 address"
+               $ (map targetAddr $ parseRemotes addr5)
+               @?= [ ("::1", 22)
+                   , ("11::2bed", 22)
+                   ]
+
+
+addr6 = (T.pack "myhost alg hash")
+testParse6 = testCase "single name address parse"
+             $ parseRemotes addr6
+             @?= [SSHRemote (SSH1Addr $ RemoteDNS ["myhost"]) "alg" "hash"]
+testTgtAddr6 = testCase "single name address"
+               $ (map targetAddr $ parseRemotes addr6)
+               @?= [ ("myhost", 22) ]
+
+addr7 = T.pack "localhost alg hash\nmyhost alg2 hash2\n"
+testParse7 = testCase "multiple name parse"
+             $ parseRemotes addr7
+             @?= [ SSHRemote (SSH1Addr $ RemoteDNS ["localhost"]) "alg" "hash"
+                 , SSHRemote (SSH1Addr $ RemoteDNS ["myhost"]) "alg2" "hash2"
+                 ]
+testTgtAddr7 = testCase "multiple name address"
+               $ (map targetAddr $ parseRemotes addr7)
+               @?= [ ("localhost", 22)
+                   , ("myhost", 22)
+                   ]
+
+addr8 = T.pack "my.host.org alg hash"
+testParse8 = testCase "single FQDN address parse"
+             $ parseRemotes addr8
+             @?= [SSHRemote (SSH1Addr $ RemoteDNS ["org", "host", "my"]) "alg" "hash"]
+testTgtAddr8 = testCase "single FQDN address"
+               $ (map targetAddr $ parseRemotes addr8)
+               @?= [ ("my.host.org", 22)]
+
+addr9 = T.pack "localhost.com alg hash\nmy.host.org alg2 hash2\na.b.c.d.f.g alg3 hash3\n"
+testParse9 = testCase "multiple FQDN parse"
+             $ parseRemotes addr9
+             @?= [ SSHRemote (SSH1Addr $ RemoteDNS ["com", "localhost"]) "alg" "hash"
+                 , SSHRemote (SSH1Addr $ RemoteDNS ["org", "host", "my"]) "alg2" "hash2"
+                 , SSHRemote (SSH1Addr $ RemoteDNS ["g", "f", "d", "c", "b", "a"]) "alg3" "hash3"
+                 ]
+testTgtAddr9 = testCase "multiple FQDN address"
+               $ (map targetAddr $ parseRemotes addr9)
+               @?= [ ("localhost.com", 22)
+                   , ("my.host.org", 22)
+                   , ("a.b.c.d.f.g", 22)
+                   ]
+
+addr12 = T.pack "[1.2.3.4]:56 alg hash"
+testParse12 = testCase "single IPv4 port parse"
+              $ parseRemotes addr12
+              @?= [SSHRemote (SSH1Addr $ RemoteV4Port "1.2.3.4" 56) "alg" "hash"]
+testTgtAddr12 = testCase "single IPv4 port address"
+                $ (map targetAddr $ parseRemotes addr12)
+                @?= [ ("1.2.3.4", 56) ]
+
+addr13 = T.pack "[1.2.3.4]:5 alg hash\n[11.22.33.44]:9876 alg2 hash2\n"
+testParse13 = testCase "multiple IPv4 port parse"
+              $ parseRemotes addr13
+              @?= [ SSHRemote (SSH1Addr $ RemoteV4Port "1.2.3.4" 5) "alg" "hash"
+                  , SSHRemote (SSH1Addr $ RemoteV4Port "11.22.33.44" 9876) "alg2" "hash2"
+                  ]
+testTgtAddr13 = testCase "multiple IPv4 port address"
+                $ (map targetAddr $ parseRemotes addr13)
+                @?= [ ("1.2.3.4", 5)
+                    , ("11.22.33.44", 9876)
+                    ]
+
+addr14 = T.pack "[::1]:1234 alg hash"
+testParse14 = testCase "single IPv6 port address"
+              $ parseRemotes addr14
+              @?= [SSHRemote (SSH1Addr $ RemoteV6Port "::1" 1234) "alg" "hash"]
+testTgtAddr14 = testCase "single IPv6 port address"
+                $ (map targetAddr $ parseRemotes addr14)
+                @?= [ ("::1", 1234) ]
+
+addr15 = T.pack "[::1]:100 alg hash\n[11::2bed]:8 alg2 hash2\n"
+testParse15 = testCase "multiple IPv4 port address parse"
+              $ parseRemotes addr15
+              @?= [ SSHRemote (SSH1Addr $ RemoteV6Port "::1" 100) "alg" "hash"
+                  , SSHRemote (SSH1Addr $ RemoteV6Port "11::2bed" 8) "alg2" "hash2"
+                  ]
+testTgtAddr15 = testCase "multiple IPv6 port address"
+                $ (map targetAddr $ parseRemotes addr15)
+                @?= [ ("::1", 100)
+                    , ("11::2bed", 8)
+                    ]
+
+addr16 = T.pack "[myhost]:234 alg hash"
+testParse16 = testCase "single name port address parse"
+              $ parseRemotes addr16
+              @?= [SSHRemote (SSH1Addr $ RemoteDNSPort ["myhost"] 234) "alg" "hash"]
+testTgtAddr16 = testCase "single name port address"
+                $ (map targetAddr $ parseRemotes addr16)
+                @?= [ ("myhost", 234) ]
+
+
+addr17 = T.pack "[localhost]:80 alg hash\n[myhost]:8080 alg2 hash2\n"
+testParse17 = testCase "multiple name port address parse"
+              $ parseRemotes addr17
+              @?= [ SSHRemote (SSH1Addr $ RemoteDNSPort ["localhost"] 80) "alg" "hash"
+                  , SSHRemote (SSH1Addr $ RemoteDNSPort ["myhost"] 8080) "alg2" "hash2"
+                  ]
+testTgtAddr17 = testCase "multiple name port address"
+                $ (map targetAddr $ parseRemotes addr17)
+                @?= [ ("localhost", 80)
+                    , ("myhost", 8080)
+                    ]
+
+addr18 = T.pack "[my.host.org]:975 alg hash"
+testParse18 = testCase "single FQDN port address parse"
+              $ parseRemotes addr18
+              @?= [SSHRemote (SSH1Addr $ RemoteDNSPort ["org", "host", "my"] 975) "alg" "hash"]
+testTgtAddr18 = testCase "single FQDN port address"
+                $ (map targetAddr $ parseRemotes addr18)
+                @?= [ ("my.host.org", 975) ]
+
+addr19 = T.pack "[localhost.com]:79 alg hash\n[my.host.org]:135 alg2 hash2\n[a.b.c.d.f.g]:6 alg3 hash3\n"
+testParse19 = testCase "multiple FQDN port address parse"
+              $ parseRemotes addr19
+              @?= [ SSHRemote (SSH1Addr $ RemoteDNSPort ["com", "localhost"] 79) "alg" "hash"
+                  , SSHRemote (SSH1Addr $ RemoteDNSPort ["org", "host", "my"] 135) "alg2" "hash2"
+                  , SSHRemote (SSH1Addr $ RemoteDNSPort ["g", "f", "d", "c", "b", "a"] 6) "alg3" "hash3"
+                  ]
+testTgtAddr19 = testCase "multiple FQDN port address"
+                $ (map targetAddr $ parseRemotes addr19)
+                @?= [ ("localhost.com", 79)
+                    , ("my.host.org", 135)
+                    , ("a.b.c.d.f.g", 6)
+                    ]
+
+testParse20 = testCase "single hashed address parse"
+              $ let hash = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="
+                in parseRemotes (T.pack $ hash <> " alg hash")
+              @?= [SSHRemote (SSH1Addr $ RemoteHashed hash) "alg" "hash"]
+testTgtAddr20 = testCase "single hashed address"
+                $ let hash = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="
+                      addr20 = T.pack $ hash <> " alg hash"
+                  in (map targetAddr $ parseRemotes addr20)
+                @?= [ ("[{hashed}]", 22) ]
+
+
+testParse21 = testCase "multiple hashed address parse"
+              $ let hash1 = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="
+                    hash2 = "|1|KRKNXaWE155igs4mkpRu5QS7/qM=|ELHQZe922+xgX7uWKx+7947IN6o="
+                    hash3 = "|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAA|BBBBBBBBBBBBBBBBBBBBBBBBBBB="
+                in parseRemotes (T.pack $ hash1 <> " alg hash\n"
+                                 <> hash2 <> " alg2 hash2\n"
+                                 <> hash3 <> " alg3 hash3")
+                   @?= [ SSHRemote (SSH1Addr $ RemoteHashed hash1) "alg" "hash"
+                       , SSHRemote (SSH1Addr $ RemoteHashed hash2) "alg2" "hash2"
+                       , SSHRemote (SSH1Addr $ RemoteHashed hash3) "alg3" "hash3"
+                       ]
+
+testParse22 = testCase "multiple mixed parse"
+              $ let hash1 = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="
+                    hash2 = "|1|KRKNXaWE155igs4mkpRu5QS7/qM=|ELHQZe922+xgX7uWKx+7947IN6o="
+                    hash3 = "|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAA|BBBBBBBBBBBBBBBBBBBBBBBBBBB="
+                    ipv61 = "::1"
+                    ipv62 = "bad2::74e:b0de"
+                    ipv41 = "127.0.0.1"
+                    ipv42 = "9.22.8.33"
+                    dns1 = "we.are.the.champions"
+                    dns2 = "no.time"
+                in parseRemotes (T.pack $ hash1 <> " alg hash\n"
+                                 <> ipv41 <> " alg4 hash4\n"
+                                 <> "localhost alg0 hash0\n"
+                                 <> hash2 <> " alg2 hash2\n"
+                                 <> ipv61 <> " alg5 hash5\n"
+                                 <> "[" <> ipv42 <> "]:18 alg6 hash6\n"
+                                 <> "[" <> dns1 <> "]:444 alg7 hash7\n"
+                                 <> hash3 <> " alg3 hash3\n"
+                                 <> "[" <> ipv62 <> "]:10101 alg8 hash8\n"
+                                 <> dns2 <> " alg9 hash9\n"
+                                )
+                   @?= [ SSHRemote (SSH1Addr $ RemoteHashed hash1) "alg" "hash"
+                       , SSHRemote (SSH1Addr $ RemoteV4 $ read ipv41) "alg4" "hash4"
+                       , SSHRemote (SSH1Addr $ RemoteDNS ["localhost"]) "alg0" "hash0"
+                       , SSHRemote (SSH1Addr $ RemoteHashed hash2) "alg2" "hash2"
+                       , SSHRemote (SSH1Addr $ RemoteV6 $ read ipv61) "alg5" "hash5"
+                       , SSHRemote (SSH1Addr $ RemoteV4Port (read ipv42) 18) "alg6" "hash6"
+                       , SSHRemote (SSH1Addr $ RemoteDNSPort (map T.pack [ "champions"
+                                                              , "the"
+                                                              , "are"
+                                                              , "we"
+                                                              ] ) 444) "alg7" "hash7"
+                       , SSHRemote (SSH1Addr $ RemoteHashed hash3) "alg3" "hash3"
+                       , SSHRemote (SSH1Addr $ RemoteV6Port (read ipv62) 10101) "alg8" "hash8"
+                       , SSHRemote (SSH1Addr $ RemoteDNS (map T.pack ["time", "no"])) "alg9" "hash9"
+                       ]
+testTgtAddr22 = testCase "multiple mixed address"
+              $ let hash1 = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="
+                    hash2 = "|1|KRKNXaWE155igs4mkpRu5QS7/qM=|ELHQZe922+xgX7uWKx+7947IN6o="
+                    hash3 = "|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAA|BBBBBBBBBBBBBBBBBBBBBBBBBBB="
+                    ipv61 = "::1"
+                    ipv62 = "bad2::74e:b0de"
+                    ipv41 = "127.0.0.1"
+                    ipv42 = "9.22.8.33"
+                    dns1 = "we.are.the.champions"
+                    dns2 = "no.time"
+                in (map targetAddr $
+                    parseRemotes (T.pack $ hash1 <> " alg hash\n"
+                                  <> ipv41 <> " alg4 hash4\n"
+                                  <> "localhost alg0 hash0\n"
+                                  <> hash2 <> " alg2 hash2\n"
+                                  <> ipv61 <> " alg5 hash5\n"
+                                  <> "[" <> ipv42 <> "]:18 alg6 hash6\n"
+                                  <> "[" <> dns1 <> "]:444 alg7 hash7\n"
+                                  <> hash3 <> " alg3 hash3\n"
+                                  <> "[" <> ipv62 <> "]:10101 alg8 hash8\n"
+                                  <> dns2 <> " alg9 hash9\n"
+                                 ))
+                   @?= [ ("[{hashed}]", 22)
+                       , (T.pack ipv41, 22)
+                       , ("localhost", 22)
+                       , ("[{hashed}]", 22)
+                       , (T.pack ipv61, 22)
+                       , (T.pack ipv42, 18)
+                       , (T.pack dns1, 444)
+                       , ("[{hashed}]", 22)
+                       , (T.pack ipv62, 10101)
+                       , (T.pack dns2, 22)
+                       ]
+
+
+testBestTgtAddr1 = testCase "ipv4 addr port first"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV4Port "9.10.11.12" 13)
+                             , (RemoteV4 "1.2.3.4")
+                             , (RemoteV4 "11.22.33.44")
+                             , (RemoteV6 "::1")
+                             , (RemoteV6Port "::2" 3)
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("9.10.11.12", 13)
+
+testBestTgtAddr2 = testCase "ipv4 addr port middle"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6 "::1")
+                             , (RemoteV6Port "::2" 3)
+                             , (RemoteV4 "1.2.3.4")
+                             , (RemoteV4Port "9.10.11.12" 13)
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteV4 "11.22.33.44")
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("9.10.11.12", 13)
+
+testBestTgtAddr3 = testCase "ipv4 port addr end"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6 "::1")
+                             , (RemoteV6Port "::2" 3)
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             , (RemoteV4 "1.2.3.4")
+                             , (RemoteV4Port "9.10.11.12" 13)
+                             ]) "alg" "key")
+              @?= ("9.10.11.12", 13)
+
+testBestTgtAddr4 = testCase "ipv4 addr first"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV4 "1.2.3.4")
+                             , (RemoteV4 "11.22.33.44")
+                             , (RemoteV6 "::1")
+                             , (RemoteV6Port "::2" 3)
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("1.2.3.4", 22)
+
+testBestTgtAddr5 = testCase "ipv4 addr middle"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6 "::1")
+                             , (RemoteV6Port "::2" 3)
+                             , (RemoteV4 "1.2.3.4")
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteV4 "11.22.33.44")
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("1.2.3.4", 22)
+
+testBestTgtAddr6 = testCase "ipv4 addr end"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6 "::1")
+                             , (RemoteV6Port "::2" 3)
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             , (RemoteV4 "1.2.3.4")
+                             ]) "alg" "key")
+              @?= ("1.2.3.4", 22)
+
+testBestTgtAddr7 = testCase "ipv6 addr port first"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6Port "::2" 3)
+                             , (RemoteV6 "::1")
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("::2", 3)
+
+testBestTgtAddr8 = testCase "ipv6 addr port middle"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6 "::1")
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteV6Port "::2" 3)
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("::2", 3)
+
+testBestTgtAddr9 = testCase "ipv6 addr port end"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6 "::1")
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             , (RemoteV6Port "::2" 3)
+                             ]) "alg" "key")
+              @?= ("::2", 3)
+
+testBestTgtAddr10 = testCase "ipv6 addr first"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteV6 "::1")
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("::1", 22)
+
+testBestTgtAddr11 = testCase "ipv6 addr middle"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteDNS ["foo", "bar"])
+                             , (RemoteV6 "::1")
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("::1", 22)
+
+testBestTgtAddr12 = testCase "ipv6 addr end"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             , (RemoteV6 "::1")
+                             ]) "alg" "key")
+              @?= ("::1", 22)
+
+testBestTgtAddr13 = testCase "dns addr port first"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("moo.cow", 89)
+
+testBestTgtAddr14 = testCase "dns addr port middle"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteDNS ["foo", "bar"])
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("moo.cow", 89)
+
+testBestTgtAddr15 = testCase "dns addr port end"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteDNS ["foo", "bar"])
+                             , (RemoteHashed "|1|hash")
+                             , (RemoteDNSPort ["cow", "moo"] 89)
+                             ]) "alg" "key")
+              @?= ("moo.cow", 89)
+
+testBestTgtAddr16 = testCase "dns addr first"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteDNS ["foo", "bar"])
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("bar.foo", 22)
+
+testBestTgtAddr17 = testCase "dns addr middle"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteHashed "|1|hash")
+                             , (RemoteDNS ["foo", "bar"])
+                             , (RemoteHashed "|1|hash")
+                             ]) "alg" "key")
+              @?= ("bar.foo", 22)
+
+testBestTgtAddr18 = testCase "dns addr end"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteHashed "|1|hash")
+                             , (RemoteDNS ["foo", "bar"])
+                             ]) "alg" "key")
+              @?= ("bar.foo", 22)
+
+testBestTgtAddr19 = testCase "settle for hashed"
+              $ (targetAddr $
+                  SSHRemote (SSHAddrs
+                             [ (RemoteHashed "|1|hash1")
+                             , (RemoteHashed "|1|hash2")
+                             , (RemoteHashed "|1|hash3")
+                             ]) "alg" "key")
+              @?= ("[{hashed}]", 22)
