1/* Copyright 2016-2017 Vector Creations Ltd 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16package gomatrixserverlib 17 18import ( 19 "fmt" 20 "net" 21 "strconv" 22) 23 24// ResolutionResult is a result of looking up a Matrix homeserver according to 25// the federation specification. 26type ResolutionResult struct { 27 Destination string // The hostname and port to send federation requests to. 28 Host ServerName // The value of the Host headers. 29 TLSServerName string // The TLS server name to request a certificate for. 30} 31 32// ResolveServer implements the server name resolution algorithm described at 33// https://matrix.org/docs/spec/server_server/r0.1.1.html#resolving-server-names 34// Returns a slice of ResolutionResult that can be used to send a federation 35// request to the server using a given server name. 36// Returns an error if the server name isn't valid. 37func ResolveServer(serverName ServerName) (results []ResolutionResult, err error) { 38 return resolveServer(serverName, true) 39} 40 41// resolveServer does the same thing as ResolveServer, except it also requires 42// the checkWellKnown parameter, which indicates whether a .well-known file 43// should be looked up. 44func resolveServer(serverName ServerName, checkWellKnown bool) (results []ResolutionResult, err error) { 45 host, port, valid := ParseAndValidateServerName(serverName) 46 if !valid { 47 err = fmt.Errorf("Invalid server name") 48 return 49 } 50 51 // 1. If the hostname is an IP literal 52 // Check if we're dealing with an IPv6 literal with square brackets. If so, 53 // remove the brackets. 54 if host[0] == '[' && host[len(host)-1] == ']' { 55 host = host[1 : len(host)-1] 56 } 57 if net.ParseIP(host) != nil { 58 var destination string 59 60 if port == -1 { 61 destination = net.JoinHostPort(host, strconv.Itoa(8448)) 62 } else { 63 destination = string(serverName) 64 } 65 66 results = []ResolutionResult{ 67 ResolutionResult{ 68 Destination: destination, 69 Host: serverName, 70 TLSServerName: host, 71 }, 72 } 73 74 return 75 } 76 77 // 2. If the hostname is not an IP literal, and the server name includes an 78 // explicit port 79 if port != -1 { 80 results = []ResolutionResult{ 81 ResolutionResult{ 82 Destination: string(serverName), 83 Host: serverName, 84 TLSServerName: host, 85 }, 86 } 87 88 return 89 } 90 91 if checkWellKnown { 92 // 3. If the hostname is not an IP literal 93 var result *WellKnownResult 94 result, err = LookupWellKnown(serverName) 95 if err == nil { 96 // We don't want to check .well-known on the result 97 return resolveServer(result.NewAddress, false) 98 } 99 } 100 101 return handleNoWellKnown(serverName), nil 102} 103 104// handleNoWellKnown implements steps 4 and 5 of the resolution algorithm (as 105// well as 3.3 and 3.4) 106func handleNoWellKnown(serverName ServerName) (results []ResolutionResult) { 107 // 4. If the /.well-known request resulted in an error response 108 _, records, err := net.LookupSRV("matrix", "tcp", string(serverName)) 109 if err == nil && len(records) > 0 { 110 for _, rec := range records { 111 // If the domain is a FQDN, remove the trailing dot at the end. This 112 // isn't critical to send the request, as Go's HTTP client and most 113 // servers understand FQDNs quite well, but it makes automated 114 // testing easier. 115 target := rec.Target 116 if target[len(target)-1] == '.' { 117 target = target[:len(target)-1] 118 } 119 120 results = append(results, ResolutionResult{ 121 Destination: fmt.Sprintf("%s:%d", target, rec.Port), 122 Host: serverName, 123 TLSServerName: string(serverName), 124 }) 125 } 126 127 return 128 } 129 130 // 5. If the /.well-known request returned an error response, and the SRV 131 // record was not found 132 results = []ResolutionResult{ 133 ResolutionResult{ 134 Destination: fmt.Sprintf("%s:%d", serverName, 8448), 135 Host: serverName, 136 TLSServerName: string(serverName), 137 }, 138 } 139 140 return 141} 142