Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 12 additions & 36 deletions instance/conn_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ var (
// The domain name pattern in accordance with RFC 1035, RFC 1123 and RFC 2181.
domainNameRegex = regexp.MustCompile(`^(?:[_a-z0-9](?:[_a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?)?$`)

instanceDNSSuffixRegex = regexp.MustCompile(`\.sql(-[a-zA-Z0-9_]+)?\.goog\.?$`)
globalInstanceDNSRegex = regexp.MustCompile(`\.global\.sql(-[a-zA-Z0-9_]+)?\.goog\.?$`)
instanceDNSNamePattern = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.(sql|sql-psa|sql-psc)\.goog\.?$`)
)

// ConnName represents the "instance connection name", in the format
Expand Down Expand Up @@ -129,53 +128,30 @@ type ConnectionNameResolver interface {
// Suffix is one of: sql.goog, sql-psa.goog, sql-psc.goog (with optional trailing dot).
// This returns ok == false when the region is "global"
func ParseInstanceDNSName(dnsName string) (instanceLabel, projectLabel, region, suffix string, ok bool) {
dnsName = strings.TrimSuffix(dnsName, ".")
dnsName = strings.ToLower(dnsName)

parts := strings.Split(dnsName, ".")
if len(parts) != 5 {
return "", "", "", "", false
}

if parts[4] != "goog" {
return "", "", "", "", false
}

suffixType := parts[3]
if suffixType != "sql" && suffixType != "sql-psa" && suffixType != "sql-psc" {
m := instanceDNSNamePattern.FindStringSubmatch(dnsName)
if m == nil {
return "", "", "", "", false
}
instanceLabel = m[1]
projectLabel = m[2]
region = m[3]
suffix = m[4] + ".goog"

instanceLabel = parts[0]
projectLabel = parts[1]
region = parts[2]
suffix = suffixType + ".goog"

// Validate region != "global". "global" is used for redirect to write endpoint DNS
if region == "global" {
return "", "", "", "", false
}

// Validate labels
if len(instanceLabel) != 12 {
return "", "", "", "", false
}
for _, c := range instanceLabel {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
return "", "", "", "", false
}
}

if !strings.Contains(region, "-") {
return "", "", "", "", false
}

return instanceLabel, projectLabel, region, suffix, true
}

// IsInstanceDNSName returns true if the domain name matches the well-known
// Cloud SQL instance DNS name pattern.
func IsInstanceDNSName(dnsName string) bool {
dnsName = strings.ToLower(dnsName)
return instanceDNSSuffixRegex.MatchString(dnsName) && !globalInstanceDNSRegex.MatchString(dnsName)
m := instanceDNSNamePattern.FindStringSubmatch(dnsName)
if m == nil {
return false
}
return m[3] != "global"
}
39 changes: 34 additions & 5 deletions internal/cloudsql/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,34 @@ func TestDNSInstanceNameResolver_Lookup_Success_DirectPSC(t *testing.T) {
}
}

func TestDNSInstanceNameResolver_Lookup_Success_DirectPSC_Permissive(t *testing.T) {
// non-hex hash, short labels, region without hyphen
dnsName := "g123.p.uscentral.sql-psc.goog"
realConnName := "my-project:uscentral:my-instance"
want, _ := instance.ParseConnNameWithDomainName(realConnName, dnsName)

client, cleanup, err := mock.NewSQLAdminService(
context.Background(),
mock.ResolveConnectSettingsSuccess(dnsName+".", "uscentral", realConnName, 1),
)
if err != nil {
t.Fatal(err)
}
defer cleanup()

r := dnsInstanceConnectionNameResolver{
dnsResolver: &fakeResolver{},
client: client,
}
got, err := r.Resolve(context.Background(), dnsName)
if err != nil {
t.Fatal("got error", err)
}
if got != want {
t.Fatal("Got", got, "Want", want)
}
}

func TestDNSInstanceNameResolver_Lookup_Success_CnamePSC(t *testing.T) {
dnsName := "db.example.com"
cnameTarget := "0123456789ab.fedcba9876543.europe-north2.sql-psc.goog"
Expand Down Expand Up @@ -155,11 +183,12 @@ func TestDNSInstanceNameResolver_Lookup_Success_CnamePSC(t *testing.T) {

func TestDNSInstanceNameResolver_Lookup_Fails_InvalidPattern(t *testing.T) {
invalidDNSNames := []string{
"0123456789ab.fedcba9876543.europe-north2.sql-psc.goog.com", // wrong suffix domain
"0123456789ag.fedcba9876543.europe-north2.sql-psc.goog", // non-hex char 'g' in hash
"0123456789a.fedcba9876543.europe-north2.sql-psc.goog", // wrong hash length (11)
"0123456789abc.fedcba9876543.europe-north2.sql-psc.goog", // wrong hash length (13)
"0123456789ab.fedcba9876543.europenorth2.sql-psc.goog", // region has no hyphen
"0123456789ab.fedcba9876543.europe-north2.sql-psc.goog.com", // wrong suffix domain
"0123456789ab.fedcba9876543.europe-north2.sql-psc.goog.other", // wrong suffix
"0123456789ab..europe-north2.sql-psc.goog", // empty project label
"-abc.def.ghi.sql.goog", // label starts with hyphen
"abc-.def.ghi.sql.goog", // label ends with hyphen
"abc.def.ghi.sql-other.goog", // invalid suffix type
}

for _, dnsName := range invalidDNSNames {
Expand Down
40 changes: 40 additions & 0 deletions monitored_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,43 @@ func TestMonitoredCache_Close(t *testing.T) {
}

}

func TestMonitoredCache_OptimizationForImmutableNames(t *testing.T) {
tcs := []struct {
desc string
domainName string
wantTicker bool
}{
{
desc: "custom DNS name",
domainName: "db.example.com",
wantTicker: true,
},
{
desc: "immutable instance DNS name",
domainName: "0123456789ab.fedcba9876543.us-central1.sql-psc.goog",
wantTicker: false,
},
{
desc: "mutable global instance DNS name",
domainName: "0123456789ab.fedcba9876543.global.sql-psc.goog",
wantTicker: true,
},
}

for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
cn, err := instance.ParseConnNameWithDomainName("my-project:my-region:my-instance", tc.domainName)
if err != nil {
t.Fatalf("ParseConnNameWithDomainName failed: %v", err)
}
c := newMonitoredCache(&spyConnectionInfoCache{}, cn, 10*time.Millisecond, nil, &testLog{t: t})
defer c.Close()

hasTicker := c.domainNameTicker != nil
if hasTicker != tc.wantTicker {
t.Errorf("got ticker = %v, want %v", hasTicker, tc.wantTicker)
}
})
}
}
Loading