package dhcp4 import ( "encoding/binary" "fmt" "strconv" "strings" "time" ) // Field is a message's header field or option, in words: for the activity // log's details. type Field struct { // Code is the option's number (0 for a header field). Code int `json:"code,omitempty"` Name string `json:"name"` Value string `json:"value"` } // protocol are the options the server and clients set themselves, which // the catalog leaves out because they can't be configured. var protocol = map[byte]Def{ 1: {Name: "subnet-mask", Type: TypeIP}, 12: {Name: "host-name", Type: TypeString}, 50: {Name: "requested-address", Type: TypeIP}, 51: {Name: "lease-time", Type: "seconds"}, 52: {Name: "option-overload", Type: TypeU8}, 53: {Name: "message-type", Type: "message-type"}, 54: {Name: "server-identifier", Type: TypeIP}, 55: {Name: "parameter-request-list", Type: "codes"}, 56: {Name: "message", Type: TypeString}, 57: {Name: "max-message-size", Type: TypeU16}, 58: {Name: "renewal-time", Type: "seconds"}, 59: {Name: "rebinding-time", Type: "seconds"}, 61: {Name: "client-identifier", Type: "client-id"}, 77: {Name: "user-class", Type: TypeHex}, 81: {Name: "client-fqdn", Type: "fqdn"}, 82: {Name: "relay-agent-information", Type: TypeHex}, 116: {Name: "auto-configure", Type: TypeU8}, 118: {Name: "subnet-selection", Type: TypeIP}, } // optionDef is an option's definition for display. func optionDef(code byte) Def { if d, ok := protocol[code]; ok { d.Code = code return d } return ByCode(code) } // OptionName is an option's name: "routers", "parameter-request-list", // "option-224". func OptionName(code byte) string { return optionDef(code).Name } // DescribeOption is an option's value in words. func DescribeOption(code byte, b []byte) string { d := optionDef(code) switch d.Type { case "seconds": if len(b) == 4 { s := binary.BigEndian.Uint32(b) return fmt.Sprintf("%d s (%s)", s, shortDuration(time.Duration(s)*time.Second)) } case "message-type": if len(b) == 1 { return MessageType(b[0]).String() } case "codes": names := make([]string, len(b)) for i, c := range b { names[i] = OptionName(c) + " (" + strconv.Itoa(int(c)) + ")" } return strings.Join(names, ", ") case "client-id": hexed := Decode(TypeHex, b) switch { case len(b) == 7 && b[0] == 1: return hexed + " (type 1: the MAC address " + Decode(TypeHex, b[1:]) + ")" case len(b) > 5 && b[0] == 255: return hexed + " (RFC 4361: IAID " + Decode(TypeHex, b[1:5]) + " and a DUID)" } return hexed case "fqdn": // Flags, two obsolete RCODEs, then the name: in DNS wire format // when the E flag is set, else as text. if len(b) >= 3 { name := string(b[3:]) if b[0]&0x04 != 0 { if names, err := decodeNames(b[3:]); err == nil && len(names) > 0 { name = names[0] } } var flags []string if b[0]&0x01 != 0 { flags = append(flags, "server updates the A record") } if b[0]&0x08 != 0 { flags = append(flags, "no DNS updates") } if len(flags) == 0 { flags = append(flags, "client updates the A record") } return name + " (" + strings.Join(flags, ", ") + ")" } default: return Decode(d.Type, b) } return Decode(TypeHex, b) } // shortDuration writes 43200s as "12h", 90s as "1m30s", a week as "7d". func shortDuration(d time.Duration) string { if d == 0 { return "0s" } var out string for _, u := range []struct { d time.Duration name string }{{24 * time.Hour, "d"}, {time.Hour, "h"}, {time.Minute, "m"}, {time.Second, "s"}} { if n := d / u.d; n > 0 { out += strconv.Itoa(int(n)) + u.name d -= n * u.d } } return out } // Describe lists a message's header fields that are set, and its options // in the order they are in the message. func (p *Packet) Describe() (fields, options []Field) { add := func(name, value string) { fields = append(fields, Field{Name: name, Value: value}) } add("transaction ID", fmt.Sprintf("0x%08x", p.XID)) add("client hardware address", p.MAC().String()) if !IsZero(p.CIAddr) { add("client address (ciaddr)", p.CIAddr.String()) } if !IsZero(p.YIAddr) { add("your address (yiaddr)", p.YIAddr.String()) } if !IsZero(p.SIAddr) { add("next server (siaddr)", p.SIAddr.String()) } if !IsZero(p.GIAddr) { add("relay agent (giaddr)", p.GIAddr.String()) } if p.Broadcast() { add("flags", "broadcast (the client wants replies broadcast)") } if p.Secs != 0 { add("seconds since the client began", strconv.Itoa(int(p.Secs))) } if p.Hops != 0 { add("hops", strconv.Itoa(int(p.Hops))) } if p.SName != "" { add("server name (sname)", p.SName) } if p.File != "" { add("boot file (file)", p.File) } for _, o := range p.Options { options = append(options, Field{Code: int(o.Code), Name: OptionName(o.Code), Value: DescribeOption(o.Code, o.Data)}) } return fields, options }