On Android, in Settings, Network & internet, Private DNS, you can only provide one in "Private DNS provider hostname" (AFAIK).
Btw, I really don't understand why it does not accept an IP (1.1.1.1), so you have to give an address (one.one.one.one). It would be more sensible to configure a DNS server from an IP rather than from an address to be resolved by a DNS server :/
> So if you want to use DNS over HTTPS on Android, it is not possible to provide a fallback.
Not true. If the (DoH) host has multiple A/AAAA records (multiple IPs), any decent DoH client would retry its requests over multiple or all of those IPs.
Does Cloudflare offer any hostname that also resolves to a different organization’s resolver (which must also have a TLS certificate for the Cloudflare hostname or DoH clients won’t be able to connect)?
DoH hosts can resolve to multiple IPs (and even different IPs for different clients)?
Also see TFA
It's worth noting that DoH (DNS-over-HTTPS) traffic remained relatively stable as most DoH users use the domain cloudflare-dns.com, configured manually or through their browser, to access the public DNS resolver, rather than by IP address. DoH remained available and traffic was mostly unaffected as cloudflare-dns.com uses a different set of IP addresses.
> A cross-organizational fallback is not possible with DoH in many clients, but it is with plain old DNS.
That's client implementation lacking, not some issue inherent to DoH?
The DoH client is configured with a URI Template, which describes how to construct the URL to use for resolution. Configuration, discovery, and updating of the URI Template is done out of band from this protocol.
Note that configuration might be manual (such as a user typing URI Templates in a user interface for "options") or automatic (such as URI Templates being supplied in responses from DHCP or similar protocols). DoH servers MAY support more than one URI Template. This allows the different endpoints to have different properties, such as different authentication requirements or service-level guarantees.
Yes, but this restriction of only a single DoH URL seems to be the norm for many popular implementations. The protocol theoretically allowing better behavior doesn't really help people using these.
> By making the Send button larger and more prominent, participants were able to spot the button four times faster.
By making the Back button larger and more prominent instead, participants would be able to spot the button four times faster. I suggest to reduce the size of the Send button.
As a non-native Ensligh speaker, I asked ChatGPT what the ER acronym means: Emergency Room (I guessed E was for "Emergency" after reading your story, but I didn't know about the 'R').
AI is also helpful to parse the story about how AI is helpful.
I'm French too, and when I was young, I learnt the Basic language before learning English (by reading Basic programs already available on our computer).
So I was able to write FOR … THEN … ELSE blocks without even knowing what the keywords meant (I just know what they did to the program). One day, I explained to my father what I was writing, and I read out loud FOR … "TEN" … "ELCE" (with a strong French accent), and he corrected me by pronouncing the words correctly ("FOR … THEN … ELSE"). I was shocked: "how do you know?" (he knew nothing about Basic or even programming).
I learnt that day that "for", "then" and "else" were not just keywords in the Basic language, but they were actually real words in English.
> "XMPP still largely resembles a synchronous protocol with limited support for rich media, which can’t realistically be deployed on mobile devices. If XMPP is so extensible, why haven’t those extensions quickly brought it up to speed with the modern world?"
Intuitively, I would say that this is actually undefined behavior (it would probably be difficult to expose a wrong behavior in practice though).
In C specs, I found 6.5.2.2, paragraph 9:
> If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.
It's worse than that. This guy takes a void* function and casts it to a char* function, then passes it a char**.
void (*name)(char *ptr);
typedef void (*name_func)(char *ptr);
void target(void *ptr)
{
printf("Input %p\n", ptr);
}
char *data = "string";
name = (name_func)target; // Illegal: casting fn that takes void* to a fn that takes char*
name(&data); // Illegal: passing a char** into a function that takes char*
Before someone mentions qsort(): the comparator function really is supposed to take a void*, and inside the function, you re-cast the void* argument to a pointer type of your desire. If you don't do it in that order, you're using it wrong.
The C standard mandates that a “pointer to void shall have the same representation and alignment requirements as a pointer to a character type”. In consequence, the same holds for an array of void pointers vs. an array of char pointers. The code therefore seems valid to me, and furthermore at no point is a function called with an argument type different from its formal parameters.
In the GP example, a char** is passed where a char* is expected. That is clearly invalid.
I was more referring to how, in the K&R example, a function of type "int (*)(char *, char *)" is cast to "int (*)(void *, void *)", in contradiction to what they said here:
> Before someone mentions qsort(): the comparator function really is supposed to take a void*, and inside the function, you re-cast the void* argument to a pointer type of your desire. If you don't do it in that order, you're using it wrong.
In contrast, the K&R example is an example of explicitly undefined behavior in the C standard:
The behavior is undefined in the following circumstances:
- A pointer is used to call a function whose type is not compatible with the pointed-to type (6.3.2.3).
The second edition was released in 1988, and it was based on a draft of the first ANSI C standard, and even then, the line stating that this was undefined behavior was already present.
> A pointer to a function is converted to point to a function of a different type and used to call a function of a type not compatible with the original type (3.3.4).
Ah, I missed that. However, footnote 41 states “The same representation and alignment requirements are meant to imply interchangeability as
arguments to functions […]”, which could be taken as implying compatibility of function types, though the normative wording doesn’t directly support that.
In C, all pointers are compatible to a void pointer without casting though (e.g. assigning a char pointer to a void pointer or the other way around is completely legal - I think that was the main reason why void pointers were added in the first place - it's basically an 'any pointer'). It's only C++ where this is an error (which is weird on its own, why even allow void pointers in C++ when the only reason they (presumably) exist doesn't work anymore).
The above code still doesn't compile in C because C complains about the function signatures being incompatible, even though the only difference is the argument type which itself is 'equivalent' in C - which I guess could be interepreted as an edge case in the C spec? (IMHO the function prototypes should actually be compatible).
"Compatibility" has a specific meaning in C which isn't the same as what you're describing here. What you're talking about is implicit conversions without narrowing.
The C standard is quite loose about pointer requirements. Conversions between data pointers or between function pointers are allowed, but conversions between each other are not guaranteed. You can read the nitty gritty here: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
POSIX defines stricter requirements for pointers than the C standard. All pointers are compatible, so they have the same size and representation, and conversions between function pointers and void* are explicitly allowed.
you can convert a short to an int without casting and it will roundtrip without loss of data; similarly you can roundtrip any data pointer through a void pointer without loss of data. It doesn't mean that an int has the same representation of a short or a void* has (necessarily) the same representation as any other pointer.
In C++ any pointer is also implicitly convertible to a void *, it is the reverse implicit conversion that is prohibited as it is not safe in the general case.
For consistency C++ should also prohibit implicit narrowing conversions (e.g. int to short ); I guess this was thought to break too much existing code and it is generally safer than a pointer conversion (although potentially lossy the behavior is fully defined). Many compilers will warn about it, and narrowing conversions are now prohibited in braced initialization.
Yes. Simply put, void pointers and struct pointers (the actual breaking example in Curl) are not guaranteed to have the same byte-level representation, by the C standard. (For example, a struct pointer may always be aligned and therefore use fewer bits than a void pointer.) Passing one to a function that expects the other may result in arbitrary breakage.
Maintaining a fork by merging upstream into downstream branches would be a mess: upstream and downstream code would be totally entangled, there would no clean way to edit/remove/reorder downstream commits. You can't easily know what you actually changed (not the full diff, but each "atomic" clean change).
> there would no clean way to edit/remove/reorder downstream commits
Yes, but I consider commits immutable so reordering or editing commits is not a thing that would happen. New commits are appended (either upstream or downstream), and any conflicting changes then of course have to be merged from upstream into downstream.
Merging seems easier? With merging you can get a definite answer about which commits are where. With rebase you’re dealing with changes/patches so the answer is less clear.
But it might be cleaner (for some definition of clean) to have a for which is just N commits on top of the upstream. Which is then rebased periodically.
It depends on whether the downstream only ever consumes upstream changes, or also often contributes changes to upstream. Rebasing in both directions can result in a messy history, too, especially if upstream isn't very careful in how it accepts pull requests.
Btw, I really don't understand why it does not accept an IP (1.1.1.1), so you have to give an address (one.one.one.one). It would be more sensible to configure a DNS server from an IP rather than from an address to be resolved by a DNS server :/
reply