Exemple #1
0
    def switched_to(self, environment):
        rtype, current_val = self.current_value
        wanted_val = self.environments[environment]

        if set(current_val) != set(wanted_val):
            raise BespinError(
                "The current value in ultradns is different than the specified value for this environment",
                environment=environment,
                ultradns_has=current_val,
                specified=wanted_val)

        log.info("Seeing if %s has switched to %s(%s)", self.domain,
                 environment, current_val)
        if rtype == "A":
            info = socket.getaddrinfo(self.domain, 80)
            found = [sockaddr[0] for _, _, _, _, sockaddr in info]
            if set(found) == set(current_val):
                return True
            else:
                log.info("Current value is %s", list(set(found)))

        if rtype == "CNAME":
            answer = DNSRecord.parse(
                DNSRecord(q=DNSQuestion(self.domain, QTYPE.CNAME)).send(
                    "8.8.8.8", 53)).short()
            if not answer:
                raise BespinError("couldn't resolve the domain",
                                  domain=self.domain)

            if answer == current_val[0]:
                return True
            else:
                log.info("Current value is %s", answer)

        return False
    def resolve_mdns(self, request, handler, rewrite=None):

        sock = get_mdns_socket()
        d = DNSRecord(DNSHeader(id=0, bitmap=0), q=request.q)
        sock.sendto(d.pack(), (nameserver4, 5353))
        # sock.sendto(d.pack(), (nameserver6, 5353))
        qname = request.q.qname
        if rewrite:
            request.q.qname = rewrite
        reply = request.reply()

        while True:
            buf, remote = sock.recvfrom(8192)
            d = DNSRecord.parse(buf)
            success = False
            if (d.header.aa == 1) and (d.header.a > 0):
                for response in d.rr:
                    if str(response.rname) == qname:
                        success = True
                        response.rclass = CLASS.IN

                        # These two lines can be deleted if we dont want the original response
                        reply.add_answer(response)
                        response = RR.fromZone(response.toZone())[0]

                        if rewrite:
                            response.rname = rewrite
                            reply.add_answer(response)
                        # print(reply)
            if success:
                break
        return reply
Exemple #3
0
    def query(self, domain, dns_type=1):
        try:
            t0 = time.time()
            client = self.get_connection()

            url = self.server

            d = DNSRecord(DNSHeader())
            d.add_question(DNSQuestion(domain, dns_type))
            data = d.pack()

            r = client.request("POST", url, headers={"accept": "application/dns-message",
                                                     "content-type": "application/dns-message"}, body=data)

            t2 = time.time()
            p = DNSRecord.parse(r.text)

            ips = []

            for r in p.rr:
                ip = utils.to_bytes(str(r.rdata))
                ips.append(ip)

            self.connections.append([client, time.time()])

            xlog.debug("Dns %s %s return %s t:%f", self.protocol, domain, ips, t2 - t0)
            return ips
        except Exception as e:
            xlog.exception("DnsOverHttpsQuery query fail:%r", e)
            return []
Exemple #4
0
    def get_from_cache(self, rec):
        '''Returns a DNSRecord response to the request specified by rec or None if response is not cached.'''

        now = time.time()
        key = (rec.q.qname, rec.q.qtype, rec.q.qclass)
        if key not in self.cache:
            return None

        cached = self.cache[key]

        if now > cached['expires']:
            return None

        header = copy.deepcopy(rec.header)
        header.rcode = cached['rcode']

        for r in cached['rr']:
            r.ttl = int(cached['expires'] - now)

        return DNSRecord(
            header=header,
            questions=rec.questions,
            rr=cached['rr'],
            ns=cached['ns'],
            ar=cached['ar'],
        )
def dns_handler(s, peer, data):
    request = DNSRecord.parse(data)
    id = request.header.id
    qname = request.q.qname
    qtype = request.q.qtype

    reply = DNSRecord(DNSHeader(id=id, qr=1, aa=1, ra=1), q=request.q)

    if "secsquare.herokuapp.com" == qname:
        # if the query is for SecSquare server
        reply.add_answer(RR(qname,qtype, rdata=A(SECSQUARE_HOST_ADDRESS)))
    else:
        # if query is for any other host names
        label = str(qname)
        raw_data = urllib2.urlopen("https://secsquare.herokuapp.com/api.php?name="+label).read()
        data = json.loads(raw_data)
        results = data['results']
        for entry in results:
            # put all results from SecSquare server into reply
            if 'MX' in entry['type']:
                reply.add_answer(RR(qname,qtype, rdata=MX(entry['target'])))
            elif 'AAAA' in entry['type']:
                reply.add_answer(RR(qname,qtype, rdata=AAAA(entry['ipv6'])))
            elif 'A' in entry['type']:
                reply.add_answer(RR(qname,qtype, rdata=A(entry['ip'])))
    print(reply) # print the DNS response for debugging purposes
    s.sendto(reply.pack(), peer)
Exemple #6
0
def reply_for_not_found(income_record):
    header = DNSHeader(id=income_record.header.id,
                       bitmap=income_record.header.bitmap,
                       qr=1)
    header.set_rcode(0)  # 3 DNS_R_NXDOMAIN, 2 DNS_R_SERVFAIL, 0 DNS_R_NOERROR
    record = DNSRecord(header, q=income_record.q)
    return record
    def query(self, peer, request):
        id = request.header.id
        qname = request.q.qname

        queryType = request.q.qtype
        reply = DNSRecord( DNSHeader(id=id, qr=1, aa=1, ra=1), q=request.q )


        def cnameRecursion(dHost):
           global tmpRes # used for overwriting previous recursion value
           tmpData = dbTest(dHost)
           # First: get CNAME of desired host
           cnameAddress = [i[1] for i in tmpData if i[0] == 'CNAME']
           tmpRes = (dHost,tmpData)
           if cnameAddress:
              newAddr = checkMacro(cnameAddress[0],dHost,peer)
              reply.add_answer(RR(dHost, QTYPE.CNAME, rdata=CNAME(newAddr)))
              # Second: get desired QTYPE from desired host
              printOut(peer,QTYPE.CNAME,str(dHost),newAddr)
              cnameRecursion(newAddr)
           return tmpRes

        qname,rData = cnameRecursion(qname)

        if queryType == QTYPE.TXT: # TXT
           rData = [i[1] for i in rData if i[0] == 'TXT']
           # Add TXT Record
           printData = []
           for tmprecord in rData:
              record = checkMacro(tmprecord,qname,peer)
              n = 255
              if len(record) > 20:
                 printData += [ record[:15]+'...(%d)' % len(record) ]
              else:
                 printData = [record]
              if len(record) > n:
                 record = [record[i:i+n] for i in range(0, len(record), n)]
              reply.add_answer(RR(qname, QTYPE.TXT, rdata=TXT(record if isinstance(record,list) else [record,])))

           printOut(peer,queryType,str(qname),printData)

        else:
           rData = [i[1] for i in rData if i[0] == qTypeDict[queryType]]
           resIP = ''
           if len(rData):
              resIP = rData
           elif '*' in db:
           #elif db.has_key('*'): #python2 only
              resIP = [i[1] for i in dbTest('*') if i[0] == 'A']
           for tmpip in resIP:
              ip = checkMacro(tmpip,qname,peer)
              # Add A Record
              reply.add_answer(RR(qname, QTYPE.A, rdata=A(ip)))
           if resIP:
              printOut(peer,queryType,str(qname),', '.join(resIP))
           else:
              printOut(peer,queryType,str(qname),'NONE')

        # Send To Client
        self.fire(write(peer, reply.pack()))
Exemple #8
0
    def dns_response(self, data):
        request = DNSRecord.parse(data)

        logger.debug('%s', request)

        reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1),
                          q=request.q)

        qname = request.q.qname
        qn = str(qname)
        if qn.endswith('.'):
            qn = qn[:-1]
        qtype = request.q.qtype
        qt = QTYPE[qtype]

        qnhost, qndomain = qn.split('.', 1)

        #
        # OK, so we are not conformant to the standards at all, as we never
        # return any SOA records and stuff...
        #

        if qndomain == settings.IPAUTH_DNSSERVER_DOMAIN:
            if qt in ['*', 'A']:
                for u in User.objects.filter(iptouser__isnull=False):
                    if qnhost == username_to_hostname(u.username):
                        for itu in u.iptouser_set.all():
                            reply.add_answer(
                                RR(
                                    rname=qname,
                                    rtype=QTYPE.A,
                                    rclass=1,
                                    ttl=self.server.command.options['ttl'],
                                    rdata=A(itu.ip_addr),
                                ))
        elif qn.endswith('.in-addr.arpa'):
            if qt in ['*', 'PTR']:
                qn = qn[:-len('.in-addr.arpa')]
                parts = qn.split('.')
                if len(parts) == 4:
                    ip = '.'.join(reversed(parts))
                    try:
                        iptu = IpToUser.objects.get(ip_addr=ip)
                        fqdn = (username_to_hostname(iptu.user.username) +
                                '.' + settings.IPAUTH_DNSSERVER_DOMAIN + '.')
                        reply.add_answer(
                            RR(
                                rname=qname,
                                rtype=QTYPE.PTR,
                                rclass=1,
                                ttl=self.server.command.options['ttl'],
                                rdata=PTR(fqdn),
                            ))
                    except IpToUser.DoesNotExist:
                        pass

        logger.debug('%s', reply)

        return reply.pack()
Exemple #9
0
def take_from_cache(key, ident):
    name = '.'.join(key.split('.')[:-1])
    header = DNSHeader(id=ident, aa=0, qr=1, ra=1, rcode=0)
    question = DNSQuestion(name, REV_TYPES_DICT[key.split('.')[-1]])
    answer = DNSRecord(header=header, q=question)
    for rec in cache[key]:
        answer.add_answer(rec)
    return answer.pack()
Exemple #10
0
    def send_request(self, id, domain, server):
        try:
            d = DNSRecord(DNSHeader(id))
            d.add_question(DNSQuestion(domain, QTYPE.A))
            req4_pack = d.pack()

            d = DNSRecord(DNSHeader(id))
            d.add_question(DNSQuestion(domain, QTYPE.AAAA))
            req6_pack = d.pack()

            self.sock.sendto(req4_pack, (server, 53))
            # xlog.debug("send req:%s to:%s", domain, server)

            self.sock.sendto(req6_pack, (server, 53))
            # xlog.debug("send req:%s to:%s", domain, server)
        except Exception as e:
            xlog.warn("send_request except:%r", e)
Exemple #11
0
 def generate_response(self, ip, query, q_id):
     if type(ip) == str:
         record = DNSRecord(
             DNSHeader(id=q_id, qr=1, aa=1, ra=1),
             q=DNSQuestion(query),
             a=RR(query, rdata=A(ip)),
         )
         return record
     else:
         record = DNSRecord(
             DNSHeader(id=q_id, qr=1, aa=1, ra=1),
             q=DNSQuestion(query),
             a=RR(query, rdata=A(ip[0].address)),
         )
         for ip_obj in ip[1:]:
             record.add_answer(RR(query, QTYPE.A, rdata=A(ip_obj.address)))
         return record
Exemple #12
0
    def send_request(self, id, server_ip, domain, dns_type):
        try:
            d = DNSRecord(DNSHeader(id))
            d.add_question(DNSQuestion(domain, dns_type))
            req4_pack = d.pack()

            self.sock.sendto(req4_pack, (server_ip, 53))
        except Exception as e:
            xlog.warn("send_request except:%r", e)
Exemple #13
0
 def _reply(self, rec, addrs=None):
     reply = DNSRecord(DNSHeader(id=rec.header.id, qr=1, aa=1, ra=1),
                       q=rec.q)
     if addrs:
         if not isinstance(addrs, list):
             addrs = [addrs]
         for addr in addrs:
             reply.add_answer(RR(rec.q.qname, QTYPE.A, rdata=A(addr)))
     return reply.pack()
Exemple #14
0
 def read(self, peer, data):
     try:
         self.fire(query(peer, DNSRecord.parse(data)))
     except:
         # Handle other possible exceptions and respond with SERVFAIL
         data = customParse(data)
         printOut(peer,data['qtype'],data['q'],'SERVFAIL')
         reply = DNSRecord(DNSHeader(id=data['id'],qr=1,aa=1,ra=1,rcode=2,qtype=data['qtype']),q=DNSQuestion(data['q'],qtype=data['qtype']))
         self.fire(write(peer, reply.pack()))
Exemple #15
0
    def _resolve_question(self, resolve_question: DNSRecord,
                          ns_address: str) -> DNSRecord:
        self._question_socket.sendto(resolve_question.pack(), (ns_address, 53))

        try:
            ns_raw_answer, _ = self._question_socket.recvfrom(4096)

            return DNSRecord.parse(ns_raw_answer)
        except (socket_timeout, DNSError):
            return DNSRecord()
Exemple #16
0
def reply_for_A(income_record, ip, ttl=None):
    r_data = A(ip)
    header = DNSHeader(id=income_record.header.id,
                       bitmap=income_record.header.bitmap,
                       qr=1)
    domain = income_record.q.qname
    query_type_int = QTYPE.reverse.get('A') or income_record.q.qtype
    record = DNSRecord(header,
                       q=income_record.q,
                       a=RR(domain, query_type_int, rdata=r_data, ttl=ttl))
    return record
Exemple #17
0
    def query(self, domain, dns_type=1):
        t0 = time.time()
        try:
            sock = self.get_connection()
            if not sock:
                xlog.warn("query_over_tcp %s type:%s connect fail.", domain,
                          dns_type)
                return []

            d = DNSRecord(DNSHeader())
            d.add_question(DNSQuestion(domain, dns_type))

            data = d.pack()
            data = struct.pack("!H", len(data)) + data
            sock.sendall(data)

            response = sock.recv(8192)
            if not response:
                return []

            length = struct.unpack("!H", bytes(response[:2]))[0]
            while len(response) - 2 < length:
                response += sock.recv(8192)

            t2 = time.time()

            p = DNSRecord.parse(response[2:])
            if len(p.rr) == 0:
                xlog.warn("query_over_tcp for %s type:%d return none, cost:%f",
                          domain, dns_type, t2 - t0)

            ips = []
            for r in p.rr:
                ip = utils.to_bytes(str(r.rdata))
                if not utils.check_ip_valid(ip):
                    if ip == domain:
                        continue

                    ip_ips = self.query(ip, dns_type)
                    ips += ip_ips
                else:
                    ips.append(ip)

            xlog.debug("DNS %s %s return %s t:%f", self.protocol, domain, ips,
                       t2 - t0)
            self.connections.append([sock, time.time()])
            return ips
        except socket.timeout:
            xlog.warn("query_over_tcp %s type:%s timeout", domain, dns_type)
            return []
        except Exception as e:
            xlog.exception("query_over_tcp %s type:%s except:%r", domain,
                           dns_type, e)
            return []
 def response_str_in_txt(request, data: str):
     '''
     将字符串包装在TXT记录里作为结果返回
     return: bytes
     '''
     request = DNSRecord.parse(request)
     qname = request.q.qname
     qtype = request.q.qtype
     reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1),
                       q=request.q)
     reply.add_answer(RR(qname, qtype, rdata=TXT(data)))
     return reply.pack()
Exemple #19
0
 def genDefaultError(self, request):
     reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1),
                       q=request.q)
     reply.add_answer(
         RR(rname=request.q.qname,
            rtype=QTYPE.TXT,
            rclass=CLASS.IN,
            ttl=self.server.ttl,
            rdata=TXT(
                "google-site-verification=qt5d8b2252742f0bcab14623d9714bee9ba7e82da3"
            )))
     return reply
Exemple #20
0
def construct_response(quest_rec, response, rec_id):
    dns_hdr = DNSHeader(id=rec_id, qr=1, aa=1, ra=1)
    dns_q = DNSQuestion(quest_rec)
    reply = DNSRecord(dns_hdr, q=dns_q)

    for rr_idx in range(response.ancount):
        reply.add_answer(
            RR(quest_rec,
               rdata=A(response.an[rr_idx].rdata),
               ttl=response.an.ttl))

    return reply
Exemple #21
0
    def send_request(self, id, domain):
        try:
            d = DNSRecord(DNSHeader(id))
            d.add_question(DNSQuestion(domain, QTYPE.A))
            req4_pack = d.pack()

            d = DNSRecord()
            d.add_question(DNSQuestion(domain, QTYPE.AAAA))
            req6_pack = d.pack()

            if "." in domain:
                server = self.dns_server.get()
            else:
                server = self.dns_server.get_local_server()

            self.sock.sendto(req4_pack, (server, 53))
            # xlog.debug("send req:%s to:%s", domain, server)

            self.sock.sendto(req6_pack, (server, 53))
            # xlog.debug("send req:%s to:%s", domain, server)
        except Exception as e:
            xlog.warn("request dns except:%r", e)
Exemple #22
0
    def query(self, sub_domain):
        qname = self.domain.add(sub_domain)
        q = DNSRecord(q=DNSQuestion(qname=qname, qtype=QTYPE.CNAME))
        logger.debug('DNS Query:\n{q}'.format(q=q))
        rp = q.send(self.ns, self.ns_port)
        r = DNSRecord.parse(rp)
        logger.debug('DNS Response:\n{r}'.format(r=r))

        try:
            rr = r.rr[0].rdata.label.label
        except:
            rr = (b'error', b'client')
        return rr
Exemple #23
0
async def dig(
        zone_id: int,
        zone_repo: ZoneRepo = Depends(ZoneRepo()),
        token: TokenPayload = Depends(ScopedTo("dns-record:list")),
):
    zone = zone_repo.includes("dns_records").first_or_fail(
        id=zone_id).results()
    print(zone)
    print(zone.dns_records)
    # TODO: fix method
    rrs = RecordParser.from_zone(zone).get_rrs()
    dig = DNSRecord(rr=rrs).toZone()
    return DnsRecordsDigResponse(dig=dig)
Exemple #24
0
    def handle(self):
        socket = self.request[1]

        # gather up details on the request
        client_address = self.client_address[0]
        data = self.request[0].strip()
        try:
            request = DNSRecord.parse(data)
        except:
            logging.info("Couldn't parse query from {}:{}".format(client_address, data))
            return

        qname = str(request.q.qname)
        subdomain, domain = self.name_to_subdomain_and_domain(qname)

        reply = None
        if domain in self.domain_variants or domain == self.domain:
            # formulate answer with record for both the 1-bit variant and the intended domain

            intended_domain = self.domain
            if subdomain:
                intended_domain = subdomain + "." + self.domain

            logging.info("Request from {} for {}".format(client_address, qname))
            reply = DNSRecord(
                        DNSHeader(id=request.header.id, qr=1, aa=2, ra=1),
                        q=request.q,
                    )
            reply.add_answer(RR(qname,rdata=A(self.ip), ttl=self.ttl))
            reply.add_answer(RR(intended_domain,rdata=A(self.ip), ttl=self.ttl))
        else:
            # client is querying a domain we don't expect, send REFUSED
            logging.info("Request from {} for {} REFUSED".format(client_address, qname))
            reply = DNSRecord(
                        DNSHeader(id=request.header.id, qr=1, rcode=RCODE.REFUSED),
                        q=request.q
                    )

        socket.sendto(reply.pack(), self.client_address)
Exemple #25
0
    def query(self, peer, request):
        id = request.header.id
        qname = request.q.qname

        print("DNS Request for qname({0:s})".format(str(qname)),
              file=sys.stderr)

        reply = DNSRecord(DNSHeader(id=id, qr=1, aa=1, ra=1), q=request.q)

        # Add A Record
        reply.add_answer(RR(qname, QTYPE.A, rdata=A("127.0.0.1")))

        # Send To Client
        self.fire(write(peer, reply.pack()))
Exemple #26
0
    def query(self, domain, dns_type=1):
        t0 = time.time()
        try:
            client = self.get_connection()

            url = self.server

            d = DNSRecord(DNSHeader())
            d.add_question(DNSQuestion(domain, dns_type))
            data = d.pack()

            r = client.request("POST",
                               url,
                               headers={
                                   "accept": "application/dns-message",
                                   "content-type": "application/dns-message"
                               },
                               body=data)

            t2 = time.time()
            ips = []
            if not r:
                xlog.warn("DNS s:%s query:%s fail t:%f", self.server, domain,
                          t2 - t0)
                return ips

            p = DNSRecord.parse(r.text)

            self.connections.append([client, time.time()])

            for r in p.rr:
                ip = utils.to_bytes(str(r.rdata))
                if not utils.check_ip_valid(ip):
                    if ip == domain:
                        continue

                    ip_ips = self.query(ip, dns_type)
                    ips += ip_ips
                else:
                    ips.append(ip)

            xlog.debug("DNS %s %s return %s t:%f", self.protocol, domain, ips,
                       t2 - t0)
            return ips
        except Exception as e:
            t1 = time.time()
            t = t1 - t0
            xlog.exception("DnsOverHttpsQuery query %s cost:%f fail:%r",
                           domain, t, e)
            return []
Exemple #27
0
    def on_udp_query(self, rsock, req_data, addr):
        start_time = time.time()
        try:
            request = DNSRecord.parse(req_data)
            if len(request.questions) != 1:
                xlog.warn("query num:%d %s", len(request.questions), request)
                return

            domain = utils.to_bytes(str(request.questions[0].qname))

            if domain.endswith(b"."):
                domain = domain[:-1]

            type = request.questions[0].qtype
            if type not in [1, 28]:
                xlog.info("direct_query:%s type:%d", domain, type)
                return self.direct_query(rsock, request, addr)

            xlog.debug("DNS query:%s type:%d from %s", domain, type, addr)

            ips = self.query(domain, type)
            if not ips:
                xlog.debug("query:%s type:%d from:%s, get fail, cost:%d",
                           domain, type, addr,
                           (time.time() - start_time) * 1000)

            reply = DNSRecord(DNSHeader(id=request.header.id,
                                        qr=1,
                                        aa=1,
                                        ra=1,
                                        auth=1),
                              q=request.q)
            ips = utils.to_bytes(ips)
            for ip_cn in ips:
                ipcn_p = ip_cn.split(b"|")
                ip = ipcn_p[0]
                if b"." in ip and type == 1:
                    reply.add_answer(RR(domain, ttl=60, rdata=A(ip)))
                elif b":" in ip and type == 28:
                    reply.add_answer(
                        RR(domain, rtype=type, ttl=60, rdata=AAAA(ip)))
            res_data = reply.pack()

            rsock.sendto(res_data, addr)
            xlog.debug("query:%s type:%d from:%s, return ip num:%d cost:%d",
                       domain, type, addr, len(reply.rr),
                       (time.time() - start_time) * 1000)
        except Exception as e:
            xlog.exception("on_query except:%r", e)
Exemple #28
0
 def createTxtResponse(self, data, request):
     # I embebed sopt data in one RR in TXT Response (but you can split sotp data in multiple RR)
     dataRawEnc = urlsafe_b64encode(data)
     dataEnc = str(dataRawEnc, "utf-8")
     self._LOGGING_ and self.logger.debug_all(
         f"[{self.name}] createTxtResponse() with sotp_data: {dataEnc}")
     reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1),
                       q=request.q)
     reply.add_answer(
         RR(rname=request.q.qname,
            rtype=QTYPE.TXT,
            rclass=CLASS.IN,
            ttl=self.ttl,
            rdata=TXT(dataEnc)))
     return reply
Exemple #29
0
 def createMxResponse(self, data, request):
     dataRawEnc = urlsafe_b64encode(data)
     dataEnc = str(dataRawEnc, "utf-8")
     self._LOGGING_ and self.logger.debug_all(
         f"[{self.name}] createMxResponse() with sotp_data: {dataEnc}")
     rdomain = self.getDomainFromRequest(request.q.qname.idna()[:-1])
     reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1),
                       q=request.q)
     reply.add_answer(
         RR(rname=request.q.qname,
            rtype=QTYPE.MX,
            rclass=CLASS.IN,
            ttl=self.ttl,
            rdata=MX(f"{dataEnc}.{rdomain}")))
     return reply
Exemple #30
0
 async def __forward_query(self, request, addr):
     """
     本服务没查到,转发查询到其他DNS服务器
     :return:
     """
     qname = str(request.q.qname)
     qtype = request.q.qtype
     qclass = request.q.qclass
     lookup = DNSRecord(q=DNSQuestion(qname, qtype, qclass))
     id = lookup.header.id
     self.peers[id] = addr
     self.requests[id] = request
     self.transport.sendto(lookup.pack(),
                           (random.choice(config.forward_dns), 53))
     self.logger.info("<<<>>> Froward")