Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add IPv6 block rotating #713

Merged
merged 40 commits into from
Mar 5, 2022
Merged

feat: add IPv6 block rotating #713

merged 40 commits into from
Mar 5, 2022

Conversation

Roki100
Copy link
Contributor

@Roki100 Roki100 commented Sep 28, 2020

This feature was coded in a collab with @Million900o
Hello! I would like to present the new feature(s):

  • Rotating trough IPv6 block to stop YouTube IP Bans (/58 block or larger recommended, however /64 should work aswell)
  • Ability to disable chunking, as it is not needed while streaming in 1x speed (in for example Discord bots)

How does the method implemented work: in short, it uses different IPv6 address each time you download/request something via ytdl, for larger blocks(that include more that one /64) it also uses random /64 to make IP Bans impossible to get.

Feel free to propose changes, as this code is for sure not the cleanest

Note: The IPv6 rotating will work only if the block provided is owned by you and configured in the OS!
For setting up guide i recommend reading:
https://contabo.com/blog/adding-ipv6-connectivity-to-your-server
https://contabo.com/blog/configuring-additional-ip-addresses/
https://support.us.ovhcloud.com/hc/en-us/articles/360002296020-How-to-Configure-an-IPv6-Address-in-Ubuntu
Or googling for more tutorials.

Also note: This functionality has been tested with /64, /58 and /48 blocks, and it works, however its a first version and i will keep improving that.
Adding new ytdl dependency was needed because its kinda complex/huge math to get a random ipv6 subnet.

closes #635

Inspired by Lavalink's rotator.

@Roki100
Copy link
Contributor Author

Roki100 commented Sep 28, 2020

I would like to also give our test results about IP Bans (tested on a 6.5k+ servers discord music bot):

  • IPv4: ~2 days
  • IPv6 /64: none
  • IPv6 /58: none
  • IPv6 /48: none

Roki100 and others added 2 commits September 28, 2020 18:02
Fixing the format of files so eslint does not throw any errors.
@TimeForANinja
Copy link
Collaborator

I guess you already spottet that the CI asks you to add tests for the new code lines 😉

@alexanderpaolini
Copy link
Contributor

How do we make tests?

@Roki100
Copy link
Contributor Author

Roki100 commented Sep 28, 2020

Done

test/info-test.js Outdated Show resolved Hide resolved
@fent
Copy link
Owner

fent commented Oct 3, 2020

hi, thanks for the PR Roki and Million900o! it's pretty impressive that there are no bans with this on your bot during your testing. how long have you ran it for using the PR?

  • Rotating trough IPv6 block to stop YouTube IP Bans (/58 block or larger recommended, however /64 should work aswell)
  • Ability to disable chunking, as it is not needed while streaming in 1x speed (in for example Discord bots)

I'm going to start looking at this within the next day or so, but I will say that generally a PR is one change. keeping PRs small and contained to their scope not only makes the review process simpler, but it makes history cleaner, release changes easier to list, and if we ever have to git bisect or git revert we can do that per PR.

please separate the above two changes into 2 PRs. I'd keep this one, remove any changes about disabling chunking since those changes should be smaller than IPv6 rotating, and create another one with only the chunking changes. but also, chunking can be disabled with dlChunkSize: Infinity, or is that not the same?

@Roki100
Copy link
Contributor Author

Roki100 commented Oct 3, 2020

hi, thanks for the PR Roki and Million900o! it's pretty impressive that there are no bans with this on your bot during your testing. how long have you ran it for using the PR?

  • Rotating trough IPv6 block to stop YouTube IP Bans (/58 block or larger recommended, however /64 should work aswell)
  • Ability to disable chunking, as it is not needed while streaming in 1x speed (in for example Discord bots)

I'm going to start looking at this within the next day or so, but I will say that generally a PR is one change. keeping PRs small and contained to their scope not only makes the review process simpler, but it makes history cleaner, release changes easier to list, and if we ever have to git bisect or git revert we can do that per PR.

please separate the above two changes into 2 PRs. I'd keep this one, remove any changes about disabling chunking since those changes should be smaller than IPv6 rotating, and create another one with only the chunking changes. but also, chunking can be disabled with dlChunkSize: Infinity, or is that not the same?

The bot currently runs on a /64 block for over a week now and there are no bans so far

Also, what do i do to remove chunking changes from this pr? like just remove them and make a commit or there is a command for that? I am not sure about the dlChunkSize: Infinity but chunking: false seems to be easier to understand and it completely "disables" the chunking part in the code

@fent
Copy link
Owner

fent commented Oct 3, 2020

Also, what do i do to remove chunking changes from this pr? like just remove them and make a commit or there is a command for that?

remove the changes for it and make a commit

I am not sure about the dlChunkSize: Infinity but chunking: false seems to be easier to understand and it completely "disables" the chunking part in the code

how about dlChunkSize: 0?

@Roki100
Copy link
Contributor Author

Roki100 commented Oct 3, 2020

remove the changes for it and make a commit

will do in a sec

how about dlChunkSize: 0?

Hmm wouldnt it make ytdl go crazy?

      let start = (options.range && options.range.start) || 0;
      let end = start + dlChunkSize;

so like start and end will be the same

@fent
Copy link
Owner

fent commented Oct 3, 2020

Hmm wouldnt it make ytdl go crazy?

      let start = (options.range && options.range.start) || 0;
      let end = start + dlChunkSize;

so like start and end will be the same

oh i meant have it be turned off if it detects that dlChunkSize === 0

@Roki100
Copy link
Contributor Author

Roki100 commented Oct 3, 2020

oh i meant have it be turned off if it detects that dlChunkSize === 0

Hmmm, i might change it to that then i guess(with addition to readme), but first of all i have to remove chunking stuff from this pr and open second one

@Roki100
Copy link
Contributor Author

Roki100 commented Oct 3, 2020

Okay, i think i've removed every change for chunking

lib/util.js Outdated Show resolved Hide resolved
lib/util.js Outdated Show resolved Hide resolved
lib/util.js Outdated Show resolved Hide resolved
test/download-test.js Outdated Show resolved Hide resolved
test/info-test.js Outdated Show resolved Hide resolved
Copy link
Collaborator

@TimeForANinja TimeForANinja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boy typing with a broken hand suxx
next time i'll send a voice memo

README.md Outdated
@@ -149,11 +151,16 @@ ytdl cannot download videos that fall into the following
* YouTube Premium content (if you have access, requires [cookies](example/cookies.js))

Generated download links are valid for 6 hours, and may only be downloadable from the same IP address.
Using IPv6 should not cause same IP address limitation.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you check with

  • different ip from same /64 blocks or
  • even different /64 blocks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did check with multiple ipv4's and ipv6 in same /64

package.json Outdated Show resolved Hide resolved
lib/utils.js Outdated
*/
exports.getRandomIPv6 = block => {
let subnet = parseInt(block.split('/')[1]);
block = block.split('/')[0];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm personally not a fan of reassigning to passed variables
also calling the expensive split function twice....
how about sth like

const [net, rawSubnet] = block.split('/');
const subnet = parseInt(rawSubnet)

?

lib/utils.js Outdated
block = block.split('/')[0];
if (!subnet || subnet > 128 || subnet < 24) throw Error('Invalid IPv6 subnet');
// Other errors will be thrown by ip6 itself
return ip6.randomSubnet(block, subnet, 128, 1, true)[0];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see we're not first rolling the /64 net
like it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea it was a requested change by you so i changed 👀

typings/index.d.ts Outdated Show resolved Hide resolved
Comment on lines 43 to 62
describe('With IPv6 Block', () => {
it('Sends request with IPv6 address', async() => {
const id = '_HSylqgVYQI';
const scope = nock(id, 'regular');
let info = await ytdl.getInfo(id, { IPv6Block: '2001:2::/48' });
nock.url(info.formats[0].url).reply(function checkAddr() {
// "this" is assigned by the function checkAddr
// eslint-disable-next-line no-invalid-this
assert.ok(net.isIPv6(this.req.options.localAddress));
scope.done();
});
});
});

describe('With invalid IPv6 Block', () => {
it('Should give an error', async() => {
const id = '_HSylqgVYQI';
await assert.rejects(ytdl.getInfo(id, { IPv6Block: '2001:2::/200' }), /Invalid IPv6 subnet/);
});
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could test the getRandomIPv6 (but not here) function instead of doing the same pass an ipv6 test (like above) twice

so that we do
a) test the whole usage via ytdl-core once
b) do white box tests only on the getRandomIPv6 function for error like
b.a) no subnet (2001:2::) or
b.b) subnet only (/64) or
b.c) passing errors from ip6.randomSubnet

@TimeForANinja TimeForANinja requested a review from fent April 10, 2021 21:52
@TimeForANinja
Copy link
Collaborator

if you wanna get rid of that ipv6 library 😉

const randomIP = (ip) => {
    const [addr, mask] = ip.split('/');
    // normalize inputs
    let base10Mask = parseInt(mask);
    const base10addr = normalizeIP(addr);
    // get random addr to pad with
    // using Math.random since we're not requiring high level of randomness
    const randomAddr = new Array(8).fill(1).map(x => Math.floor(Math.random() * 0xffff));
    // merge base10addr with randomAddr
    const mergedAddr = randomAddr.map((randomItem, idx) => {
        // calculate the amount of static bits
        const staticBits = Math.min(base10Mask, 16);
        // adjust the bitmask with the staticBits
        base10Mask -= staticBits;
        // calculate the bitmask
        // lsb makes the calculation way more complicated
        const mask = 0xffff - ((2**(16 - staticBits)) - 1);
        // combine base10addr and random
        return (base10addr[idx] & mask) + (randomItem & (mask ^ 0xffff));
    });
    // return new addr
    return mergedAddr.map(x => x.toString('16')).join(':');
}

const normalizeIP = (ip) => {
    // split by fill position
    const parts = ip.split('::').map(x => x.split(':'));
    // normalize start and end
    const partStart = parts[0] || [];
    const partEnd = parts[1] || [];
    // placeholder for full ip
    const fullIP = new Array(8).fill(0);
    // fill in start and end parts
    for (let i = 0; i < Math.min(partStart.length, 7); i++) {
        fullIP[i] = parseInt(partStart[i], 16) || 0;
    }
    for (let i = 0; i < Math.min(partEnd.length, 7) ; i++) {
        fullIP[7 - i] = parseInt(partEnd[i], 16) || 0;
    }
    return fullIP;
}

@muhitrhn
Copy link

muhitrhn commented Jun 27, 2021

Can anyone help me to setup IPv6 on AWS console and setting it up with? (I already have all the codes in place in the latest version.)

I really can’t get it working.

@TimeForANinja
Copy link
Collaborator

@Roki100 can you have another look, pls

@Roki100
Copy link
Contributor Author

Roki100 commented Jul 30, 2021

@Roki100 can you have another look, pls

oh, yea sure, i have to come back from holiday tho as i dont have pc atm (3 days)

@TimeForANinja
Copy link
Collaborator

sure - just tell me when you had a look 😉

lib/utils.js Outdated Show resolved Hide resolved
Co-authored-by: Voltrex <[email protected]>
@stale
Copy link

stale bot commented Dec 9, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale Issues that were closed for inactivity label Dec 9, 2021
@Roki100
Copy link
Contributor Author

Roki100 commented Dec 13, 2021

@TimeForANinja aww, my bad on not responding, yeah everything seems to be fine 👍
Edit: Sorry again for being so late with the response here 😞

@stale stale bot removed the stale Issues that were closed for inactivity label Dec 13, 2021
@TimeForANinja TimeForANinja merged commit 06f5611 into fent:master Mar 5, 2022
@github-actions
Copy link

github-actions bot commented Mar 5, 2022

🎉 This PR is included in version 4.11.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Preventing Error 429: Too Many Requests
7 participants