mirror of https://github.com/hashicorp/consul
Merge pull request #3343 from zeroae/f-node-dns-txt-record
commit
622bc8473b
69
agent/dns.go
69
agent/dns.go
|
@ -341,7 +341,7 @@ func (d *DNSServer) nameservers(edns bool) (ns []dns.RR, extra []dns.RR) {
|
||||||
}
|
}
|
||||||
ns = append(ns, nsrr)
|
ns = append(ns, nsrr)
|
||||||
|
|
||||||
glue := d.formatNodeRecord(addr, fqdn, dns.TypeANY, d.config.NodeTTL, edns)
|
glue := d.formatNodeRecord(nil, addr, fqdn, dns.TypeANY, d.config.NodeTTL, edns)
|
||||||
extra = append(extra, glue...)
|
extra = append(extra, glue...)
|
||||||
|
|
||||||
// don't provide more than 3 servers
|
// don't provide more than 3 servers
|
||||||
|
@ -485,9 +485,9 @@ INVALID:
|
||||||
|
|
||||||
// nodeLookup is used to handle a node query
|
// nodeLookup is used to handle a node query
|
||||||
func (d *DNSServer) nodeLookup(network, datacenter, node string, req, resp *dns.Msg) {
|
func (d *DNSServer) nodeLookup(network, datacenter, node string, req, resp *dns.Msg) {
|
||||||
// Only handle ANY, A and AAAA type requests
|
// Only handle ANY, A, AAAA, and TXT type requests
|
||||||
qType := req.Question[0].Qtype
|
qType := req.Question[0].Qtype
|
||||||
if qType != dns.TypeANY && qType != dns.TypeA && qType != dns.TypeAAAA {
|
if qType != dns.TypeANY && qType != dns.TypeA && qType != dns.TypeAAAA && qType != dns.TypeTXT {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,23 +530,45 @@ RPC:
|
||||||
n := out.NodeServices.Node
|
n := out.NodeServices.Node
|
||||||
edns := req.IsEdns0() != nil
|
edns := req.IsEdns0() != nil
|
||||||
addr := d.agent.TranslateAddress(datacenter, n.Address, n.TaggedAddresses)
|
addr := d.agent.TranslateAddress(datacenter, n.Address, n.TaggedAddresses)
|
||||||
records := d.formatNodeRecord(addr, req.Question[0].Name, qType, d.config.NodeTTL, edns)
|
records := d.formatNodeRecord(out.NodeServices.Node, addr, req.Question[0].Name, qType, d.config.NodeTTL, edns)
|
||||||
if records != nil {
|
if records != nil {
|
||||||
resp.Answer = append(resp.Answer, records...)
|
resp.Answer = append(resp.Answer, records...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatNodeRecord takes a Node and returns an A, AAAA, or CNAME record
|
// encodeKVasRFC1464 encodes a key-value pair according to RFC1464
|
||||||
func (d *DNSServer) formatNodeRecord(addr, qName string, qType uint16, ttl time.Duration, edns bool) (records []dns.RR) {
|
func encodeKVasRFC1464(key, value string) (txt string) {
|
||||||
|
// For details on these replacements c.f. https://www.ietf.org/rfc/rfc1464.txt
|
||||||
|
key = strings.Replace(key, "`", "``", -1)
|
||||||
|
key = strings.Replace(key, "=", "`=", -1)
|
||||||
|
|
||||||
|
// Backquote the leading spaces
|
||||||
|
leadingSpacesRE := regexp.MustCompile("^ +")
|
||||||
|
numLeadingSpaces := len(leadingSpacesRE.FindString(key))
|
||||||
|
key = leadingSpacesRE.ReplaceAllString(key, strings.Repeat("` ", numLeadingSpaces))
|
||||||
|
|
||||||
|
// Backquote the trailing spaces
|
||||||
|
trailingSpacesRE := regexp.MustCompile(" +$")
|
||||||
|
numTrailingSpaces := len(trailingSpacesRE.FindString(key))
|
||||||
|
key = trailingSpacesRE.ReplaceAllString(key, strings.Repeat("` ", numTrailingSpaces))
|
||||||
|
|
||||||
|
value = strings.Replace(value, "`", "``", -1)
|
||||||
|
|
||||||
|
return key + "=" + value
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatNodeRecord takes a Node and returns an A, AAAA, TXT or CNAME record
|
||||||
|
func (d *DNSServer) formatNodeRecord(node *structs.Node, addr, qName string, qType uint16, ttl time.Duration, edns bool) (records []dns.RR) {
|
||||||
// Parse the IP
|
// Parse the IP
|
||||||
ip := net.ParseIP(addr)
|
ip := net.ParseIP(addr)
|
||||||
var ipv4 net.IP
|
var ipv4 net.IP
|
||||||
if ip != nil {
|
if ip != nil {
|
||||||
ipv4 = ip.To4()
|
ipv4 = ip.To4()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ipv4 != nil && (qType == dns.TypeANY || qType == dns.TypeA):
|
case ipv4 != nil && (qType == dns.TypeANY || qType == dns.TypeA):
|
||||||
return []dns.RR{&dns.A{
|
records = append(records, &dns.A{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
Rrtype: dns.TypeA,
|
Rrtype: dns.TypeA,
|
||||||
|
@ -554,10 +576,10 @@ func (d *DNSServer) formatNodeRecord(addr, qName string, qType uint16, ttl time.
|
||||||
Ttl: uint32(ttl / time.Second),
|
Ttl: uint32(ttl / time.Second),
|
||||||
},
|
},
|
||||||
A: ip,
|
A: ip,
|
||||||
}}
|
})
|
||||||
|
|
||||||
case ip != nil && ipv4 == nil && (qType == dns.TypeANY || qType == dns.TypeAAAA):
|
case ip != nil && ipv4 == nil && (qType == dns.TypeANY || qType == dns.TypeAAAA):
|
||||||
return []dns.RR{&dns.AAAA{
|
records = append(records, &dns.AAAA{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
Rrtype: dns.TypeAAAA,
|
Rrtype: dns.TypeAAAA,
|
||||||
|
@ -565,10 +587,10 @@ func (d *DNSServer) formatNodeRecord(addr, qName string, qType uint16, ttl time.
|
||||||
Ttl: uint32(ttl / time.Second),
|
Ttl: uint32(ttl / time.Second),
|
||||||
},
|
},
|
||||||
AAAA: ip,
|
AAAA: ip,
|
||||||
}}
|
})
|
||||||
|
|
||||||
case ip == nil && (qType == dns.TypeANY || qType == dns.TypeCNAME ||
|
case ip == nil && (qType == dns.TypeANY || qType == dns.TypeCNAME ||
|
||||||
qType == dns.TypeA || qType == dns.TypeAAAA):
|
qType == dns.TypeA || qType == dns.TypeAAAA || qType == dns.TypeTXT):
|
||||||
// Get the CNAME
|
// Get the CNAME
|
||||||
cnRec := &dns.CNAME{
|
cnRec := &dns.CNAME{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
|
@ -587,7 +609,7 @@ func (d *DNSServer) formatNodeRecord(addr, qName string, qType uint16, ttl time.
|
||||||
MORE_REC:
|
MORE_REC:
|
||||||
for _, rr := range more {
|
for _, rr := range more {
|
||||||
switch rr.Header().Rrtype {
|
switch rr.Header().Rrtype {
|
||||||
case dns.TypeCNAME, dns.TypeA, dns.TypeAAAA:
|
case dns.TypeCNAME, dns.TypeA, dns.TypeAAAA, dns.TypeTXT:
|
||||||
records = append(records, rr)
|
records = append(records, rr)
|
||||||
extra++
|
extra++
|
||||||
if extra == maxRecurseRecords && !edns {
|
if extra == maxRecurseRecords && !edns {
|
||||||
|
@ -596,6 +618,25 @@ func (d *DNSServer) formatNodeRecord(addr, qName string, qType uint16, ttl time.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if node != nil && (qType == dns.TypeANY || qType == dns.TypeTXT) {
|
||||||
|
for key, value := range node.Meta {
|
||||||
|
txt := value
|
||||||
|
if !strings.HasPrefix(strings.ToLower(key), "rfc1035-") {
|
||||||
|
txt = encodeKVasRFC1464(key, value)
|
||||||
|
}
|
||||||
|
records = append(records, &dns.TXT{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: qName,
|
||||||
|
Rrtype: dns.TypeTXT,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
Ttl: uint32(ttl / time.Second),
|
||||||
|
},
|
||||||
|
Txt: []string{txt},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return records
|
return records
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,7 +970,7 @@ func (d *DNSServer) serviceNodeRecords(dc string, nodes structs.CheckServiceNode
|
||||||
handled[addr] = struct{}{}
|
handled[addr] = struct{}{}
|
||||||
|
|
||||||
// Add the node record
|
// Add the node record
|
||||||
records := d.formatNodeRecord(addr, qName, qType, ttl, edns)
|
records := d.formatNodeRecord(node.Node, addr, qName, qType, ttl, edns)
|
||||||
if records != nil {
|
if records != nil {
|
||||||
resp.Answer = append(resp.Answer, records...)
|
resp.Answer = append(resp.Answer, records...)
|
||||||
}
|
}
|
||||||
|
@ -973,7 +1014,7 @@ func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the extra record
|
// Add the extra record
|
||||||
records := d.formatNodeRecord(addr, srvRec.Target, dns.TypeANY, ttl, edns)
|
records := d.formatNodeRecord(node.Node, addr, srvRec.Target, dns.TypeANY, ttl, edns)
|
||||||
if len(records) > 0 {
|
if len(records) > 0 {
|
||||||
// Use the node address if it doesn't differ from the service address
|
// Use the node address if it doesn't differ from the service address
|
||||||
if addr == node.Node.Address {
|
if addr == node.Node.Address {
|
||||||
|
|
|
@ -78,6 +78,18 @@ func dnsA(src, dest string) *dns.A {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dnsTXT returns a DNS TXT record struct
|
||||||
|
func dnsTXT(src string, txt []string) *dns.TXT {
|
||||||
|
return &dns.TXT{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: dns.Fqdn(src),
|
||||||
|
Rrtype: dns.TypeTXT,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
},
|
||||||
|
Txt: txt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRecursorAddr(t *testing.T) {
|
func TestRecursorAddr(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
addr, err := recursorAddr("8.8.8.8")
|
addr, err := recursorAddr("8.8.8.8")
|
||||||
|
@ -89,6 +101,35 @@ func TestRecursorAddr(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncodeKVasRFC1464(t *testing.T) {
|
||||||
|
// Test cases are from rfc1464
|
||||||
|
type rfc1464Test struct {
|
||||||
|
key, value, internalForm, externalForm string
|
||||||
|
}
|
||||||
|
tests := []rfc1464Test{
|
||||||
|
{"color", "blue", "color=blue", "color=blue"},
|
||||||
|
{"equation", "a=4", "equation=a=4", "equation=a=4"},
|
||||||
|
{"a=a", "true", "a`=a=true", "a`=a=true"},
|
||||||
|
{"a\\=a", "false", "a\\`=a=false", "a\\`=a=false"},
|
||||||
|
{"=", "\\=", "`==\\=", "`==\\="},
|
||||||
|
|
||||||
|
{"string", "\"Cat\"", "string=\"Cat\"", "string=\"Cat\""},
|
||||||
|
{"string2", "`abc`", "string2=``abc``", "string2=``abc``"},
|
||||||
|
{"novalue", "", "novalue=", "novalue="},
|
||||||
|
{"a b", "c d", "a b=c d", "a b=c d"},
|
||||||
|
{"abc ", "123 ", "abc` =123 ", "abc` =123 "},
|
||||||
|
|
||||||
|
// Additional tests
|
||||||
|
{" abc", " 321", "` abc= 321", "` abc= 321"},
|
||||||
|
{"`a", "b", "``a=b", "``a=b"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
answer := encodeKVasRFC1464(test.key, test.value)
|
||||||
|
verify.Values(t, "internalForm", answer, test.internalForm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDNS_NodeLookup(t *testing.T) {
|
func TestDNS_NodeLookup(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
a := NewTestAgent(t.Name(), "")
|
a := NewTestAgent(t.Name(), "")
|
||||||
|
@ -300,6 +341,7 @@ func TestDNS_NodeLookup_CNAME(t *testing.T) {
|
||||||
Answer: []dns.RR{
|
Answer: []dns.RR{
|
||||||
dnsCNAME("www.google.com", "google.com"),
|
dnsCNAME("www.google.com", "google.com"),
|
||||||
dnsA("google.com", "1.2.3.4"),
|
dnsA("google.com", "1.2.3.4"),
|
||||||
|
dnsTXT("google.com", []string{"my_txt_value"}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
defer recursor.Shutdown()
|
defer recursor.Shutdown()
|
||||||
|
@ -330,23 +372,113 @@ func TestDNS_NodeLookup_CNAME(t *testing.T) {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should have the service record, CNAME record + A record
|
wantAnswer := []dns.RR{
|
||||||
if len(in.Answer) != 3 {
|
&dns.CNAME{
|
||||||
|
Hdr: dns.RR_Header{Name: "google.node.consul.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x10},
|
||||||
|
Target: "www.google.com.",
|
||||||
|
},
|
||||||
|
&dns.CNAME{
|
||||||
|
Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Rdlength: 0x2},
|
||||||
|
Target: "google.com.",
|
||||||
|
},
|
||||||
|
&dns.A{
|
||||||
|
Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4},
|
||||||
|
A: []byte{0x1, 0x2, 0x3, 0x4}, // 1.2.3.4
|
||||||
|
},
|
||||||
|
&dns.TXT{
|
||||||
|
Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xd},
|
||||||
|
Txt: []string{"my_txt_value"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
verify.Values(t, "answer", in.Answer, wantAnswer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNS_NodeLookup_TXT(t *testing.T) {
|
||||||
|
a := NewTestAgent(t.Name(), ``)
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "google",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
NodeMeta: map[string]string{
|
||||||
|
"rfc1035-00": "value0",
|
||||||
|
"key0": "value1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := a.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion("google.node.consul.", dns.TypeTXT)
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
in, _, err := c.Exchange(m, a.DNSAddr())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have the 1 TXT record reply
|
||||||
|
if len(in.Answer) != 2 {
|
||||||
t.Fatalf("Bad: %#v", in)
|
t.Fatalf("Bad: %#v", in)
|
||||||
}
|
}
|
||||||
|
|
||||||
cnRec, ok := in.Answer[0].(*dns.CNAME)
|
txtRec, ok := in.Answer[0].(*dns.TXT)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Bad: %#v", in.Answer[0])
|
t.Fatalf("Bad: %#v", in.Answer[0])
|
||||||
}
|
}
|
||||||
if cnRec.Target != "www.google.com." {
|
if len(txtRec.Txt) != 1 {
|
||||||
t.Fatalf("Bad: %#v", in.Answer[0])
|
t.Fatalf("Bad: %#v", in.Answer[0])
|
||||||
}
|
}
|
||||||
if cnRec.Hdr.Ttl != 0 {
|
if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" {
|
||||||
t.Fatalf("Bad: %#v", in.Answer[0])
|
t.Fatalf("Bad: %#v", in.Answer[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDNS_NodeLookup_ANY(t *testing.T) {
|
||||||
|
a := NewTestAgent(t.Name(), ``)
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
NodeMeta: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := a.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion("bar.node.consul.", dns.TypeANY)
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
in, _, err := c.Exchange(m, a.DNSAddr())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantAnswer := []dns.RR{
|
||||||
|
&dns.A{
|
||||||
|
Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4},
|
||||||
|
A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1
|
||||||
|
},
|
||||||
|
&dns.TXT{
|
||||||
|
Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa},
|
||||||
|
Txt: []string{"key=value"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
verify.Values(t, "answer", in.Answer, wantAnswer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestDNS_EDNS0(t *testing.T) {
|
func TestDNS_EDNS0(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
a := NewTestAgent(t.Name(), "")
|
a := NewTestAgent(t.Name(), "")
|
||||||
|
|
|
@ -57,8 +57,9 @@ we can instead use `foo.node.consul.` This convention allows for terse
|
||||||
syntax where appropriate while supporting queries of nodes in remote
|
syntax where appropriate while supporting queries of nodes in remote
|
||||||
datacenters as necessary.
|
datacenters as necessary.
|
||||||
|
|
||||||
For a node lookup, the only records returned are A records containing
|
For a node lookup, the only records returned are A and AAAA records
|
||||||
the IP address of the node.
|
containing the IP address, and TXT records containing the
|
||||||
|
`node_meta` values of the node.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ dig @127.0.0.1 -p 8600 foo.node.consul ANY
|
$ dig @127.0.0.1 -p 8600 foo.node.consul ANY
|
||||||
|
@ -76,11 +77,19 @@ $ dig @127.0.0.1 -p 8600 foo.node.consul ANY
|
||||||
|
|
||||||
;; ANSWER SECTION:
|
;; ANSWER SECTION:
|
||||||
foo.node.consul. 0 IN A 10.1.10.12
|
foo.node.consul. 0 IN A 10.1.10.12
|
||||||
|
foo.node.consul. 0 IN TXT "meta_key=meta_value"
|
||||||
|
foo.node.consul. 0 IN TXT "value only"
|
||||||
|
|
||||||
|
|
||||||
;; AUTHORITY SECTION:
|
;; AUTHORITY SECTION:
|
||||||
consul. 0 IN SOA ns.consul. postmaster.consul. 1392836399 3600 600 86400 0
|
consul. 0 IN SOA ns.consul. postmaster.consul. 1392836399 3600 600 86400 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
By default the TXT records value will match the node's metadata key-value
|
||||||
|
pairs according to [RFC1464](https://www.ietf.org/rfc/rfc1464.txt).
|
||||||
|
Alternatively, the TXT record will only include the node's metadata value when the
|
||||||
|
node's metadata key starts with `rfc1035-`.
|
||||||
|
|
||||||
## Service Lookups
|
## Service Lookups
|
||||||
|
|
||||||
A service lookup is used to query for service providers. Service queries support
|
A service lookup is used to query for service providers. Service queries support
|
||||||
|
|
|
@ -432,6 +432,8 @@ will exit with an error at startup.
|
||||||
- Metadata keys must contain only alphanumeric, `-`, and `_` characters.
|
- Metadata keys must contain only alphanumeric, `-`, and `_` characters.
|
||||||
- Metadata keys must not begin with the `consul-` prefix; that is reserved for internal use by Consul.
|
- Metadata keys must not begin with the `consul-` prefix; that is reserved for internal use by Consul.
|
||||||
- Metadata values must be between 0 and 512 (inclusive) characters in length.
|
- Metadata values must be between 0 and 512 (inclusive) characters in length.
|
||||||
|
- Metadata values for keys begining with `rfc1035-` are encoded verbatim in DNS TXT requests, otherwise
|
||||||
|
the metadata kv-pair is encoded according [RFC1464](https://www.ietf.org/rfc/rfc1464.txt).
|
||||||
|
|
||||||
* <a name="_pid_file"></a><a href="#_pid_file">`-pid-file`</a> - This flag provides the file
|
* <a name="_pid_file"></a><a href="#_pid_file">`-pid-file`</a> - This flag provides the file
|
||||||
path for the agent to store its PID. This is useful for sending signals (for example, `SIGINT`
|
path for the agent to store its PID. This is useful for sending signals (for example, `SIGINT`
|
||||||
|
|
Loading…
Reference in New Issue