Key-value store domain: kv.sslip.io → k-v.io

I didn't want a really long domain for the key-value store; I wanted a
short, easy-to-remember domain. And it cost $400 for ten years.

Many good domains (e.g. keyvalue.store, kv.io)
were taken, and some weren't easily registered (e.g. the Albanian
domain, keyv.al).

Browsing these domains that were never put into use is like strolling
along the Boulevard of Broken Dreams: high hopes dashed against the hard
rocks of reality.
This commit is contained in:
Brian Cunnie
2021-12-29 19:56:52 -08:00
parent 3066a22f57
commit 5065229a03
4 changed files with 68 additions and 81 deletions

View File

@@ -121,26 +121,26 @@ var _ = Describe("sslip.io-dns-server", func() {
"@127.0.0.1 example.com txt +short", "@127.0.0.1 example.com txt +short",
`\A\z`, `\A\z`,
`TypeTXT example.com. \? nil, SOA example.com. briancunnie.gmail.com. 2021080200 900 900 1800 300\n$`), `TypeTXT example.com. \? nil, SOA example.com. briancunnie.gmail.com. 2021080200 900 900 1800 300\n$`),
Entry(`getting a non-existent value: TXT for my-key.kv.sslip.io"`, Entry(`getting a non-existent value: TXT for my-key.k-v.io"`,
"@127.0.0.1 my-key.kv.sslip.io txt +short", "@127.0.0.1 my-key.k-v.io txt +short",
`\A\z`, `\A\z`,
`TypeTXT my-key.kv.sslip.io. \? nil, SOA my-key.kv.sslip.io. briancunnie.gmail.com. 2021080200 900 900 1800 300\n$`), `TypeTXT my-key.k-v.io. \? nil, SOA my-key.k-v.io. briancunnie.gmail.com. 2021080200 900 900 1800 300\n$`),
Entry(`putting a value: TXT for put.MyValue.MY-KEY.kv.sslip.io"`, Entry(`putting a value: TXT for put.MyValue.MY-KEY.k-v.io"`,
"@127.0.0.1 put.MyValue.MY-KEY.kv.sslip.io txt +short", "@127.0.0.1 put.MyValue.MY-KEY.k-v.io txt +short",
`"MyValue"`, `"MyValue"`,
`TypeTXT put.MyValue.MY-KEY.kv.sslip.io. \? \["MyValue"\]`), `TypeTXT put.MyValue.MY-KEY.k-v.io. \? \["MyValue"\]`),
Entry(`getting a value: TXT for my-key.kv.sslip.io"`, Entry(`getting a value: TXT for my-key.k-v.io"`,
"@127.0.0.1 my-key.kv.sslip.io txt +short", "@127.0.0.1 my-key.k-v.io txt +short",
`"MyValue"`, `"MyValue"`,
`TypeTXT my-key.kv.sslip.io. \? \["MyValue"\]`), `TypeTXT my-key.k-v.io. \? \["MyValue"\]`),
Entry(`deleting a value: TXT for delete.my-key.kv.sslip.io"`, Entry(`deleting a value: TXT for delete.my-key.k-v.io"`,
"@127.0.0.1 delete.my-key.kv.sslip.io txt +short", "@127.0.0.1 delete.my-key.k-v.io txt +short",
`"MyValue"`, `"MyValue"`,
`TypeTXT delete.my-key.kv.sslip.io. \? \["MyValue"\]`), `TypeTXT delete.my-key.k-v.io. \? \["MyValue"\]`),
Entry(`getting a non-existent value: TXT for my-key.kv.sslip.io"`, Entry(`getting a non-existent value: TXT for my-key.k-v.io"`,
"@127.0.0.1 my-key.kv.sslip.io txt +short", "@127.0.0.1 my-key.k-v.io txt +short",
`\A\z`, `\A\z`,
`TypeTXT my-key.kv.sslip.io. \? nil, SOA my-key.kv.sslip.io. briancunnie.gmail.com. 2021080200 900 900 1800 300\n$`), `TypeTXT my-key.k-v.io. \? nil, SOA my-key.k-v.io. briancunnie.gmail.com. 2021080200 900 900 1800 300\n$`),
) )
}) })
Describe("for more complex assertions", func() { Describe("for more complex assertions", func() {
@@ -225,33 +225,33 @@ var _ = Describe("sslip.io-dns-server", func() {
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT sslip.io. \? \["protonmail-verification=ce0ca3f5010aa7a2cf8bcc693778338ffde73e26"\], \["v=spf1 include:_spf.protonmail.ch mx ~all"\]\n`)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT sslip.io. \? \["protonmail-verification=ce0ca3f5010aa7a2cf8bcc693778338ffde73e26"\], \["v=spf1 include:_spf.protonmail.ch mx ~all"\]\n`))
}) })
}) })
When(`a TXT record for a host under the "kv.sslip.io" domain is queried`, func() { When(`a TXT record for a host under the "k-v.io" domain is queried`, func() {
It(`the PUT has a three-minute TTL`, func() { It(`the PUT has a three-minute TTL`, func() {
digArgs = "@localhost put.a.b.kv.sslip.io txt" digArgs = "@localhost put.a.b.k-v.io txt"
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...) digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter) digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Eventually(digSession).Should(Say(`put.a.b.kv.sslip.io. 180 IN TXT "a"`)) Eventually(digSession).Should(Say(`put.a.b.k-v.io. 180 IN TXT "a"`))
Eventually(digSession, 1).Should(Exit(0)) Eventually(digSession, 1).Should(Exit(0))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT put.a.b.kv.sslip.io. \? \["a"\]`)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT put.a.b.k-v.io. \? \["a"\]`))
}) })
It(`the GET has a three-minute TTL`, func() { It(`the GET has a three-minute TTL`, func() {
digArgs = "@localhost b.kv.sslip.io txt" digArgs = "@localhost b.k-v.io txt"
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...) digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter) digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Eventually(digSession).Should(Say(`b.kv.sslip.io. 180 IN TXT "a"`)) Eventually(digSession).Should(Say(`b.k-v.io. 180 IN TXT "a"`))
Eventually(digSession, 1).Should(Exit(0)) Eventually(digSession, 1).Should(Exit(0))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT b.kv.sslip.io. \? \["a"\]`)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT b.k-v.io. \? \["a"\]`))
}) })
It(`the DELETE has a three-minute TTL`, func() { It(`the DELETE has a three-minute TTL`, func() {
digArgs = "@localhost delete.b.kv.sslip.io txt" digArgs = "@localhost delete.b.k-v.io txt"
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...) digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter) digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Eventually(digSession).Should(Say(`delete.b.kv.sslip.io. 180 IN TXT "a"`)) Eventually(digSession).Should(Say(`delete.b.k-v.io. 180 IN TXT "a"`))
Eventually(digSession, 1).Should(Exit(0)) Eventually(digSession, 1).Should(Exit(0))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT delete.b.kv.sslip.io. \? \["a"\]`)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT delete.b.k-v.io. \? \["a"\]`))
}) })
}) })
When(`a record for an "_acme-challenge" domain is queried`, func() { When(`a record for an "_acme-challenge" domain is queried`, func() {

View File

@@ -20,7 +20,7 @@ import (
// Xip contains info that the routines need to answer a query that I don't want to plumb // Xip contains info that the routines need to answer a query that I don't want to plumb
// through the call hierarchy // through the call hierarchy
// (the source address for `ip.sslip.io`, and the etcd client for `kv.sslip.io`) // (the source address for `ip.sslip.io`, and the etcd client for `k-v.io`)
type Xip struct { type Xip struct {
SrcAddr net.IP SrcAddr net.IP
Etcd *v3client.Client Etcd *v3client.Client
@@ -53,7 +53,7 @@ type DomainCustomizations map[string]DomainCustomization
// KvCustomizations is a lookup table for custom TXT records // KvCustomizations is a lookup table for custom TXT records
// e.g. KvCustomizations["my-key"] = []dnsmessage.TXTResource{ TXT: { "my-value" } } // e.g. KvCustomizations["my-key"] = []dnsmessage.TXTResource{ TXT: { "my-value" } }
// The key should NOT include ".kv.sslip.io." // The key should NOT include ".k-v.io."
type KvCustomizations map[string][]dnsmessage.TXTResource type KvCustomizations map[string][]dnsmessage.TXTResource
// There's nothing like global variables to make my heart pound with joy. // There's nothing like global variables to make my heart pound with joy.
@@ -66,7 +66,7 @@ var (
// https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses // https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
ipv6RE = regexp.MustCompile(`(^|[.-])(([0-9a-fA-F]{1,4}-){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,7}-|([0-9a-fA-F]{1,4}-){1,6}-[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,5}(-[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}-){1,4}(-[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}-){1,3}(-[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}-){1,2}(-[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}-((-[0-9a-fA-F]{1,4}){1,6})|-((-[0-9a-fA-F]{1,4}){1,7}|-)|fe80-(-[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|--(ffff(-0{1,4})?-)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}-){1,4}-((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))($|[.-])`) ipv6RE = regexp.MustCompile(`(^|[.-])(([0-9a-fA-F]{1,4}-){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,7}-|([0-9a-fA-F]{1,4}-){1,6}-[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,5}(-[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}-){1,4}(-[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}-){1,3}(-[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}-){1,2}(-[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}-((-[0-9a-fA-F]{1,4}){1,6})|-((-[0-9a-fA-F]{1,4}){1,7}|-)|fe80-(-[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|--(ffff(-0{1,4})?-)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}-){1,4}-((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))($|[.-])`)
dns01ChallengeRE = regexp.MustCompile(`(?i)_acme-challenge\.`) dns01ChallengeRE = regexp.MustCompile(`(?i)_acme-challenge\.`)
kvRE = regexp.MustCompile(`\.kv\.sslip\.io\.$`) kvRE = regexp.MustCompile(`\.k-v\.io\.$`)
nsAwsSslip, _ = dnsmessage.NewName("ns-aws.sslip.io.") nsAwsSslip, _ = dnsmessage.NewName("ns-aws.sslip.io.")
nsAzureSslip, _ = dnsmessage.NewName("ns-azure.sslip.io.") nsAzureSslip, _ = dnsmessage.NewName("ns-azure.sslip.io.")
nsGceSslip, _ = dnsmessage.NewName("ns-gce.sslip.io.") nsGceSslip, _ = dnsmessage.NewName("ns-gce.sslip.io.")
@@ -745,27 +745,27 @@ func ipSslipIo(sourceIP string) ([]dnsmessage.TXTResource, error) {
return []dnsmessage.TXTResource{{TXT: []string{sourceIP}}}, nil return []dnsmessage.TXTResource{{TXT: []string{sourceIP}}}, nil
} }
// when TXT for "kv.sslip.io" is queried, return the key-value pair // when TXT for "k-v.io" is queried, return the key-value pair
func (x Xip) kvTXTResources(fqdn string) ([]dnsmessage.TXTResource, error) { func (x Xip) kvTXTResources(fqdn string) ([]dnsmessage.TXTResource, error) {
// "labels" => official RFC 1035 term // "labels" => official RFC 1035 term
// kv.sslip.io. => ["kv", "sslip", "io"] are labels // k-v.io. => ["k-v", "io"] are labels
var ( var (
verb string // i.e. "get", "put", "delete" verb string // i.e. "get", "put", "delete"
key string // e.g. "my-key" as in "my-key.kv.sslip.io" key string // e.g. "my-key" as in "my-key.k-v.io"
value string // e.g. "my-value" as in "put.my-value.my-key.kv.sslip.io" value string // e.g. "my-value" as in "put.my-value.my-key.k-v.io"
) )
labels := strings.Split(fqdn, ".") labels := strings.Split(fqdn, ".")
labels = labels[:len(labels)-4] // strip ".kv.sslip.io" labels = labels[:len(labels)-3] // strip ".k-v.io"
key = strings.ToLower(labels[len(labels)-1]) // key is always present, always first subdomain of "kv.sslip.io" key = strings.ToLower(labels[len(labels)-1]) // key is always present, always first subdomain of "k-v.io"
switch { switch {
case len(labels) == 1: case len(labels) == 1:
verb = "get" // default action if only key, not verb, is not present verb = "get" // default action if only key, not verb, is not present
case len(labels) == 2: case len(labels) == 2:
verb = strings.ToLower(labels[0]) // verb, if present, is leftmost, "put.value.key.kv.sslip.io" verb = strings.ToLower(labels[0]) // verb, if present, is leftmost, "put.value.key.k-v.io"
case len(labels) > 2: case len(labels) > 2:
verb = strings.ToLower(labels[0]) verb = strings.ToLower(labels[0])
// concatenate multiple labels to create value, especially useful for version numbers // concatenate multiple labels to create value, especially useful for version numbers
value = strings.Join(labels[1:len(labels)-1], ".") // e.g. "put.94.0.2.firefox-version.kv.sslip.io" value = strings.Join(labels[1:len(labels)-1], ".") // e.g. "put.94.0.2.firefox-version.k-v.io"
} }
// prepare to query etcd: // prepare to query etcd:
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)

View File

@@ -172,7 +172,7 @@ var _ = Describe("Xip", func() {
AfterEach(func() { AfterEach(func() {
x.Etcd.Close() x.Etcd.Close()
}) })
DescribeTable(`the domain "kv.sslip.io" is queried`, DescribeTable(`the domain "k-v.io" is queried`,
func(fqdn string, txts []string) { func(fqdn string, txts []string) {
txtResources, err := x.TXTResources(fqdn) txtResources, err := x.TXTResources(fqdn)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@@ -183,25 +183,25 @@ var _ = Describe("Xip", func() {
} }
}, },
// simple tests: get, put, delete with single label value // simple tests: get, put, delete with single label value
Entry("no arguments → empty array", "kv.sslip.io.", []string{}), Entry("no arguments → empty array", "k-v.io.", []string{}),
Entry("putting a value → that value", "PUT.MyValue.my-key.kv.sslip.io.", []string{"MyValue"}), Entry("putting a value → that value", "PUT.MyValue.my-key.k-v.io.", []string{"MyValue"}),
Entry("getting that value → that value", "my-key.kv.sslip.io.", []string{"MyValue"}), Entry("getting that value → that value", "my-key.k-v.io.", []string{"MyValue"}),
Entry("getting that value with an UPPERCASE key → that value", "MY-KEY.kv.sslip.io.", []string{"MyValue"}), Entry("getting that value with an UPPERCASE key → that value", "MY-KEY.k-v.io.", []string{"MyValue"}),
Entry("explicitly getting that value → that value", "GeT.my-key.kv.sslip.io.", []string{"MyValue"}), Entry("explicitly getting that value → that value", "GeT.my-key.k-v.io.", []string{"MyValue"}),
Entry("deleting that value → the deleted value", "DelETe.my-key.kv.sslip.io.", []string{"MyValue"}), Entry("deleting that value → the deleted value", "DelETe.my-key.k-v.io.", []string{"MyValue"}),
Entry("getting that deleted value → empty array", "my-key.kv.sslip.io.", []string{}), Entry("getting that deleted value → empty array", "my-key.k-v.io.", []string{}),
// errors // errors
Entry("getting a non-existent key → empty array", "nonexistent.kv.sslip.io.", []string{}), Entry("getting a non-existent key → empty array", "nonexistent.k-v.io.", []string{}),
Entry("putting but skipping the value → error txt", "put.my-key.kv.sslip.io.", []string{"422: no value provided"}), Entry("putting but skipping the value → error txt", "put.my-key.k-v.io.", []string{"422: no value provided"}),
Entry("deleting a non-existent key → silently succeeds", "delete.non-existent.kv.sslip.io.", []string{}), Entry("deleting a non-existent key → silently succeeds", "delete.non-existent.k-v.io.", []string{}),
Entry("using a garbage verb → error txt", "post.my-key.kv.sslip.io.", []string{"422: valid verbs are get, put, delete"}), Entry("using a garbage verb → error txt", "post.my-key.k-v.io.", []string{"422: valid verbs are get, put, delete"}),
// others // others
Entry("putting a multi-label value", "put.96.0.4664.55.chrome-version.kv.sslip.io.", []string{"96.0.4664.55"}), Entry("putting a multi-label value", "put.96.0.4664.55.chrome-version.k-v.io.", []string{"96.0.4664.55"}),
Entry("putting a super-long multi-label value to use in a DNS amplification attack gets truncated to 63 characters", Entry("putting a super-long multi-label value to use in a DNS amplification attack gets truncated to 63 characters",
"put"+ "put"+
".IReturnedAndSawUnderTheSunThatTheRaceIsNotToTheSwiftNotThe"+ ".IReturnedAndSawUnderTheSunThatTheRaceIsNotToTheSwiftNotThe"+
".BattleToTheStrongNeitherYetBreadToTheWiseNorYetRichesToMenOf"+ ".BattleToTheStrongNeitherYetBreadToTheWiseNorYetRichesToMenOf"+
".amplify.kv.sslip.io.", ".amplify.k-v.io.",
[]string{"IReturnedAndSawUnderTheSunThatTheRaceIsNotToTheSwiftNotThe.Batt"}, []string{"IReturnedAndSawUnderTheSunThatTheRaceIsNotToTheSwiftNotThe.Batt"},
), ),
) )

View File

@@ -227,45 +227,32 @@ dig @ns.sslip.io txt ip.sslip.io +short -6 # forces IPv6 lookup; sample reply "2
"https://icanhazip.com/">https://icanhazip.com/</a> requires 8692 bytes spread out over 34 packets—over 14 times "https://icanhazip.com/">https://icanhazip.com/</a> requires 8692 bytes spread out over 34 packets—over 14 times
as much! Admittedly bandwidth usage is a bigger concern for the one hosting the service than the one using the as much! Admittedly bandwidth usage is a bigger concern for the one hosting the service than the one using the
service.</p> service.</p>
<h4 id="key-value-store"><code>kv.sslip.io</code>: (key-value) read/write/delete TXTs</h4> <h4 id="key-value-store"><code>k-v.io</code>: (key-value) read/write/delete TXTs</h4>
<p>We enable special behavior under the <code>kv.sslip.io</code> subdomain: it can be treated as a key-value <p>We enable special behavior under the <code>k-v.io</code> domain: it can be treated as a key-value store, the
store, the sub-subdomain being the key, and the TXT record being the value.</p> subdomain being the key, and the TXT record being the value.</p>
<p>For example, to write ("put") the value "12.0.1" to the key "macos-version" on the <p>For example, to write ("put") the value "12.0.1" to the key "macos-version" on the
<code>ns-gce.sslip.io.</code> nameserver, you'd use the following <code>dig</code> command:</p> <code>ns-gce.sslip.io.</code> nameserver, you'd use the following <code>dig</code> command:</p>
<pre><code class="lang-shell">dig @ns-gce<span class="hljs-selector-class">.sslip</span><span class= <pre><code class="lang-shell">dig @ns-gce.sslip.io. txt put.12.0.1.macos-version.k-v.io</code>.
"hljs-selector-class">.io</span>. txt put.<span class="hljs-number">12.0</span>.<span class= </pre>
"hljs-number">1</span><span class="hljs-selector-class">.macos-version</span><span class=
"hljs-selector-class">.kv</span><span class="hljs-selector-class">.sslip</span><span class=
"hljs-selector-class">.io</span>.
</code></pre>
<p>To read ("get") the value back, you'd write the following <code>dig</code> command:</p> <p>To read ("get") the value back, you'd write the following <code>dig</code> command:</p>
<pre><code class="lang-shell">dig @ns-gce<span class="hljs-selector-class">.sslip</span><span class= <pre><code class="lang-shell">dig @ns-gce.sslip.io. txt get.macos-version.k-v.io.</code>
"hljs-selector-class">.io</span>. txt get<span class="hljs-selector-class">.macos-version</span><span class= </pre>
"hljs-selector-class">.kv</span><span class="hljs-selector-class">.sslip</span><span class=
"hljs-selector-class">.io</span>.
</code></pre>
<p>Since "get" is the default behavior, you don't need to include it in the domain name:</p> <p>Since "get" is the default behavior, you don't need to include it in the domain name:</p>
<pre><code class="lang-shell">dig @ns-gce<span class="hljs-selector-class">.sslip</span><span class= <pre><code class="lang-shell">dig @ns-gce.sslip.io. txt macos-version.k-v.io.</code>
"hljs-selector-class">.io</span>. txt macos-version<span class="hljs-selector-class">.kv</span><span class= </pre>
"hljs-selector-class">.sslip</span><span class="hljs-selector-class">.io</span>.
</code></pre>
<p>Finally, when you're done with the key-value, you can "delete" it:</p> <p>Finally, when you're done with the key-value, you can "delete" it:</p>
<pre><code class="lang-shell">dig @ns-gce<span class="hljs-selector-class">.sslip</span><span class= <pre><code class="lang-shell">dig @ns-gce.sslip.io. txt delete.macos-version.k-v.io.
"hljs-selector-class">.io</span>. txt delete<span class="hljs-selector-class">.macos-version</span><span class=
"hljs-selector-class">.kv</span><span class="hljs-selector-class">.sslip</span><span class=
"hljs-selector-class">.io</span>.
</code></pre> </code></pre>
<p>Notes:</p> <p><code class="lang-shell">Notes:</code></p>
<ul> <ul>
<li>Keys are case-insensitive (to accommodate DNS convention). In other words, <code>KEY.kv.sslip.io</code> and <li>Keys are case-insensitive (to accommodate DNS convention). In other words,
<code>key.kv.sslip.io</code> return the same TXT record.</li> <code>KEY.k-v.io</code> and <code>key.k-v.io</code> return the same TXT record.</code></li>
<li>Values are case-sensitive. <code>put.CamelCase.style.kv.sslip.io</code> sets the TXT record to <li>Values are case-sensitive. <code>put.CamelCase.style.k-v.io</code> sets the TXT record to "CamelCase".</li>
"CamelCase".</li> <li><code>put</code> requests will return the TXT record being put; i.e. <code>put.hello.world.k-v.io</code>
<li><code>put</code> requests will return the TXT record being put; i.e. returns one TXT record of one string, <code>hello</code>.</li>
<code>put.hello.world.kv.sslip.io</code> returns one TXT record of one string, <code>hello</code>.</li>
<li><code>delete</code> requests will return the TXT record being deleted; i.e. <li><code>delete</code> requests will return the TXT record being deleted; i.e.
<code>delete.world.kv.sslip.io</code> returns one TXT record of one string, <code>hello</code>. If the TXT <code>delete.world.k-v.io</code> returns one TXT record of one string, <code>hello</code>. If the TXT record
record does not exist, no TXT records will be returned.</li> does not exist, no TXT records will be returned.</li>
<li>Values are limited to 63 bytes to mitigate using the sslip.io servers in a <a href= <li>Values are limited to 63 bytes to mitigate using the sslip.io servers in a <a href=
"https://us-cert.cisa.gov/ncas/alerts/TA13-088A">DNS amplification attack</a>. "https://us-cert.cisa.gov/ncas/alerts/TA13-088A">DNS amplification attack</a>.
</li> </li>