Exemple #1
0
    def init_response(bundle: TransactionBundle):
        """
        Create the message object in bundle.response

        :param bundle: The transaction bundle
        """
        # Start building the response
        if isinstance(bundle.request, SolicitMessage):
            bundle.response = AdvertiseMessage(bundle.request.transaction_id)

        elif isinstance(bundle.request, (RequestMessage, RenewMessage, RebindMessage,
                                         ReleaseMessage, DeclineMessage, InformationRequestMessage)):
            bundle.response = ReplyMessage(bundle.request.transaction_id)

        elif isinstance(bundle.request, ConfirmMessage):
            # Receipt of Confirm Messages: If [...] there were no addresses in any of the IAs sent by the client, the
            # server MUST NOT send a reply to the client.
            for option in bundle.request.get_options_of_type((IANAOption, IATAOption, IAPDOption)):
                if option.get_options_of_type((IAAddressOption, IAPrefixOption)):
                    # Found an address or prefix option
                    break
            else:
                # Not found: ignore request
                raise CannotRespondError("No IAs present in confirm reply")

            bundle.response = ReplyMessage(bundle.request.transaction_id)

        else:
            raise CannotRespondError("Do not know how to reply to {}".format(type(bundle.request).__name__))

        # Build the plain chain of relay reply messages
        bundle.create_outgoing_relay_messages()
Exemple #2
0
    def post(self, bundle: TransactionBundle):
        """
        Upgrade the response from a AdvertiseMessage to a ReplyMessage if appropriate
        :param bundle: The transaction bundle
        """
        # Does this transaction even allow rapid commit?
        if not bundle.allow_rapid_commit:
            return

        # We only look for SolicitMessages that have a RapidCommitOption
        if not isinstance(
                bundle.request, SolicitMessage
        ) or not bundle.request.get_option_of_type(RapidCommitOption):
            return

        # And only if the current response is an AdvertiseMessage
        if not isinstance(bundle.response, AdvertiseMessage):
            return

        # Ok, this looks promising, do extra checks if requested
        if not self.rapid_commit_rejections:
            # Ok, we don't want to rapid-commit rejections. Check for them.
            if bundle.get_unhandled_options(
                (IANAOption, IATAOption, IAPDOption)):
                # Unhandled options. We are post-processing, so they are not going to be answered anymore
                return

            # Did we already refuse anything?
            ia_options = [
                option for option in bundle.response.options
                if isinstance(option, (IANAOption, IATAOption))
            ]
            for option in ia_options:
                status = option.get_option_of_type(StatusCodeOption)
                if status and status.status_code == STATUS_NO_ADDRS_AVAIL:
                    # Refusal: don't do anything
                    return

            iapd_options = [
                option for option in bundle.response.options
                if isinstance(option, IAPDOption)
            ]
            for option in iapd_options:
                status = option.get_option_of_type(StatusCodeOption)
                if status and status.status_code == STATUS_NO_PREFIX_AVAIL:
                    # Refusal: don't do anything
                    return

        # It seems the request and response qualify: upgrade to ReplyMessage
        bundle.response = ReplyMessage(bundle.response.transaction_id,
                                       [RapidCommitOption()] +
                                       bundle.response.options)
Exemple #3
0
    def construct_plain_status_reply(self, bundle: TransactionBundle,
                                     option: StatusCodeOption) -> ReplyMessage:
        """
        Construct a reply message signalling a status code to the client.

        :param bundle: The transaction bundle containing the incoming request
        :param option: The status code option to include in the reply
        :return: A reply with only the bare necessities and a status code
        """
        return ReplyMessage(
            bundle.request.transaction_id,
            options=[
                bundle.request.get_option_of_type(ClientIdOption),
                ServerIdOption(duid=self.server_id), option
            ])
Exemple #4
0
    def construct_use_multicast_reply(self, bundle: TransactionBundle) -> ReplyMessage:
        """
        Construct a message signalling to the client that they should have used multicast.

        :param bundle: The transaction bundle containing the incoming request
        :return: The proper answer to tell a client to use multicast
        """
        # Make sure we only tell this to requests that came in over unicast
        if bundle.received_over_multicast:
            logger.error("Not telling client to use multicast, they already did...")
            return None

        return ReplyMessage(bundle.request.transaction_id, options=[
            bundle.request.get_option_of_type(ClientIdOption),
            ServerIdOption(duid=self.server_id),
            StatusCodeOption(STATUS_USE_MULTICAST, "You cannot send requests directly to this server, "
                                                   "please use the proper multicast addresses")
        ])
reply_message = ReplyMessage(
    transaction_id=bytes.fromhex('f350d6'),
    options=[
        IANAOption(iaid=bytes.fromhex('c43cb2f1'),
                   options=[
                       IAAddressOption(
                           address=IPv6Address('2001:db8:ffff:1:c::e09c'),
                           preferred_lifetime=375,
                           valid_lifetime=600),
                   ]),
        IAPDOption(iaid=bytes.fromhex('c43cb2f1'),
                   options=[
                       IAPrefixOption(
                           prefix=IPv6Network('2001:db8:ffcc:fe00::/56'),
                           preferred_lifetime=375,
                           valid_lifetime=600),
                   ]),
        ClientIdOption(duid=LinkLayerDUID(hardware_type=1,
                                          link_layer_address=bytes.fromhex(
                                              '3431c43cb2f1'))),
        ServerIdOption(duid=LinkLayerTimeDUID(hardware_type=1,
                                              time=488458703,
                                              link_layer_address=bytes.fromhex(
                                                  '00137265ca42'))),
        ReconfigureAcceptOption(),
        RecursiveNameServersOption(
            dns_servers=[IPv6Address('2001:4860:4860::8888')]),
    ],
)
Exemple #6
0
    most one OPTION_S46_V4V6BIND option and at least one OPTION_S46_BR
    option.
    """

    option_type = OPTION_S46_CONT_LW


# Register where these options may occur
SolicitMessage.add_may_contain(S46ContainerOption)
AdvertiseMessage.add_may_contain(S46ContainerOption)
RequestMessage.add_may_contain(S46ContainerOption)
ConfirmMessage.add_may_contain(S46ContainerOption)
RenewMessage.add_may_contain(S46ContainerOption)
RebindMessage.add_may_contain(S46ContainerOption)
ReleaseMessage.add_may_contain(S46ContainerOption)
ReplyMessage.add_may_contain(S46ContainerOption)

S46RuleOption.add_may_contain(S46PortParametersOption)

S46V4V6BindingOption.add_may_contain(S46PortParametersOption)

S46MapEContainerOption.add_may_contain(S46RuleOption, min_occurrence=1)
S46MapEContainerOption.add_may_contain(S46BROption, min_occurrence=1)
S46MapEContainerOption.add_may_contain(S46PortParametersOption)

S46MapTContainerOption.add_may_contain(S46RuleOption, min_occurrence=1)
S46MapTContainerOption.add_may_contain(S46DMROption, min_occurrence=1, max_occurrence=1)
S46MapTContainerOption.add_may_contain(S46PortParametersOption)

S46LWContainerOption.add_may_contain(S46V4V6BindingOption, min_occurrence=0, max_occurrence=1)
S46LWContainerOption.add_may_contain(S46BROption, min_occurrence=1)
Exemple #7
0
 def test_wrong_way(self):
     with self.assertLogs() as cm:
         TransactionBundle(ReplyMessage(), False)
     self.assertEqual(len(cm.output), 1)
     self.assertRegex(cm.output[0], 'server should not receive')
Exemple #8
0
        return my_offset

    def save(self) -> bytes:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        self.validate()

        options_buffer = bytearray()
        for option in self.options:
            options_buffer.extend(option.save())

        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type, len(options_buffer)))
        buffer.extend(options_buffer)
        return buffer


# Register where these options may occur
SolicitMessage.add_may_contain(NTPServersOption)
AdvertiseMessage.add_may_contain(NTPServersOption)
RequestMessage.add_may_contain(NTPServersOption)
RenewMessage.add_may_contain(NTPServersOption)
RebindMessage.add_may_contain(NTPServersOption)
InformationRequestMessage.add_may_contain(NTPServersOption)
ReplyMessage.add_may_contain(NTPServersOption)

NTPServersOption.add_may_contain(NTPSubOption, 1)
Exemple #9
0
        :param length: The amount of data we are allowed to read from the buffer
        :return: The number of bytes used from the buffer
        """
        my_offset, option_len = self.parse_option_header(
            buffer, offset, length)

        if option_len != 4:
            raise ValueError('INF_MAX_RT Options must have length 4')

        self.inf_max_rt = unpack_from('!I', buffer,
                                      offset=offset + my_offset)[0]
        my_offset += 4

        return my_offset

    def save(self) -> Union[bytes, bytearray]:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        return pack('!HHI', self.option_type, 4, self.inf_max_rt)


# Register where these options may occur
AdvertiseMessage.add_may_contain(SolMaxRTOption, 0, 1)
ReplyMessage.add_may_contain(SolMaxRTOption, 0, 1)

AdvertiseMessage.add_may_contain(InfMaxRTOption, 0, 1)
ReplyMessage.add_may_contain(InfMaxRTOption, 0, 1)
Exemple #10
0
        :param offset: The offset in the buffer where to start reading
        :param length: The amount of data we are allowed to read from the buffer
        :return: The number of bytes used from the buffer
        """
        my_offset, option_len = self.parse_option_header(buffer, offset, length)

        if option_len != 4:
            raise ValueError('INF_MAX_RT Options must have length 4')

        self.inf_max_rt = unpack_from('!I', buffer, offset=offset + my_offset)
        my_offset += 4

        self.validate()

        return my_offset

    def save(self) -> bytes:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        self.validate()
        return pack('!HHI', self.option_type, 4, self.inf_max_rt)


AdvertiseMessage.add_may_contain(SolMaxRTOption)
AdvertiseMessage.add_may_contain(InfMaxRTOption)
ReplyMessage.add_may_contain(SolMaxRTOption)
ReplyMessage.add_may_contain(InfMaxRTOption)
Exemple #11
0
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type,
                           len(self.sip_servers) * 16))
        for address in self.sip_servers:
            buffer.extend(address.packed)

        return buffer


# Register where these options may occur
SolicitMessage.add_may_contain(SIPServersDomainNameListOption)
AdvertiseMessage.add_may_contain(SIPServersDomainNameListOption)
RequestMessage.add_may_contain(SIPServersDomainNameListOption)
RenewMessage.add_may_contain(SIPServersDomainNameListOption)
RebindMessage.add_may_contain(SIPServersDomainNameListOption)
InformationRequestMessage.add_may_contain(SIPServersDomainNameListOption)
ReplyMessage.add_may_contain(SIPServersDomainNameListOption)

SolicitMessage.add_may_contain(SIPServersAddressListOption)
AdvertiseMessage.add_may_contain(SIPServersAddressListOption)
RequestMessage.add_may_contain(SIPServersAddressListOption)
RenewMessage.add_may_contain(SIPServersAddressListOption)
RebindMessage.add_may_contain(SIPServersAddressListOption)
InformationRequestMessage.add_may_contain(SIPServersAddressListOption)
ReplyMessage.add_may_contain(SIPServersAddressListOption)
Exemple #12
0
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        self.validate()

        domain_buffer = encode_domain_list(self.search_list)

        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type, len(domain_buffer)))
        buffer.extend(domain_buffer)
        return buffer


SolicitMessage.add_may_contain(RecursiveNameServersOption, 0, 1)
AdvertiseMessage.add_may_contain(RecursiveNameServersOption, 0, 1)
RequestMessage.add_may_contain(RecursiveNameServersOption, 0, 1)
RenewMessage.add_may_contain(RecursiveNameServersOption, 0, 1)
RebindMessage.add_may_contain(RecursiveNameServersOption, 0, 1)
InformationRequestMessage.add_may_contain(RecursiveNameServersOption, 0, 1)
ReplyMessage.add_may_contain(RecursiveNameServersOption, 0, 1)

SolicitMessage.add_may_contain(DomainSearchListOption, 0, 1)
AdvertiseMessage.add_may_contain(DomainSearchListOption, 0, 1)
RequestMessage.add_may_contain(DomainSearchListOption, 0, 1)
RenewMessage.add_may_contain(DomainSearchListOption, 0, 1)
RebindMessage.add_may_contain(DomainSearchListOption, 0, 1)
InformationRequestMessage.add_may_contain(DomainSearchListOption, 0, 1)
ReplyMessage.add_may_contain(DomainSearchListOption, 0, 1)
Exemple #13
0
    def save(self) -> Union[bytes, bytearray]:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        domain_buffer = encode_domain_list(self.search_list)

        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type, len(domain_buffer)))
        buffer.extend(domain_buffer)
        return buffer


# Register where these options may occur
SolicitMessage.add_may_contain(RecursiveNameServersOption)
AdvertiseMessage.add_may_contain(RecursiveNameServersOption)
RequestMessage.add_may_contain(RecursiveNameServersOption)
RenewMessage.add_may_contain(RecursiveNameServersOption)
RebindMessage.add_may_contain(RecursiveNameServersOption)
InformationRequestMessage.add_may_contain(RecursiveNameServersOption)
ReplyMessage.add_may_contain(RecursiveNameServersOption)

SolicitMessage.add_may_contain(DomainSearchListOption)
AdvertiseMessage.add_may_contain(DomainSearchListOption)
RequestMessage.add_may_contain(DomainSearchListOption)
RenewMessage.add_may_contain(DomainSearchListOption)
RebindMessage.add_may_contain(DomainSearchListOption)
InformationRequestMessage.add_may_contain(DomainSearchListOption)
ReplyMessage.add_may_contain(DomainSearchListOption)
        :param buffer: The buffer to read data from
        :param offset: The offset in the buffer where to start reading
        :param length: The amount of data we are allowed to read from the buffer
        :return: The number of bytes used from the buffer
        """
        my_offset, option_len = self.parse_option_header(buffer, offset, length)

        if option_len != 4:
            raise ValueError("SOL_MAX_RT Options must have length 4")

        self.sol_max_rt = unpack_from("!I", buffer, offset=offset + my_offset)[0]
        my_offset += 4

        self.validate()

        return my_offset

    def save(self) -> bytes:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        self.validate()
        return pack("!HHI", self.option_type, 4, self.sol_max_rt)


AdvertiseMessage.add_may_contain(SolMaxRTTechnicolorOption)
ReplyMessage.add_may_contain(SolMaxRTTechnicolorOption)
Exemple #15
0
        max_offset = option_len + header_offset  # The option_len field counts bytes *after* the header fields
        while max_offset > my_offset:
            address = IPv6Address(buffer[offset + my_offset:offset + my_offset + 16])
            self.sntp_servers.append(address)
            my_offset += 16

        return my_offset

    def save(self) -> Union[bytes, bytearray]:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type, len(self.sntp_servers) * 16))
        for address in self.sntp_servers:
            buffer.extend(address.packed)

        return buffer


# Register where these options may occur
SolicitMessage.add_may_contain(SNTPServersOption)
AdvertiseMessage.add_may_contain(SNTPServersOption)
RequestMessage.add_may_contain(SNTPServersOption)
RenewMessage.add_may_contain(SNTPServersOption)
RebindMessage.add_may_contain(SNTPServersOption)
InformationRequestMessage.add_may_contain(SNTPServersOption)
ReplyMessage.add_may_contain(SNTPServersOption)
        :return: The buffer with the data from this element
        """
        options_buffer = bytearray()
        for option in self.options:
            options_buffer.extend(option.save())

        buffer = bytearray()
        buffer.extend(
            pack('!HHIIB', self.option_type,
                 len(options_buffer) + 25, self.preferred_lifetime,
                 self.valid_lifetime, self.prefix.prefixlen))
        buffer.extend(self.prefix.network_address.packed)
        buffer.extend(options_buffer)
        return buffer


# Register where these options may occur
SolicitMessage.add_may_contain(IAPDOption)
AdvertiseMessage.add_may_contain(IAPDOption)
RequestMessage.add_may_contain(IAPDOption)
ConfirmMessage.add_may_contain(IAPDOption)
RenewMessage.add_may_contain(IAPDOption)
RebindMessage.add_may_contain(IAPDOption)
ReleaseMessage.add_may_contain(IAPDOption)
ReplyMessage.add_may_contain(IAPDOption)

IAPDOption.add_may_contain(IAPrefixOption)
IAPDOption.add_may_contain(StatusCodeOption, 0, 1)

IAPrefixOption.add_may_contain(StatusCodeOption, 0, 1)
Exemple #17
0
    most one OPTION_S46_V4V6BIND option and at least one OPTION_S46_BR
    option.
    """

    option_type = OPTION_S46_CONT_LW


# Register where these options may occur
SolicitMessage.add_may_contain(S46ContainerOption)
AdvertiseMessage.add_may_contain(S46ContainerOption)
RequestMessage.add_may_contain(S46ContainerOption)
ConfirmMessage.add_may_contain(S46ContainerOption)
RenewMessage.add_may_contain(S46ContainerOption)
RebindMessage.add_may_contain(S46ContainerOption)
ReleaseMessage.add_may_contain(S46ContainerOption)
ReplyMessage.add_may_contain(S46ContainerOption)

S46RuleOption.add_may_contain(S46PortParametersOption)

S46V4V6BindingOption.add_may_contain(S46PortParametersOption)

S46MapEContainerOption.add_may_contain(S46RuleOption, min_occurrence=1)
S46MapEContainerOption.add_may_contain(S46BROption, min_occurrence=1)
S46MapEContainerOption.add_may_contain(S46PortParametersOption)

S46MapTContainerOption.add_may_contain(S46RuleOption, min_occurrence=1)
S46MapTContainerOption.add_may_contain(S46DMROption,
                                       min_occurrence=1,
                                       max_occurrence=1)
S46MapTContainerOption.add_may_contain(S46PortParametersOption)
        max_offset = option_len + header_offset  # The option_len field counts bytes *after* the header fields
        domain_name_len, self.domain_name = parse_domain_bytes(buffer, offset=offset + my_offset, length=option_len - 1,
                                                               allow_relative=True)
        my_offset += domain_name_len

        if my_offset != max_offset:
            raise ValueError('Option length does not match the combined length of the included domain name')

        return my_offset

    def save(self) -> Union[bytes, bytearray]:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        domain_buffer = encode_domain(self.domain_name)

        buffer = bytearray()
        buffer.extend(pack('!HHB', self.option_type, 1 + len(domain_buffer), self.flags))
        buffer.extend(domain_buffer)
        return buffer


SolicitMessage.add_may_contain(ClientFQDNOption, 0, 1)
AdvertiseMessage.add_may_contain(ClientFQDNOption, 0, 1)
RequestMessage.add_may_contain(ClientFQDNOption, 0, 1)
RenewMessage.add_may_contain(ClientFQDNOption, 0, 1)
RebindMessage.add_may_contain(ClientFQDNOption, 0, 1)
ReplyMessage.add_may_contain(ClientFQDNOption, 0, 1)
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        self.validate()

        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type, len(self.sip_servers) * 16))
        for address in self.sip_servers:
            buffer.extend(address.packed)

        return buffer


# Register where these options may occur
SolicitMessage.add_may_contain(SIPServersDomainNameListOption)
AdvertiseMessage.add_may_contain(SIPServersDomainNameListOption)
RequestMessage.add_may_contain(SIPServersDomainNameListOption)
RenewMessage.add_may_contain(SIPServersDomainNameListOption)
RebindMessage.add_may_contain(SIPServersDomainNameListOption)
InformationRequestMessage.add_may_contain(SIPServersDomainNameListOption)
ReplyMessage.add_may_contain(SIPServersDomainNameListOption)

SolicitMessage.add_may_contain(SIPServersAddressListOption)
AdvertiseMessage.add_may_contain(SIPServersAddressListOption)
RequestMessage.add_may_contain(SIPServersAddressListOption)
RenewMessage.add_may_contain(SIPServersAddressListOption)
RebindMessage.add_may_contain(SIPServersAddressListOption)
InformationRequestMessage.add_may_contain(SIPServersAddressListOption)
ReplyMessage.add_may_contain(SIPServersAddressListOption)
Exemple #20
0
        :return: The buffer with the data from this element
        """
        self.validate()

        options_buffer = bytearray()
        for option in self.options:
            options_buffer.extend(option.save())

        buffer = bytearray()
        buffer.extend(pack('!HHIIB', self.option_type, len(options_buffer) + 25,
                           self.preferred_lifetime, self.valid_lifetime, self.prefix.prefixlen))
        buffer.extend(self.prefix.network_address.packed)
        buffer.extend(options_buffer)
        return buffer


# Register where these options may occur
SolicitMessage.add_may_contain(IAPDOption)
AdvertiseMessage.add_may_contain(IAPDOption)
RequestMessage.add_may_contain(IAPDOption)
RenewMessage.add_may_contain(IAPDOption)
RebindMessage.add_may_contain(IAPDOption)
ReleaseMessage.add_may_contain(IAPDOption)
ReplyMessage.add_may_contain(IAPDOption)

IAPDOption.add_may_contain(IAPrefixOption)
IAPDOption.add_may_contain(StatusCodeOption, 0, 1)

IAPrefixOption.add_may_contain(StatusCodeOption, 0, 1)
Exemple #21
0
        my_offset += name_len

        if my_offset != max_offset:
            raise ValueError(
                'Option length does not match the length of the included fqdn')

        return my_offset

    def save(self) -> Union[bytes, bytearray]:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        fqdn_buffer = encode_domain(self.fqdn)

        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type, len(fqdn_buffer)))
        buffer.extend(fqdn_buffer)
        return buffer


# Update the messages where this option may appear
SolicitMessage.add_may_contain(AFTRNameOption, 0, 1)
AdvertiseMessage.add_may_contain(AFTRNameOption, 0, 1)
RequestMessage.add_may_contain(AFTRNameOption, 0, 1)
RenewMessage.add_may_contain(AFTRNameOption, 0, 1)
RebindMessage.add_may_contain(AFTRNameOption, 0, 1)
InformationRequestMessage.add_may_contain(AFTRNameOption, 0, 1)
ReplyMessage.add_may_contain(AFTRNameOption, 0, 1)
Exemple #22
0
        return my_offset

    def save(self) -> Union[bytes, bytearray]:
        """
        Save the internal state of this object as a buffer.

        :return: The buffer with the data from this element
        """
        buffer = bytearray()
        buffer.extend(pack('!HH', self.option_type, len(self.timezone)))
        buffer.extend(self.timezone.encode('ascii'))
        return buffer


SolicitMessage.add_may_contain(PosixTimezoneOption, 0, 1)
AdvertiseMessage.add_may_contain(PosixTimezoneOption, 0, 1)
RequestMessage.add_may_contain(PosixTimezoneOption, 0, 1)
RenewMessage.add_may_contain(PosixTimezoneOption, 0, 1)
RebindMessage.add_may_contain(PosixTimezoneOption, 0, 1)
InformationRequestMessage.add_may_contain(PosixTimezoneOption, 0, 1)
ReplyMessage.add_may_contain(PosixTimezoneOption, 0, 1)

SolicitMessage.add_may_contain(TZDBTimezoneOption, 0, 1)
AdvertiseMessage.add_may_contain(TZDBTimezoneOption, 0, 1)
RequestMessage.add_may_contain(TZDBTimezoneOption, 0, 1)
RenewMessage.add_may_contain(TZDBTimezoneOption, 0, 1)
RebindMessage.add_may_contain(TZDBTimezoneOption, 0, 1)
InformationRequestMessage.add_may_contain(TZDBTimezoneOption, 0, 1)
ReplyMessage.add_may_contain(TZDBTimezoneOption, 0, 1)